SoFunction
Updated on 2025-04-13

angularjs source code analysis injector

Introduction

Injector is used to perform automatic parameter injection, for example

function fn ($http, $scope, aService) {
}

When ng runs, $http, $scope, aService will be automatically passed in as parameters for execution.

It's actually easy to figure it out, injector did two things

  1. Cache those services and inject them as parameters later
  2. Analyze the parameter list and find the required parameter injection

The following source code analyzes how to implement the above two things.

structure

createInjector -> createInternalInjector  return: instanceInjector

So createInjector() returns instanceInjector, the structure is as follows:

{
 invoke: invoke,
 instantiate: instantiate,
 get: getService,
 annotate: annotate,
 has: function(name) {
  return (name + providerSuffix) || (name);
 }
}

Source code analysis

1. createInjector

function createInjector(modulesToLoad, strictDi) {
 strictDi = (strictDi === true);
 var INSTANTIATING = {},
   providerSuffix = 'Provider',
   path = [],
   loadedModules = new HashMap([], true),
   // Pre-configure $provide, for call to register service in loadModules, etc.   providerCache = {
    $provide: {
      provider: supportObject(provider),
      factory: supportObject(factory),
      service: supportObject(service),
      value: supportObject(value),
      constant: supportObject(constant),
      decorator: decorator
     }
   },

   // providerInjector, instanceInjector Two injectors   // instanceInjector provides service and other injection to the outside, providerInjector provides provider to obtain internally   providerInjector = (providerCache.$injector =
     createInternalInjector(providerCache, function() {
      throw $injectorMinErr('unpr', "Unknown provider: {0}", (' <- '));
     }, strictDi)),
   instanceCache = {},
   instanceInjector = (instanceCache.$injector =
     createInternalInjector(instanceCache, function(servicename) {
      var provider = (servicename + providerSuffix);
      return (provider.$get, provider, undefined, servicename);
     }, strictDi));

 // Load the module forEach(loadModules(modulesToLoad), function(fn) { (fn || noop); });

 return instanceInjector;
}

2. $provide

$provide: {
  provider: supportObject(provider),
  factory: supportObject(factory),
  service: supportObject(service),
  value: supportObject(value),
  constant: supportObject(constant),
  decorator: decorator
}

2.1 supportObject

For packaging methods, the method before packaging accepts two parameters (key, value). The packaged method can support the object parameters passing in, that is, multiple keys -> values.

function supportObject(delegate) {
 return function(key, value) {
  if (isObject(key)) {
   forEach(key, reverseParams(delegate));
  } else {
   return delegate(key, value);
  }
 };
}

2.2 provider

Review how provider, service and factory are used

('serviceName', function(){
 return {
  getName: function(){},
  setName: function(){}
 }
});

('serviceName', function(){
  = function() {}

  = function() {}
});

('serviceName', function($httpProvider){
 // Inject $httpProvider this.$get = function() {
  return {
   getName: function(){},
   setName: function(){}
  };
 }
});

('serviceName', {
  $get: function () {}
});
function provider(name, provider_) {
 assertNotHasOwnProperty(name, 'service');
 // When provider_ is fn or array, other providers can be injected into parameters // Because (provider_) can be passed in other dependent providers when (provider_) // This is also the difference between provider and service and factory methods 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 factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }

function service(name, constructor) {
 return factory(name, ['$injector', function($injector) {
  return $(constructor);
 }]);
}

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

Finally summarized to the implementation of the provider, cache the provider to the providerCache for call

What is different from the others is the implementation of constant, which is saved in providerCache and instanceCache respectively, so that both the provider and the service can be injected.

function constant(name, value) {
 assertNotHasOwnProperty(name, 'constant');
 providerCache[name] = value;
 instanceCache[name] = value;
}

2.3 Review loadModules

function runInvokeQueue(queue) {
 var i, ii;
 for(i = 0, ii = ; i < ii; i++) {
  var invokeArgs = queue[i],
    provider = (invokeArgs[0]);
  // The format of the queue that is stored in [$provide, factory, arguments]  // After replacement, $($provide, arguments);  // It is to call $provid's factory, service, etc.  provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
 }
}

2.4 decorator

Example:

(function($provide) {
 $('Mail', function($delegate) {
  $ = function(cc) {
   (cc);
  };
  return $delegate;
 });
})

Using the example, it can be seen that the passed parameter $delegate is the original service instance, and you need to add a method to this instance, which is the so-called decorator

Source code:

function decorator(serviceName, decorFn) {
 var origProvider = (serviceName + providerSuffix),
   orig$get = origProvider.$get;

 origProvider.$get = function() {
  // Generate the required service instance through the provider obtained above, and inject it into the parameter list with $delegate  var origInstance = (orig$get, origProvider);
  return (decorFn, null, {$delegate: origInstance});
 };
}

3. createInternalInjector

3.1 Overall structure

// Get it from cache, if not, call factory to create it. See getService analysis for details

function createInternalInjector(cache, factory) {

 function getService(serviceName) {
 }

 function invoke(fn, self, locals, serviceName){
 }

 function instantiate(Type, locals, serviceName) {
 }

 return {
  // Execute fn, with parameter injection function  invoke: invoke,
  // Instantiate fn, parameter injection can be used  instantiate: instantiate,
  // Get the provider or service  get: getService,
  // Get the parameter list of the method for injection  annotate: annotate,
  // Confirm whether it contains a provider or service  has: function(name) {
   return (name + providerSuffix) || (name);
  }
 };
}

3.2 annotate

Get the parameter list of fn

// type1
function fn (a, b, c) -> ['a', 'b', 'c']

// type2
['a', 'b', fn] -> ['a', 'b']

// type3
function fn () {}
fn.$inject = ['a', 'c']
-> ['a', 'c']

Source code:

function annotate(fn, strictDi, name) {
 var $inject,
   fnText,
   argDecl,
   last;

 if (typeof fn === 'function') {
  if (!($inject = fn.$inject)) {
   $inject = [];
   if () {
    // In strict mode, or throw error    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);
    }
    // Remove comments    fnText = ().replace(STRIP_COMMENTS, '');
    // Select all parameters fn(a,b,c,d) -> 'a,b,c,d'    argDecl = (FN_ARGS);
    // Divide into array    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;
}

3.3 getService

// When there is no service in cache, enter else, cache[serviceName] = INSTANTIATING to make a mark// Because the next call to factory(serviceName) is actually a recursive call// function(servicename) {
//  var provider = (servicename + providerSuffix);
//  return (provider.$get, provider, undefined, servicename);
// }
// (provider.$get will get the parameters that need to be injected and then inject it// Therefore, after marking it, you can determine whether there is a circular dependencyfunction getService(serviceName) {
 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);
  } catch (err) {
   if (cache[serviceName] === INSTANTIATING) {
    delete cache[serviceName];
   }
   throw err;
  } finally {
   ();
  }
 }
}

3.4 invoke

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

 var args = [],
   // Get the parameter 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);
  }
  // locals are preferred  (
   locals && (key)
   ? locals[key]
   : getService(key)
  );
 }
 if (isArray(fn)) {
  fn = fn[length];
 }

 return (self, args);
}

3.5 instantiate

function instantiate(Type, locals, serviceName) {
 var Constructor = function() {},
   instance, returnedValue;
 
 // When type is array, get the last parameter such as: ['$window', function($win){}]  = (isArray(Type) ? Type[ - 1] : Type).prototype;
 instance = new Constructor();
 // Call invoke to execute Type method returnedValue = invoke(Type, instance, locals, serviceName);

 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}

The function of instantiate is to instantiate the Type, and parameters can be automatically passed into the constructor during the instantiation process.