The document provides an overview of AngularJS dependency injection and the $provide service. It explains the differences between providers, factories, services, values, constants, and decorators. It describes how $provide registers these items in the providerCache and instanceCache and instantiates them lazily through the $injector. Circular dependencies can cause errors since items point to each other before being fully instantiated.
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
AngularJs $provide API internals & circular dependency problem.
1. AngularJS. An overview of the
$provide’s API and $injector.
Yan Yankowski
Differences between
providers, factories, services, values,
constants, and decorators.
2. createInjector Method
•
providerCache – contains references to instantiated providers (after the body
function of the provider has been invoked as the provider’s constructor). All
custom factories and services in the application will be converted to providers and
registered here on application start. When the providerCache object is created it
always has $provide as the 1-st cached provider. This providerCache object also
has constants cached.
The $get method
hasn’t been
invoked.
3. • instanceCache – all instantiated providers (i.e.
whose method $get has been called) and
constants will be cached here for further
reusage.
A logManagerProvider’s $get
method has already been
invoked. Hence we see here
an instantiated service
object providing logging
functionality.
4. createInternalInjector Method
• How the provider is instantiated
1
providerCache
2
Important!
At this stage providerCache
dictionary is used by the
getService method.
The algorithm of the instantiation:
1.
Check whether the provider is already
instantiated (saved in the providerCache). If yes –
return it from the cache. Normally all the
providers defined in the application are already
there at this point.
2.
If the value in the cache points to the
INSTANTIATING token, then we are inside the
instantiation process of a dependency of some
other parentModule . The problem is that we are
also dependent on the parentModule and trying
to instantiate it as well. A chicken and an egg
problem.
3.
If no instance is found in the providerCache then
the exception will be thrown upon accessing the
key. It usually means that either the required
dependency is not provided or the js-file is not
included.
3
function() {
throw Error("Unknown provider: " + path.join(' <- '));
}
5. • How the instance of a service object is created
Important!
At this stage instanceCache
dictionary is used by the
getService method.
1
instanceCache
The algorithm of the instantiation:
1.
Check whether the instance is already
created (saved in the instanceCache). If yes –
return it from the instanceCache.
If the value in the cache points to the
INSTANTIATING token, then some other
service is simultaneously trying to instantiate
given object.
If no instance is found in the instanceCache
then the factory function takes the
responsibility of instantiating the object. The
instance is then cached.
2.
2
3.
3
The invoke method pipes the dependency names of the service,
instantiates each one of them with the getService method, then
loads the instantiated dependencies into the args array, iterates
over the args array and calls the provider body function, passing
it each dependency as a parameter.
function(servicename) {
var provider = providerInjector.get(servicename
+providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
}
6. Provider
function provider(name, provider_) {
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw Error('Provider ' + name + ' must define $get factory method.');
}
return providerCache[name + providerSuffix] = provider_;
}
•
•
The name parameter is a string containing the name of the provider
The provider_ parameter must be one of the following three types:
1.
2.
3.
A function that returns an object with a $get method.
An array. In this case the last element of this array is always a function (cf. 1-st item of the list) or an object which has the $get method. All
the previous items of the array are treated as arguments to be injected upon the provider instantiation.
An object containing the method $get.
7. • Points of interest:
1) must define $get method (which in its turn
returns a factory/service object inside itself);
2) Uses providerInjector to retrieve its instance
(calls getService method internally, which in
it’s turn retrieves the instance of the provider
from the providerCache);
3) Once the $get method has been invoked the
instanceInjector will be used to retrieve the
instance of the created service object (from
instanceCache).
8. Factory
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
The factory defines the body for the $get method of the underlying provider. We can see it by
looking at the above code block. Internally factory calls the provider registration method of the
$provide object, basically being a wrapper over it.
9. Service
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
• uses $injector for instantiation;
• uses constructor function for instantiation – it
means that the service function is treated as a
constructor;
10. • When do I prefer Service to Factory ?
The service is preferable when you want to
define a constructor function to be instantiated
with new.
angular.service(“MyService”, function (){
this.foo;
});
Or
angular.service(“MyService”, MyConstructorFunction);
function MyConstructor(){ this.value }
MyConstructor.prototype.someFunction =
function(){ }
Will eventually be instantiated as :
$injector.instantiate(function (){
this.foo;
});
OR
new (function (){
this.foo;
});
11. So what is the main difference
between the Factory and the Service?
• Factory wrapper returns an instance of an
object.
• Service wrapper defines a constructor of an
object. This constructor will be later used to
instantiate a return object by the factory.
12. Value
function value(name, value) { return factory(name, valueFn(value)); }
function valueFn(value) {return function() {return value;};}
•
From the above code we see that the value method is a wrapper over factory
method. Hence a value is just one more layer over a provider registration method.
When to use?
• when you don’t need complicated logic and encapsulation;
• when you want to provide simple object for further injection.
13. Constant
function constant(name, value) {
providerCache[name] = value;
instanceCache[name] = value;
}
Important!
Both providerCache[constantName] and
instanceCache[constantName] point to
the same instance, which means that the
constant is equally usable during the
config and run stages..
• The constant object value can be accessed and used
during the configuration phase of the application. The
method $get of the providers hasn’t yet been called at
this stage, but the constants don’t need $get to be
called – they are already fully instantiated. So the
constants are the only objects inside the application
scope able to provide custom functionality at this
stage.
14. • When the application is started a new
instance of the constant object is placed into
providerCache and instanceCache (since no
call to the method $get is needed) .
The constant object
is fully available on
the application
configuration stage.
15. Good to know that …
•
•
The constant object is not interceptable by the decorator since it
lacks the $get function!
In the Jasmine testing framework using angular mock lib the mock
of the constant is created by using $provide.constant() method.
16. Decorator
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
Get the provider to be
decorated
Save reference to
the original $get
method
Wrap the original
$get method
inside a new one.
• Use decorator to add functionality to the
existing services. Useful in cases when new
functionality has to be added into core
AngularJS services without touching the
source.
17. What exactly leads to circular
dependency exception.
• Suppose we have the following code:
window.mainApp = angular.module("mainApp", []);
mainApp.run(["mainLogger", function (mainLogger) {
mainLogger.log();
}]);
mainApp.service("mainLogger", [" secondaryLogger", function (secondaryLogger) {
this.log = function() {
console.log(); };
}]);
mainApp.service("secondaryLogger", ["mainLogger", function (mainLogger) {
this.log = function () { console.log(); };
}]);
Both services here are dependent on each other.
18. When a new service
(mainLogger) is
registered, its name is
first inserted as a key
into instanceCache
with the value pointing
to the INSTANTIATING
token. No actual
provider object is yet
created.
AngularJS then proceeds to creating mainLoggerProvider object: registers it in the providerCache. The
framework detects that the service has a dependency on secondaryLogger service. To resolve this dependency it
needs to create a secondaryLoggerProvider object, register it in the providerCache, and call its $get method in
order to create an instance of the secondaryLogger service (to inject into mainLoggerProvider). At this point the
framework sees the dependency on mainLoggerProvider and honestly tries to get it from the instanceCache or
to instantiate it. As we remember it is already in the instanceCache dictionary still pointing to the INSTANTIATING
token. Exception follows…