Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

NgModelController can't be instantiated by $controller in mock tests #7720

Open
pocesar opened this issue Jun 5, 2014 · 3 comments
Open

Comments

@pocesar
Copy link
Contributor

pocesar commented Jun 5, 2014

When testing a service, it's impossible right now to instantiate a new ngModelController and create it on the fly, only through $compile and element.controller(), since ngModelController isn't a named controller, but merely a function name inside the angular closure.

the following all fail:

inject(function(NgModelController) { // of course, it isn't a service, there's no provider
});

inject(function($controller, $rootScope) { 
  // it's not a named controller, can't be instantiated, blind shooting with controller names
  // Error: Argument '...' is not a function, got undefined
  $controller('ngModelController', {$scope: $rootScope.$new() }); 
  $controller('NgModelController', {$scope: $rootScope.$new() });
  $controller('NgModel', {$scope: $rootScope.$new() }); 
  $controller('ngModel', {$scope: $rootScope.$new() });
});

Angular does it internal testing relying on the NgModelController function. The only way for it to work is doing

$compile('<input ng-model="dummy">')($rootScope.$new()).controller('ngModel');
@Narretz
Copy link
Contributor

Narretz commented Jun 20, 2014

This is a problem that also happens if you put your directive controllers into the directive close, because you don't want them to live in the global scope or be available through a module.

The way this works in angular core is that the ngModelController is global before the project is build (where it adds a closure around the whole project) I don't really know how this could be improved, but it seems to be an inconvenience more tha anything.

@rodyhaddad
Copy link
Contributor

As @pocesar hinted to, we can make it a named controller, as in registered with $controller, and referenced by name in the ngModelDirective definition.
That sounds reasonable.

@btford btford removed the gh: issue label Aug 20, 2014
@p0lar-bear
Copy link

p0lar-bear commented Aug 10, 2017

For now I've found that one can inject ngModelDirective, which results in a one-element array containing the $compile object for ngModel. From there you can just reference the controller property of the object to get the ngModelController. This is exactly how $componentController gets the controller of components (albeit using the $injector service).

See: https://stackoverflow.com/a/45601995/887925

describe('...', function () {
    var $controller,
        $rootScope,
        ngModelController;

    beforeEach(inject(function(_$controller_, _$rootScope_, _ngModelDirective_) {
        $controller = _$controller_;
        $rootScope = _$rootScope_;
        ngModelController = _ngModelDirective_[0].controller;
    }));
});

From there you can call $controller on your ngModelController. Just make sure to pass it a scope, jqLite object and an attrs object or else it'll throw a missing dependency error.

var scope = $rootScope.new();
scope.value = null;
$controller(ngModelController, null, {
    $scope: scope,
    $element: angular.element('<input>'),
    $attrs: { ngModel: 'value' }
});

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants