Angular的按需加载(2)

136 阅读2分钟
原文链接: alili.tech

上一篇我们实现了最简单的Angular的按需加载,

可以通过替换Angular的内部方法,再使用require.js配合$q完成了按需加载.

今天来说说Angular如何按需加载第三方module;

在此之前,我们得先弄清楚Angular是如何启动的.

setupModuleLoader方法

在上图中,我们得注意一个非常主要的方法;

setupModuleLoader方法,顾名思义 —— 模块加载器;

此方法最后返回的对象为:


                                    
var moduleInstance = {
    _invokeQueue: invokeQueue,
    _runBlocks: runBlocks,
    requires: requires,
    name: name,
    provider: invokeLater('$provide', 'provider'),
    factory: invokeLater('$provide', 'factory'),
    service: invokeLater('$provide', 'service'),
    value: invokeLater('$provide', 'value'),
    constant: invokeLater('$provide', 'constant', 'unshift'),
    animation: invokeLater('$animateProvider', 'register'),
    filter: invokeLater('$filterProvider', 'register'),
    controller: invokeLater('$controllerProvider', 'register'),
    directive: invokeLater('$compileProvider', 'directive'),
    config: config,
    run: function(block) {
        runBlocks.push(block);
        return this;
    }
}


                                

看着这对象的格式,有没有特别熟悉?

当我们运行


                                    
angular.module('app',[]);

//或者

angular.module('app')


                                

我们新建一个module或者获取一个module的时候,
返回的就是这个对象.

当我们做链式操作的时候,
angular.module(‘app’,[]).config().run().controller();

每一步操作,返回的都是它,所以我们才可以做链式操作;

当angular初始化完成之后,开始在dom里寻找ng-app属性;

当找到ng-app后,拿到ng-app的value后才开始启动 angular;
内部运行bootstrap方法;

如果要选择手动启动的话也可以这样:


                                    
angular.bootstrap(document, ['moduleName']);


                                

当bootstrap的时候angular在做什么呢?

doBootstrap方法

在源码里中的bootstrap方法里有一个doBootstrap方法:


                                    

var doBootstrap = function() {
  element = jqLite(element);
  //判断是否启动
  if (element.injector()) {
    var tag = (element[0] === document) ? 'document' : startingTag(element);
    throw ngMinErr(
        'btstrpd',
        "App Already Bootstrapped with this Element '{0}'",
        tag.replace(/</,'<').replace(/>/,'>'));
  }

  //angular.bootstrap(document, ['moduleName']);
  //这里的modules就是之前我们bootstrap时候传进来的 moduleName
  //modules ==> ['moduleName']
  modules = modules || [];
  modules.unshift(['$provide', function($provide) {
    $provide.value('$rootElement', element);
  }]);

  .....

  //重点来了 createInjector
  var injector = createInjector(modules, config.strictDi);

  injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
     function bootstrapApply(scope, element, compile, injector) {
      scope.$apply(function() {
        element.data('$injector', injector);
        compile(element)(scope);
      });
    }]
  );
  return injector;
};


                                

createInjector方法

我们看这个方法主要做了什么


                                    
function createInjector(modulesToLoad, strictDi) {
  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap([], true),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) {
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},
      protoInstanceInjector =
          createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(
                provider.$get, provider, undefined, serviceName);
          }),
      instanceInjector = protoInstanceInjector;

  providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) };

  //重点!!! 这里的modulesToLoad 就是我们之前传进来的参数
  //modulesToLoad ==> ['moduleName']
  var runBlocks = loadModules(modulesToLoad);
  instanceInjector = protoInstanceInjector.get('$injector');
  instanceInjector.strictDi = strictDi;

  forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });

  return instanceInjector;
...
//声明的一些函数
}


                                

loadModules方法


                                    
function loadModules(modulesToLoad) {
//判断modulesToLoad 传进来的是不是一个数组
//assertArg函数是报错用的
  assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');

  var runBlocks = [], moduleFn;
  //开始遍历
  forEach(modulesToLoad, function(module) {
    if (loadedModules.get(module)) return;
    loadedModules.put(module, true);

   //声明一个调用函数,这个为以后按需加载很重要的函数
    function runInvokeQueue(queue) {
      var i, ii;
      for (i = 0, ii = queue.length; i < ii; i++) {
        var invokeArgs = queue[i],
            provider = providerInjector.get(invokeArgs[0]);

        provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
      }
    }

    try {
    //现在我们拿到了我们的moduleName
      if (isString(module)) {
      //angularModule其实就是之前的setupModuleLoader方法,
      //那当然他返回的就是moduleInstance对象,也就是说的那个
        moduleFn = angularModule(module);

        //把模块里的runBlocks都取出来
        runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
        //然后开始调用上面的方法
        //开始通过runInvokeQueue方法遍历执行 之前setupModuleLoader实例化的方法
        runInvokeQueue(moduleFn._invokeQueue);
        runInvokeQueue(moduleFn._configBlocks);
      } else if (isFunction(module)) {
          runBlocks.push(providerInjector.invoke(module));
      } else if (isArray(module)) {
          runBlocks.push(providerInjector.invoke(module));
      } else {
        assertArgFn(module, 'module');
      }
    } catch (e) {
          ....
          //一些对于异常的处理

    }
  });
  return runBlocks;
}


                                

以上就是angular加载模块的主要方法