Here, we discuss some commonly used advanced Inversion of Control containers: lazy-loading, lifetime management, and deferred creation/processing resolution).
Lazy-Loading
The so-called lazy loading is to instantiate the object when you need to use it. Many dependency injection systems create components as dependencies from the beginning. But sometimes, you don't want to instantiate these components until you use them in your application. In Angular, a good example is when you set a behavior during configuration, and the behavior references some components that have not been created yet.
Suppose you want to intercept the system’s built-in $log service, so you store it in $rootScope. Of course I don't recommend this, but this example is simpler and more effective. To intercept, you use $provide during configuration and then call the modified method. If you want to reference $rootScope directly at this time, you will get an exception due to circular reference. And the solution is to lazy load $rootScope via $injector .
The following code will only load $rootScope the first time it is used.
$provide.decorator(, [, , ($delegate, $injector) { log = $delegate.log.bind($delegate); $delegate.log = (msg) { rs = $injector.get(); (rs.logs === undefined) { rs.logs = []; } rs.logs.push(msg); log(msg); }; $delegate; }]);
Following calls will get the same singleton $rootScope. Here is a working example. I seem to have heard an (incorrect) statement before (Angular only supports singletons)... Of course it's not true. The methods in $injector are used to manage the life cycle of your components for you.
Life cycle management
The life cycle involves how you manage instances of components. By default, when you inject an Angular dependency, Dependency Injection will create a copy of it for you and reuse it in your application. Most of the time this is exactly what we expect. In some cases, multiple instances of the same component are required. Assume the following counting service:
Counter($log) { $log.log(); } angular.extend(Counter.prototype, { count: 0, increment: () { .count += 1; .count; } }); Counter.$inject = []; app.service(, Counter);
Your application will keep track of different counters. And after you inject the service, you will always get the same counter. Is this a limitation of Angular?
Of course not. Again, you can create a new copy at any time via the $injector service. The following code uses two independent counters:
app.run([, , , (rs, c, i) { rs.count = c.count; rs.update = c.increment; rs.update2 = () { c = i.instantiate(Counter); rs.count2 = c.count; rs.update2 = () { c.increment(); rs.count2 = c.count; }; }; }]);
You can see that the counters are tracked by separate instances, here is a usable example. If you need to generate new instances frequently, you can register the service like this:
app.factory(, [, (i) { { getCounter: () { i.instantiate(Counter); } }; }]);
It’s that simple to generate the required instance, and you can use your factory component instead $injector:
app.run([, , (rs, cf) { c1 = cf.getCounter(), c2 = cf.getCounter(); rs.count = c1.count; rs.update = c1.increment; rs.count2 = c2.count; rs.update2 = () { rs.count2 = c2.increment(); }; }]);
You can check out this full version of the available example. As you can see, it's entirely possible to manage the lifecycle of your components using Angular's built-in dependency injection. What about deferred resolution - for example, there are components you need to bring in after Angular has been configured, and need to be wrapped with their dependencies.
Deferred Resolution
We have introduced a way to lazily handle dependencies in Angular. When you want to wrap something, you can call instantiate of the $injector service, and it can then resolve the dependency by parameter sniffing, which looks like it would be using the static properties of $inject, or it can also do it by inspecting the It is implemented as an array. In other words, the following is a completely valid way of writing:
$injector.instantiate(['dependency', Constructor]);
You can also call methods on decorated arrays. Suppose you have a method that depends on the $log service. You can call it at runtime with deferred processing, like this:
myFunc = [, ($log) { $log.log(); }]; $injector.invoke(myFunc);
You can take a look at this working example (open your console and see what happens after you press the button).
Summary
To sum up, Angular's dependency injection provides many advanced features that you will want and often use in your business application production line. The convenience of factories, services, and providers often leads Angular developers to believe that there is only one option available. The magic lies in the $injector service, which you can use to generate the required singletons, create new components or dynamically reference methods with dependencies.
Finally, note that injections in your client code are available even outside of Angular. Let’s look at an example of calling the $log service through injection, wrapped outside Angular, click here. Why do we need to pass 'ng' into the method's array? It is a core module of Angular and will be added implicitly when you wrap your module, but if your directive generates its own injected instance, you must add it explicitly.