Is $scope dependecy injection in AngularJS controller definition? - angularjs

All:
What confused me about dependency injection in AngularJS is its concept(I guess I have still not get the whole idea of DI):
I wonder how to tell which are dependencies that should be(and can be) injected?
For example:
If I define a controller and directive:
app.controller("maincontroller", ["$scope", "dependency1", function($scope, dependency1){
}])
app.directive("dir1", ["dependency2", "dependency3",
function(dependency2, dependency3){
return {
restrict: "AE",
scope:{},
controller: function($scope){},
link:function(scope, EL, attrs, ctrl){}
};
}])
I wonder why I need to inject $scope as dependency in controller, but no need to do that in directive definition. And what is more: if I put $scope in that directive function, it will gives me an error:
Error: error:unpr
Unknown Provider
Unknown provider: $scopeProvider <- $scope <- dir1Directive
[1] Is this $scope a dependency, or [2] my understanding about dependency is totally wrong(someone told me that $scope is not a dependency) or [3] my understanding about directive def function is wrong(only service can be put in directive def function)?
If my wrong is the last one, then how many type of dependencies does AngularJS have?
Thanks

$scope isn't actually a service !
This is why you don't have to pass it as a dependency of the directive. $scope is a local passed to the injector, a value passed to the function, as if i would do console.log(myValue). myValue is not a service.
The $injector's invoke method, which passes services to a function, can replace a requested dependency by a given value, see the docs for more details. I recognize that mixing services and simples values in the arguments list are confusing, as we can't know which one are the services and which one are simple arguments, but it works that way.
We can consider that something like this is executed when a new controller is instanciated :
var $rootScope = $injector.get('$rootScope');
$injector.invoke(controller, context /*the this binding*/, {
$scope: $rootScope.new()
});
See also the code of the controller service for further details.
Update : difference between $scope and services
Take this function :
function add(a, b) {
return a + b;
}
a and b are "simple arguments", they are the values on which the function perform calculations. Imagine that you want to broadcast a message to your app that an addition just have been performed. In Angular, we can use the $rootScope.$broadcast method.
function add(a, b) {
$rootScope.$broadcast('addition.done', a + b);
return a + b;
}
But here, $rootScope has never been declared. We have to load the $rootScope service in our function. Angular uses the $injector.invoke method in order to do this : it takes a function or an array (the "square bracket" notation), extract the services names from the function arguments/array elements, and calls the function with the corresponding services passed as arguments.
function add(a, b, $rootScope) {
$rootScope.$broadcast('addition.done', a + b);
return a + b;
}
var $injector = angular.injector(['ng']); // Creates the injector, with the services of `ng` module.
$injector.invoke(add); // Calls the function with the requested services
It will throw an error, because a and b aren't services. In fact, they don't have to be services, because they are values that we want to set ourself. For the injector, they are locals. In order to perform the addition of 2 and 3 with the add function, we have to do the following :
function add(a, b, $rootScope) {
$rootScope.$broadcast('addition.done', a + b);
return a + b;
}
var $injector = angular.injector(['ng']);
$injector.invoke(add, this, {
a: 2, // When requesting the `a` service, return the value `2`
b: 3 // The same here, with `b` and `3`
});
The same is done with controllers. $scope itself is not a service, but a new scope, build each time the directive is loaded. Like a and b, the newly created $scope is a value on which the function perform some logic and modifications. $scope is not a dependency, but an argument of the function. And because the $injector extracts the dependencies from the arguments list, we can't tell if an argument is a service or not (except if you already know it, but this isn't an evidence). Here, $scope is not a service, and is also the same object that in the link function in your directive. Note the fact that the documentation does not name the scope argument $scope, but scope. If scope was a service, it would throw an error, but actually it doesn't, because the link function isn't called with $injector.invoke.

Related

Why doesn't angular factory return different values?

I've got this fiddle that creates a functional example of the question.
My understanding of module.factory vs. module.service is that the service function you provide is returned by calling return new serviceFunction while the factory function is called and it's return value provided. I thought one implication of this was that you'd get different instances of the factory result.
I created this factory:
app.factory('fact', function() {
return Math.random();
});
and then two controllers with the factory injected. I then referenced the result of the factory method as such:
app.controller('test1Controller', ['$scope', 'fact', function ($scope, fact) {
$scope.fact = fact;
}]);
app.controller('test2Controller', ['$scope', 'fact', function ($scope, fact) {
$scope.fact = fact;
}]);
I expected the result to be two different random numbers, but they are the same. How is this possible if the factory method is being called twice?
Your assumptions are wrong. Whatever the way you choose to create a service, it's always a singleton. Angular will call your service constructor, or your service factory, or your provider function only once, and reuse the created object.

Do Angular Directive Factory Functions Get Invoked as Constructors of Functions

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.

Angularjs: $scope vs scope

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.

When to use '$injector' and just injector in angularjs?

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.

How to define AngularJS Helpers that reuse injectors from their context?

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).

Resources