Angular. JS learning-dependency injection $ injector
Before dependency injection (IoC), it is very simple and straightforward to create an Object in the program, that is, to create a new Object in the code, we create, maintain, modify, and delete objects on our own. That is to say, we control the entire lifecycle of objects until they are not referenced and recycled. It is true that this practice is understandable when the number of objects created or maintained is small, but when a large project needs to create objects of an order of magnitude, it only relies on programmers to maintain all objects, this is hard to achieve, especially if we want to reuse some objects throughout the entire life cycle of the program, we need to write a cache module to cache all objects, which increases the complexity. Of course, the benefits of IoC are not limited to this. It also reduces the coupling between dependencies and does not need to be referenced or passed in the code to operate dependencies.
In js, we can introduce dependencies in this way.
1. Use global variables to reference
2. Pass function parameters as needed
The disadvantage of using global variables is that the global namespace is polluted, and the reference can be passed through the function parameter in two ways:
1. Closure Transfer
2. parse the dependency object in the background and pass the parameter through Function. prototype. call.
In AngularJS, dependency injection is implemented through the latter. The following sections will introduce the implementation of the IoC module.
Obtain dependency
Var FN_ARGS =/^ function \ s * [^ \ (] * \ (\ s * ([^ \)] *) \)/m; var FN_ARG_SPLIT = /,/; // obtain the service name var FN_ARG =/^ \ s *(_?) (\ S ++ ?) \ 1 \ s * $/; var STRIP_COMMENTS =/(\/. * $) | (\/\ * [\ s \ S] *? \ * \/)/Mg; var $ injectorMinErr = minErr ('$ injector'); function anonFn (fn) {// For anonymous functions, showing at the very least the function signature can help in // debugging. var fnText = fn. toString (). replace (STRIP_COMMENTS, ''), args = fnText. match (FN_ARGS); if (args) {return 'function ('+ (args [1] | ''). replace (/[\ s \ r \ n] +/, '') + ')';} return 'fn ';} function annotate (fn, strictDi, name ){ Var $ inject, fnText, argDecl, last; if (typeof fn = 'function') {if (! ($ Inject = fn. $ inject) {$ inject = []; if (fn. length) {if (strictDi) {if (! IsString (name) |! Name) {name = fn. name | anonFn (fn);} throw $ injectorMinErr ('strictdi ',' {0} is not using explicit annotation and cannot be invoked in strict mode', name );} fnText = fn. toString (). replace (STRIP_COMMENTS, ''); argDecl = fnText. match (FN_ARGS); forEach (argDecl [1]. split (FN_ARG_SPLIT), function (arg) {arg. replace (FN_ARG, function (all, underscore, name) {$ inject. push (name) ;}) ;};} fn. $ inject = $ inject;} else if (isArray (fn) {last = fn. length-1; assertArgFn (fn [last], 'fn '); $ inject = fn. slice (0, last);} else {assertArgFn (fn, 'fn ', true);} return $ inject ;}
The annotate function performs targeted analysis on the input parameters. If a function is passed, the dependent module is passed as the input parameter. In this case, regular expression matching can be performed through the serialization function, obtain the name of the dependent module and store it in the $ inject array. In addition, an exception is thrown when dependency is passed through function input parameters in strict mode; the second type of dependency transmission is through an array. The last element of the array is the function that needs to be depended on. The annotate function returns the parsed dependency name.
Create a Injector
AngularJS APIS also provide injector. Through injector, you can use methods such as get, has, instantiate, invoke, and annotate mentioned in the previous section, you can better understand the source code.
Function createInternalInjector (cache, factory) {// For the service injector providerInjector, retrieve the service only according to the service name. The factory will throw an exception function getService (serviceName, caller) {if (cache. hasOwnProperty (serviceName) {if (cache [serviceName] === INSTANTIATING) {throw $ injectorMinErr ('cdep', 'Circular dependency found: {0 }', serviceName + '<-' + path. join ('<-');} return cache [serviceName];} else {try {path. unshift (servic EName); cache [serviceName] = INSTANTIATING; return cache [serviceName] = factory (serviceName, caller);} catch (err) {if (cache [serviceName] === INSTANTIATING) {delete cache [serviceName];} throw err;} finally {path. shift () ;}} function invoke (fn, self, locals, serviceName) {if (typeof locals === 'string') {serviceName = locals; locals = null ;} var args = [], // parse and obtain the list of injection services $ inject = anno Tate (fn, strictDi, serviceName), length, I, key; for (I = 0, length = $ inject. length; I <length; I ++) {key = $ inject [I]; if (typeof key! = 'String') {throw $ injectorMinErr ('itkn ', 'encrect injection token! Expected service name as string, got {0} ', key);} // The injected service is passed into args. push (locals & locals. hasOwnProperty (key) as a parameter )? Locals [key]: getService (key, serviceName);} if (isArray (fn) {fn = fn [length];} // http://jsperf.com/angularjs-invoke-apply-vs-switch /// #5388 return fn. apply (self, args);} function instantiate (Type, locals, serviceName) {// Check if Type is annotated and use just the given function at N-1 as parameter // e.g. someModule. factory ('greeter ', [' $ Windows', function (renamed $ window) {}]); // Object creation: http://jsperf.com/create-constructor/2 Var instance = Object. create (isArray (Type )? Type [Type. length-1]: Type ). prototype); var returnedValue = invoke (Type, instance, locals, serviceName); return isObject (returnedValue) | isFunction (returnedValue )? ReturnedValue: instance;} return {invoke: invoke, instantiate: instantiate, get: getService, annotate: annotate, has: function (name) {return providerCache. hasOwnProperty (name + providerSuffix) | cache. hasOwnProperty (name );}};}
The createInternalInjector method creates a $ injector object. The passed parameters are cache objects and factory functions. In specific implementation, AngularJS creates two injector objects-providerInjector and instanceInjector (the differences between the two objects are mainly because the cached objects passed by the createInternalInjector method are different), but through angular. injector () exports instanceInjector. ProviderInjector is mainly used to obtain the service provider, that is, serviceProvider. For instanceInjector, it is mainly used to execute the $ get method of the provider object obtained from providerInjector, produce the service object (dependency), and pass the service object to the corresponding function to complete IoC.
First of all, let's start with the get method. The get method mainly obtains the service with the specified name, and obtains the instanceInjector through the angular injector method. When this service object (dependency) is not in the cache, we need to execute the factory (serviceName, caller) method. Let's look at the factory function:
instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); }));
The red part is the factory function. It shows that the provider serviceProvider of the corresponding service is obtained through providerInjector, and then the $ get method of serviceProvider is executed in the serviceProvider context by calling the invoke method of instanceInjector, return the service object and save it in the cache. In this way, the service object (dependency) is obtained and cached.
The invoke method is also very simple. Its input parameters are fn, self, locals, serviceName, that is, the function to be executed, the context of the function execution, the options provided, and the service name. First, obtain all the dependency names of the function. After the annotate method is used, if the options provides the dependency on the name, use it. Otherwise, use the get method to obtain the dependency and pass in the function, return the execution result of the function. The result returned by invoke is often a service object.
The instantiate method mainly creates an example based on the provided constructor for dependency or service provision. It is worth mentioning that the new keyword is not used to create an Object, but the Object. create provided by ECMA5 is used to inherit the prototype Object Implementation of the function, which is very clever.
The has method successively checks whether serviceProvider and service exist in the cache.
So far, the $ injector object has been created.
Register a service (dependency)
Services cannot come out of thin air. We need to implement or introduce services or dependencies externally. Therefore, the registration service module is also worth further consideration. AngularJS provides a variety of Registration service APIs, But we focus on the provider method. Other factory and service methods are built based on this.
These methods (provider, factory, etc.) are bound to providerCache. provide object, and we use angular. module ('app', []). provider (...) the provider function called by the method will be called during module loading (this call is abstracted into an array, that is, [provider, method, arguments]) bound to the inner invokeQueue array, finally in providerCache. provide object, and we use angular. module ('app', []). provider (...) the provider function called by the method will be called during module loading (this call is abstracted into an array, that is, [provider, method, arguments]) bound to the inner invokeQueue array, finally in providerCache. the provider method is called on the provide object, and other control Ler, directive and other methods are similar, but they are bound to the providerCache. controllerProvider, providerCache. controllerProvider, and providerCache. compileProvider objects.
Function provider (name, provider _) {assertNotHasOwnProperty (name, 'service'); if (isFunction (provider _) | isArray (provider _) {provider _ = providerInjector. instantiate (provider _);} if (! Provider _. $ get) {throw $ injectorMinErr ('pget', "Provider '{0} 'must define $ get factory method. ", name);} return providerCache [name + providerSuffix] = provider _;} function enforceReturnValue (name, factory) {return function enforcedReturnValue () {var result = instanceInjector. invoke (factory, this); if (isUndefined (result) {throw $ injectorMinErr ('undef ', "Provider' {0} 'must return a value fr Om $ get factory method. ", name);} return result ;};} function factory (name, factoryFn, enforce) {return provider (name, {$ get: enforce! = False? EnforceReturnValue (name, factoryFn): factoryFn});} function service (name, constructor) {return factory (name, ['$ injector', function ($ injector) {return $ injector. instantiate (constructor) ;}]);} function value (name, val) {return factory (name, valueFn (val), false);} function constant (name, value) {assertNotHasOwnProperty (name, 'constant'); providerCache [name] = value; instanceCache [name] = v Alue;} // call (intercept) after the service is instantiated. Intercept the function to inject the instantiated service, which can be modified and extended to replace the service. 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 });};}
The provider method requires two parameters: Service name (dependency name), factory method, or an array containing dependency and factory method. Create an instance of the factory method using providerInjector and add it to providerCache.
The factory method encapsulates the second parameter as an object containing the $ get method, namely serviceProvider, cache. Not complex.
The service method is nested with the $ injector service, namely, instanceInjector. It creates an constructor instance and serves as a service object.
The value Method encapsulates only one provider, and its $ get method returns the value.
The constant method saves the value to providerCache and instanceCache respectively, and does not require invoke to obtain the value.
The special and highly scalable decorator method is to add an intercept function after the get method of serviceProvider and add an intercept function after passing the dependency get method, the service object returned by the original invoke $ get method is obtained by passing the dependency delegate. We can use decorator to expand or delete services.
Process
Finally, based on the basic implementation, we will go through the specific injection process to make it easier for us to understand it in depth.
angular.module("app",[]) .provider("locationService",function(){ ... }) .controller("WeatherController",function($scope,locationService,$location){ locationService.getWeather() .then(function(data){ $scope.weather = data; },function(e){ console.log("error message: "+ e.message) }); })
We don't care about the specific code implementation. We just use the above Code as a demonstration.
First, determine the range of AngularJS context and obtain the dependency module (null here );
Continue to register the Service (dependency) and cache serviceProvider to providerCache;
Declare the Controller;
The injector example is obtained here. By executing the invoke function, the ["injector example" is obtained. By executing the invoke function, ["scope", "locationService", "location"] dependency list is obtained, use location "] dependency list to obtain the corresponding dependent object through the get method of injector. For scope, scope, and location services, AngularJS has been injected into Angular during initialization. Therefore, you can obtain the corresponding provider object and execute relevant methods to return scope, scope, and location objects, the locationService is declared in the provider. Therefore, the locationServiceProvider object is obtained and instanceInjector is called. invoke (locationServiceProvider. $ get, locationServiceProvider, undefined, "locationService") returns the locationService object.
Finally, all dependencies are assembled into arrays [scope, locationService, scope, locationService, location] and passed to the anonymous function for execution as parameters.