Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

AngularJs $provide API internals & circular dependency problem.


Published on

A lecture that I've recently presented to my colleagues on some aspects of AngularJS framework internal workings.

Published in: Technology
  • Be the first to comment

AngularJs $provide API internals & circular dependency problem.

  1. 1. AngularJS. An overview of the $provide’s API and $injector. Yan Yankowski Differences between providers, factories, services, values, constants, and decorators.
  2. 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. 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. 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. 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. 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. 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. 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. 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. 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 (){; }); Or angular.service(“MyService”, MyConstructorFunction); function MyConstructor(){ this.value } MyConstructor.prototype.someFunction = function(){ } Will eventually be instantiated as : $injector.instantiate(function (){; }); OR new (function (){; });
  11. 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. 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. 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. 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. 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. 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. 17. What exactly leads to circular dependency exception. • Suppose we have the following code: window.mainApp = angular.module("mainApp", []);["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. 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…