SoFunction
Updated on 2025-04-12

Detailed analysis of $injector for learning

Preface

Before dependency injection (IoC), it is very simple and direct to create an object in the program, that is, new Object is enough in the code, and we are responsible for creating, maintaining, modifying and deleting it ourselves. That is to say, we control the entire life cycle of the object until the object is not referenced and recycled. Admittedly, this approach is understandable when the number of objects created or maintained is small, but when a large project needs to create an order of magnitude objects, it is difficult to rely solely on programmers to maintain all objects, 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 degree of dependencies, and requires no reference or parameter transfer in the code to operate dependencies.

In js, we can introduce dependencies like this

1. Use global variable references

2. Pass through function parameters where needed

Needless to say, the disadvantages of using global variables pollute the global namespace, and passing references through letter parameters can also be achieved in two ways:

1. Closure delivery

2. The dependency object is parsed in the background and passedTransfer the parameters

In AngularJS, dependency injection is implemented through the latter, and the following sections will introduce the specific implementation of the IoC module.

Get dependencies

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
 // Get the service namevar 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 = ().replace(STRIP_COMMENTS, ''),
  args = (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 () {
  if (strictDi) {
   if (!isString(name) || !name) {
   name =  || anonFn(fn);
   }
   throw $injectorMinErr('strictdi',
   '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
  }
  fnText = ().replace(STRIP_COMMENTS, '');
  argDecl = (FN_ARGS);
  forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
   (FN_ARG, function(all, underscore, name) {
   $(name);
   });
  });
  }
  fn.$inject = $inject;
 }
 } else if (isArray(fn)) {
 last =  - 1;
 assertArgFn(fn[last], 'fn');
 $inject = (0, last);
 } else {
 assertArgFn(fn, 'fn', true);
 }
 return $inject;
}

annotateThe function performs targeted analysis of the incoming parameters. If a function is passed, the dependent module is passed as the incoming parameters. At this time, regular matching can be performed through the serialization function, the name of the dependent module can be obtained and stored in$injectReturns in the array. In addition, executing dependencies through the function parameter transfer method will throw exceptions in strict mode; the second type of dependency transfer is through the array method, and the last element of the array is a function that needs to be used.annotateThe function finally returns the resolved dependency name.

Injector creation

AngularJS API also provides the injector part, which can be used through the injector part.get,has,instantiate,invokeAnd the previous section mentionedannotateSuch methods can be understood more clearly through source code.

function createInternalInjector(cache, factory) {
 // For the service injector providerInjector, only obtaining the service based on the service name, the factory will throw an exception function getService(serviceName, caller) {
  if ((serviceName)) {
  if (cache[serviceName] === INSTANTIATING) {
   throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
     serviceName + ' <- ' + (' <- '));
  }
  return cache[serviceName];
  } else {
  try {
   (serviceName);
   cache[serviceName] = INSTANTIATING;
   return cache[serviceName] = factory(serviceName, caller);
  } catch (err) {
   if (cache[serviceName] === INSTANTIATING) {
   delete cache[serviceName];
   }
   throw err;
  } finally {
   ();
  }
  }
 }

 function invoke(fn, self, locals, serviceName) {
  if (typeof locals === 'string') {
  serviceName = locals;
  locals = null;
  }

  var args = [],
   // parse and get the injection service list   $inject = annotate(fn, strictDi, serviceName),
   length, i,
   key;

  for (i = 0, length = $; i < length; i++) {
  key = $inject[i];
  if (typeof key !== 'string') {
   throw $injectorMinErr('itkn',
     'Incorrect injection token! Expected service name as string, got {0}', key);
  }
  // The injected service is passed in as a parameter  (
   locals && (key)
   ? locals[key]
   : getService(key, serviceName)
  );
  }
  if (isArray(fn)) {
  fn = fn[length];
  }

  // /angularjs-invoke-apply-vs-switch
  // #5388
  return (self, args);
 }

 function instantiate(Type, locals, serviceName) {
  // Check if Type is annotated and use just the given function at n-1 as parameter
  // . ('greeter', ['$window', function(renamed$window) {}]);
  // Object creation: /create-constructor/2
  var instance = ((isArray(Type) ? Type[ - 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 (name + providerSuffix) || (name);
  }
 };
 }

createInternalInjectorThe method creates the $injector object, and the parameters passed are cache objects and factory functions respectively. In the specific implementation, AngularJS creates two injector objects - providerInjector and instanceInjector (the difference between these two objects is mainly because the cache objects passed by the createInternalInjector method is different), and the instanceInjector exported through () is the instanceInjector. For providerInjector, it is mainly used to obtain the service provider, namely the serviceProvider. For instanceInjector, it is mainly used to execute the provider object obtained from providerInjector$getMethod, produce service objects (dependencies), and pass service objects to the corresponding function to complete IoC.

First, let’s start with the get method. The get method mainly obtains the service with the specified name. The instanceInjector is obtained through the angular injector method. When there is no service object (dependency) in the cache, we need to execute it.factory(serviceName, caller)Method, let's look at the factory function for:

instanceInjector = (instanceCache.$injector =
   createInternalInjector(instanceCache, function
(serviceName, caller) { var provider = (serviceName + providerSuffix, caller); return
 (provider.$get, provider, undefined, serviceName);
   }));

The red part is the factory function, which shows that the provider serviceProvider that obtains the corresponding service through the providerInjector, then calls the instanceInjector's invoke method to execute the serviceProvider $get method in the serviceProvider context, returns the service object and saves it in the cache. In this way, the acquisition and caching of the service object (dependency) is completed.

The invoke method is also very simple. Its entry parameters are asked separatelyfn, self, locals, serviceName, that is, the function to be executed, the function execution context, the options options and service name provided. First, all dependency names of the function are obtained. After completing it through the annotate method, if the dependency on the name is provided in options, it is used. Otherwise, the dependency is obtained through the get method, and finally the function is passed in, and the execution result of the function is returned. The result returned by invoke is often a service object.

The instantiate method mainly creates an example based on the provided constructor, used as a dependency or service. It is worth mentioning that the object is not created through the new keyword, but is provided by ECMA5.It is very clever to inherit the prototype object implementation of the function.

The has method is to successively determine whether the serviceProvider and service exist in the cache.

At this point, the $injector object is created.

Register service (dependence)

Services cannot come out of thin air. We need to implement them ourselves or introduce services or dependencies externally. Therefore, the module of the registration service is also worth exploring. AngularJS provides a variety of APIs for registration services, but we focus on the provider method, and other factory and service methods are built based on this.

These methods (provider, factory, etc.) are bound toOn the object, and we pass(′app′,[]).provider(...)The provider function called in the method will bind the call (the call abstracted into an array, that is, [provider, method, arguments]) to the internal invokeQueue array, and finallyOn the object, and we pass(′app′,[]).provider(...)The provider function called in the method will bind the call (the call abstracted into an array, that is, [provider, method, arguments]) to the internal invokeQueue array, and finallyCalling the provider method on the object, other controllers, directives and other methods are similar, but they are bound to,,on the object.

function provider(name, provider_) {
 assertNotHasOwnProperty(name, 'service');
 if (isFunction(provider_) || isArray(provider_)) {
  provider_ = (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 = (factory, this);
  if (isUndefined(result)) {
  throw $injectorMinErr('undef', "Provider '{0}' must return a value from $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 $(constructor);
 }]);
 }

 function value(name, val) { return factory(name, valueFn(val), false); }

 function constant(name, value) {
 assertNotHasOwnProperty(name, 'constant');
 providerCache[name] = value;
 instanceCache[name] = value;
 }
 // Call (intercept) after the service is instantiated, inject the instantiated service into the intercept function, and can modify and extend the replacement service. function decorator(serviceName, decorFn) {
 var origProvider = (serviceName + providerSuffix),
  orig$get = origProvider.$get;

 origProvider.$get = function() {
  var origInstance = (orig$get, origProvider);
  return (decorFn, null, {$delegate: origInstance});
 };
 }

The provider method requires two parameters, one is the service name (dependency name), and the other is the factory method or an array containing dependencies and factory methods. First, create an instance of the factory method through the providerInjector and add it to the providerCache, return.

The factory method simply encapsulates the second parameter into an object containing the $get method, namely the serviceProvider, cached. Not complicated.

The service method is nested and injected$injectorA service, namely instanceInjector, creates an instance of the constructor as a service object.

The value method only encapsulates a provider,$getThe method returns a value value.

The constant method stores the value of the value into providerCache and instanceCache respectively, and does not require invoke to obtain its value.

The more special and highly extensible decorator method is to add an intercept function after the serviceProvider's get method, and add an intercept function after the transitive dependency get method, and obtain the original by passing the transitive dependency delegate.invoke $getThe service object returned by the method. We can use decorator to extend, delete and other operations on the service.

process

Finally, based on the basic implementation, we go through the specific injection process, which makes it easier for us to understand in-depth.

("app",[])
  .provider("locationService",function(){
  ...
 })
 .controller("WeatherController",function($scope,locationService,$location){
  ()
   .then(function(data){
    $ = data;
   },function(e){
    ("error message: "+ )
   });
 })

We don't care about the specific code implementation, we just use the above code as a demonstration.

First determine the scope of the AngularJS context and get the dependent module (empty here);

Continue to register the service (dependency), cache the serviceProvider into the providerCache;

Declare the controller;

Here we get the injector example, by executing the invoke function, obtain the ["injector example, by executing the invoke function, obtain the ["scope", "locationService", "location"] dependency list, and obtain the corresponding dependency object through the injector get method. For scope and scope and location services, it has been injected into Angular during AngularJS initialization, so the corresponding provider object can be obtained, and the relevant methods can be executed to return scope and scope and location objects. The locationService is declared in the provider, so the locationServiceProvider object is obtained and the call(locationServiceProvider.$get, locationServiceProvider, undefined, “locationService”)Returns the locationService object.

Finally, all dependencies are assembled into an array [scope, locationService, scope, locationService, location] as parameters and passed to the anonymous function for execution.

Summarize

At this point, dependency injection is completed. Do you have any further understanding of $injector in dependency injection? The above is the entire content of this article. I hope the content of this article will be of some help to your study or work. If you have any questions, you can leave a message to communicate.