Angular. JS learning dependency injection $ injector details, angular. jsinjector
Preface
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 useFunction.prototype.call
Passing Parameters
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) {v Ar $ 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 ;}
annotate
The function performs targeted analysis on input parameters. If a function is passed, the dependent module is passed as the input parameter. In this case, you can perform regular matching using the serialization function to obtain the name of the dependent module and store it in$inject
Returned in the array. In addition, an exception is thrown when the dependency is passed through the function input parameter in strict mode. In the second mode, the dependency is transmitted through the array, the last element of the array is the function that requires dependency.annotate
The function returns the parsed dependency name.
Create a Injector
AngularJS APIS also provide injector, which can be used through injector.get
,has
,instantiate
,invoke
Andannotate
Through the source code, you can have a clearer understanding.
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) {}]); // Obje Ct 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 );}};}
createInternalInjector
Method To create 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 provider object obtained from providerInjector$get
Method, produce service objects (dependencies), and pass the service objects to the corresponding functions 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 executefactory(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, and its input parameters are asked separatelyfn
,self
,locals
,serviceName
That is, the function to be executed, the context of the function execution, the options Option and service name provided. 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 object is not created through the new Keyword, but is provided through ECMA5Object.create
To inherit the implementation of the function's prototype object, 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 boundproviderCache.provide
Object, and we useangular.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, and finally inproviderCache.provide
Object, and we useangular.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, and finally inproviderCache.provide
The provider method is called on the object. Other methods such as controller and directive are similar, but they are boundproviderCache.controllerProvider,providerCache.controllerProvider,providerCache.compileProvider
Object.
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 and injected.$injector
A service, namely, instanceInjector, creates an instance of the constructor as a service object.
The value Method encapsulates only one provider.$get
Return 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, and pass the dependency delegate to get the originalinvoke $get
Service object returned by the method. 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 by callinginstanceInjector.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.
Summary
So far, dependency injection has been completed. Do you know more about dependency injection $ injector? The above is all about this article. I hope this article will help you in your study or work. If you have any questions, please leave a message.