In the documentation for angularJS injector, some of the code uses $injector, while some code uses just injector. I am confused. What are the differences between these two, and where are the appropriate places to use them? I also have the same question for $scope and scope.
When you want to use injector, scope, or any other build in angularjs provider as dependency of other service you need to prefix them with $, as angular by convention registers all build in providers with $ prefix, so scope is registered as $scope, injector as $injector etc.
When angular instantiates objects(using injector btw) it checks what dependencies object has (as it needs to inject them) and the way it's done is by checking variables names of constructor function of that object, so that's why it's so important to name variables correctly or you'll get error unknown provider ...
When you retrieve injector as following:
var injector = angular.injector(['gaad', 'components']);
you don't use $ prefix, as it's normal variable and actually you can call it whatever you want ($injector included).
When you want to have injector as dependency, you need to name it as following:
angular.module('app').factory('$exceptionHandler', function($injector) {
...
});
or (convention needed when you use minification of angularjs scripts):
angular.module('app').factory('$exceptionHandler', ['$injector', function(anyNameYouWant) {
...
}]);
Similar for scope, when used in link function in directive:
link: function(scope, element, attr) {
}
you don't have to call it $scope as nothing is injected here. It's only one of function parameters. You can call it whatever you want, but convention here is to use scope not $scope to differentiate cases when scope is injected and when is used as parameter.
Related
I am trying to test a directive which is defined in a sub-module of another submodule of the root module. I am unable to access the scope variables defined in the controller of the directive. The problem seems to lie in accessing the scope from inside the test.
The main module is named oppia and it has a submodule named stateEditorModule and this submodule also has a submodule named stateContentEditorModule. I have tried injecting $scope instead of $rootScope but it doesn't seem to work.
This is the test code:
beforeEach(function() {
module('oppia');
module('stateEditorModule');
module('stateContentEditorModule');
});
outerScope = $rootScope.$new();
var elem = angular.element(
'<state-content-editor>' +
'</state-content-editor>');
var compiledElem = $compile(elem)(outerScope);
outerScope.$digest();
ctrlScope = compiledElem[0].getControllerScope();
}));
fit('should start with the content editor not being open', function() {
expect(ctrlScope.contentEditorIsOpen).toBe(false);
});
The contentEditorIsOpen variable is defined in the directive's scope.
The contentEditorIsOpen is inaccessible with the current state of the code.
I am stuck on this for a quite long and I would be grateful if someone could provide a solution or point to the documentation on how to test a multi-modular AngularJS app.
Normally you wouldn't test the directive itself. Instead, you would test the controller of that directive.
This means you need to register the controller in DI. Then you can get the controller of that directive directly through DI (without creating and compiling the direcitve), provide an initial scope to that controller, call a function, and assert some vars on that scope to be a specific value.
Example from the doc:
https://docs.angularjs.org/guide/unit-testing#testing-a-controller
If you are using components instead of directives, check out this:
https://docs.angularjs.org/guide/component#unit-testing-component-controllers
UPDATE:
I used the controllerAs property of the directive to give a name to the controller as stateContentEditorController
controllerAs only creates an alias for that controller in the scope of the template, but it won't register it in the DI (dependency injection) module, so you can't retrieve it from the DI system.
What I suggested was to write your directive and your controller separately and register your controller using angular.module('moduleName').controller('controllerName', [...]). Then in your directive definition object just write {controller: 'controllerName', ...}. This way you can retrieve the controller in your tests.
In Angular, when registering a directive to a module, does the directive factory function get invoked using new or just with simple function call?
eg.
var MyDirective = function() {
return {
link: function() { ... }
};
}
module('myMod', []).directive('myDirective', MyDirective);
Does MyDirective get called internally as:
... = MyDirective();
or as
... = new MyDirective();
The Angular Guide on providers states:
Earlier we mentioned that we also have special purpose objects that
are (...) are Controller, Directive, Filter and Animation.
The instructions for the injector to create these special objects
(with the exception of the Controller objects) use the Factory recipe
behind the scenes.
This fact be clearly seen in compile.js source code. And because we know that factory recipe in Angular simply invokes the function (with its dependencies, via $injector.invoke(fn)) so the correct answer to your question is ... = MyDirective();
It is invoked using an $injector, i.e. $injector.invoke(MyDirective), so that dependencies can be resolved and injected. Internally, $injector.invoke call MyDirective(), without the new, and pass in the dependencies as arguments.
I'm wondering where and if there's a documentation on what can be injected into a function with dependency injection.
I understand that all registered services/factories in angularjs can be injected but when stumbling upon a solution to a problem I had, I read about the following:
function AppController($scope, $element, $compile) {
// ...
}
So from the tutorials I know $scope (but I did not find anything about $scope or scope in http://docs.angularjs.org/api/ - all I found was $rootScope).
Same with the $element. But $compile is a service just like $http - I understand where they come from.
Obviously I'm missing a very basic point about dependency injection and would be happy if someone could explain it to me.
$rootScope is an instance of $scope, and $scope docs are found here: http://docs.angularjs.org/api/ng.$rootScope.Scope
$element is implicitly injected into compile, controller and linking directive functions and is explained here: http://docs.angularjs.org/api/ng.$compile
While $rootScope can be explicitly injected anywhere you want, $element is available only inside the directives.
In Angularjs, is there a specific reason to use $scope in controllers and scope (without "$") in directives link function? Is it just a convention or anything else?
The case when you do $scope in controller the Dependency Injection injects scope based on matching the variable name $scope, in this case using scope as name would not work.
For case of directive the injection is position based so you can name your variable a or b or any thing. The directive order for link function is
(scope, iElement, iAttrs, controller)
so first element is always scope object.
The module factory methods like controller, directive, factory, filter, service, animation, config and run receive arguments through dependency injection (DI). In case of DI, you inject the scope object with the dollar prefix i.e. $scope. The reason is the injected arguments must match to the names of inject-able objects followed by dollar ($) prefix.
For example, you can inject the scope and element objects into a controller as given below:
module.controller('MyController', function ($scope, $element) { // injected arguments });
When the methods like directive linker function don’t receive arguments through dependency injection, you just pass the scope object without using dollar prefix i.e. scope. The reason is the passing arguments are received by its caller.
module.directive('myDirective', function () // injected arguments here
{
return {
// linker function does not use dependency injection
link: function (scope, el, attrs) {
// the calling function will passes the three arguments to the linker: scope, element and attributes, in the same order
}
};
});
In short, in case of dependency injection the scope object is received as $scope while in case of non-dependency injection scope object is received as scope or with any name.
The reason of this behavior is that, Unlike controllers, directives don't use dependency injection, instead they are passed the scope created by the controller that is placed behind the view.
this is very tricky, So you can reuse your directive on different scopes.
The $ in "$scope" indicates that the scope value is being injected into the current context.
$scope is a service provided by $scopeProvider. You can inject it into controllers, directives or other services using Angular's built-in dependency injector:
module.controller(function($scope) {...})
which is shorthand for
module.controller(['$scope', function($scope) {...}])
However, scope could be anything, it's a function parameter name, could be foo or a12342saa.
function link( scope, element, attributes ) {
// Only scope
}
The "$" in "$scope" indicates that the scope value is being injected into the current context. But, not all references to scope are based on dependency injection.
How do I define a function that has access to all the injected arguments of the controller/directive/service, without passing them explicitly?
myShortHand = function(scope, http, element, url) {
http.get(url).success(function (data) {
element.html(data);
});
}
function MyCtrl($scope, $http, $element) {
$scope.name = 'Superhero';
$scope.click = function() { myShortHand($scope, $http, $element, url); }
}
This is just a simplified example, and shows the amount of boilerplate code. My shorthand actually takes 4 $ arguments and it kind of defeats the purpose of a shorthand! Note that I can't really use a service: $http could be injected in a service, but $scope and $element need to use the same injector as MyCtrl. Is there a way to define controller methods (perhaps in the prototype) that can use dependency injection in their argument list and use the same injector as the instance they are called on?
It is not clear from your question where the myShortHand function is defined but if it happens to be defined within a directive's linking function you could simply relay on the closure scope. In this case you don't need to pass arguments available in a closure.
Next, what you could do is to inject $injector into your directive and pass it in into the helper function as the only argument beside $element and $scope. Then, in your helper function you can retrieve any service by calling $injector.get method. For example, to get access to the $http service you could write:
myShortHand = function(scope, element, url, $injector) {
$injector.get('$http').get(url).success(function (data) {
element.html(data);
});
}
This technique would work if your helper can't relay on the closure scope and needs access to several services.
Having said all the above I would re-consider code split in your helpers. Usually functions that need many arguments are not well-focused and got many side effects (or are doing too much).