栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > JavaScript

探索angularjs+requirejs全面实现按需加载的套路

JavaScript 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

探索angularjs+requirejs全面实现按需加载的套路

在进行有一定规模的项目时,通常希望实现以下目标:1、支持复杂的页面逻辑(根据业务规则动态展现内容,例如:权限,数据状态等);2、坚持前后端分离的基本原则(不分离的时候,可以在后端用模版引擎直接生成好页面);3、页面加载时间短(业务逻辑复杂就需要引用第三方的库,但很可能加载的库和用户本次操作没关系);4,还要代码好维护(加入新的逻辑时,影响的文件尽量少)。

想同时实现这些目标,就必须有一套按需加载的机制,页面上展现的内容和所有需要依赖的文件,都可以根据业务逻辑需要按需加载。最近都是基于angularjs做开发,所以本文主要围绕angularjs提供的各种机制,探索全面实现按需加载的套路。

一、一步一步实现
基本思路:1、先开发一个框架页面,它可以完成一些基本的业务逻辑,并且支持扩展的机制;2、业务逻辑变复杂,需要把部分逻辑拆分到子页面中,子页面按需加载;3、子页面中的展现内容也变了复杂,又需要进行拆分,按需加载;4、子页面的内容复杂到依赖外部模块,需要按需加载angular模块。

1、框架页
提到前端的按需加载,就会想到AMD( Asynchronous Module Definition),现在用requirejs的非常多,所以首先考虑引入requires。

index.html


注意:采用手动启动angular的方式,因此html中没有ng-app。

spa-loader.js

require.config({
  paths: {
    "domReady": '/static/js/domReady',
    "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min",
    "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min",
  },
  shim: {
    "angular": {
      exports: "angular"
    },
    "angular-route": {
      deps: ["angular"]
    },
  },
  deps: ['/test/lazyspa/spa.js'],
  urlArgs: "bust=" + (new Date()).getTime()
});

spa.js

define(["require", "angular", "angular-route"], function(require, angular) {
  var app = angular.module('app', ['ngRoute']);
  require(['domReady!'], function(document) {
    angular.bootstrap(document, ["app"]); 
    window.loading.finish();
  });
});

2、按需加载子页面
angular的routeProvider+ng-view已经提供完整的子页面加载的方法,直接用。
注意必须设置html5Mode,否则url变化以后,routeProvider不截获。

index.html


  page1
  page2
  main


spa.js

app.config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {
  
  $locationProvider.html5Mode(true);
  
  $routeProvider.when('/test/lazyspa/page1', {
    template: 'page1',
  }).when('/test/lazyspa/page2', {
    template: 'page2',
  }).otherwise({
    template: 'main',
  });
}]);

3、按需加载子页面中的内容
用routeProvider的前提是url要发生变化,但是有的时候只是子页面中的局部要发生变化。如果这些变化主要是和绑定的数据相关,不影响页面布局,或者影响很小,那么通过ng-if一类的标签基本就解决了。但是有的时候要根据页面状态,完全改变局部的内容,例如:用户登录前和登录后局部要发生的变化等,这就意味着局部的布局可能也挺复杂,需要作为独立的单元来对待。

利用ng-include可以解决页面局部内容加载的问题。但是,我们可以再考虑更复杂一些的情况。这个页面片段对应的代码是后端动态生成的,而且不仅仅有html还有js,js中定义了代码片段对应的controller。这种情况下,不仅仅要考虑动态加载html的问题,还要考虑动态定义controller的问题。controller是通过angular的controllerProvider的register方法注册,因此需要获得controllerProvider的实例。

spa.js

app.config(['$locationProvider', '$routeProvider', '$controllerProvider', function($locationProvider, $routeProvider, $controllerProvider) {
  app.providers = {
    $controllerProvider: $controllerProvider //注意这里!!!
  };
  
  $locationProvider.html5Mode(true);
  
  $routeProvider.when('/test/lazyspa/page1', {
    
    template: 'page1',
    controller: 'ctrlPage1'
  }).when('/test/lazyspa/page2', {
    template: 'page2',
  }).otherwise({
    template: 'main',
  });
  app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) {
    
    
    app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) {
      $scope.openalert = function() {
 alert('page1 alert');
      };
    }]);
    
    $templateCache.put('page1.html', '');
  }]);
}]);

4、动态加载模块
采用上面子页面片段的加载方式存在一个局限,就是各种逻辑(js)要加入到启动模块中,这样还是限制子页面片段的独立封装。特别是,如果子页面片段需要使用第三方模块,且这个模块在启动模块中没有事先加载时,就没有办法了。所以,必须要能够实现模块的动态加载。实现模块的动态加载就是把angular启动过程中加载模块的方式提取出来,再处理一些特殊情况。

但是,实际跑起来发现文章中的代码有问题,就是“$injector”到底是什么?研究了angular的源代码injector.js才大概搞明白是怎么回事。

一个应用有两个$injector,providerInjector和instanceInjector。invokeQueue和用providerInjector,runBlocks用instanceProvider。如果$injector用错了,就会找到需要的服务。

routeProvider中动态加载模块文件。

template: 'page2',
resolve: {
  load: ['$q', function($q) {
    var defer = $q.defer();
    
    require(['/test/lazyspa/module1.js'], function(loader) {
      loader.onload && loader.onload(function() {
 defer.resolve();
      });
    });
    return defer.promise;
  }]
}

动态加载angular模块

angular._lazyLoadModule = function(moduleName) {
  var m = angular.module(moduleName);
  console.log('register module:' + moduleName);
  
  var $injector = angular.element(document).injector();
  
  angular.forEach(m.requires, function(r) {
    angular._lazyLoadModule(r);
  });
  
  angular.forEach(m._invokeQueue, function(invokeArgs) {
    try {
      var provider = providers.$injector.get(invokeArgs[0]);
      provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
    } catch (e) {
      console.error('load module invokeQueue failed:' + e.message, invokeArgs);
    }
  });
  
  angular.forEach(m._configBlocks, function(invokeArgs) {
    try {
      providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]);
    } catch (e) {
      console.error('load module configBlocks failed:' + e.message, invokeArgs);
    }
  });
  
  angular.forEach(m._runBlocks, function(fn) {
    $injector.invoke(fn);
  });
};

定义模块
module1.js

define(["angular"], function(angular) {
  var onloads = [];
  var loadCss = function(url) {
    var link, head;
    link = document.createElement('link');
    link.href = url;
    link.rel = 'stylesheet';
    head = document.querySelector('head');
    head.appendChild(link);
  };
  loadCss('//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css');
  
  require.config({
    paths: {
      'ui-bootstrap-tpls': '//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min'
    },
    shim: {
      "ui-bootstrap-tpls": {
 deps: ['angular']
      }
    }
  });
  
  require(['ui-bootstrap-tpls'], function() {
    var m1 = angular.module('module1', ['ui.bootstrap']);
    m1.config(['$controllerProvider', function($controllerProvider) {
      console.log('module1 - config begin');
    }]);
    m1.controller('ctrlModule1', ['$scope', '$uibModal', function($scope, $uibModal) {
      console.log('module1 - ctrl begin');
      
      var dlg = '';
      dlg += '

二、完整的代码
index.html



  
    
    
    
    SPA
  
  
    
      
 page1
 page2
 main
      
      
    
    
    
  

spa-loader.js

window.loading = {
  finish: function() {
    
  },
  load: function() {
    require.config({
      paths: {
 "domReady": '/static/js/domReady',
 "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min",
 "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min",
      },
      shim: {
 "angular": {
   exports: "angular"
 },
 "angular-route": {
   deps: ["angular"]
 },
      },
      deps: ['/test/lazyspa/spa.js'],
      urlArgs: "bust=" + (new Date()).getTime()
    });
  }
};
window.loading.load();

spa.js

'use strict';
define(["require", "angular", "angular-route"], function(require, angular) {
  var app = angular.module('app', ['ngRoute']);
  
  angular._lazyLoadModule = function(moduleName) {
    var m = angular.module(moduleName);
    console.log('register module:' + moduleName);
    
    var $injector = angular.element(document).injector();
    
    angular.forEach(m.requires, function(r) {
      angular._lazyLoadModule(r);
    });
    
    angular.forEach(m._invokeQueue, function(invokeArgs) {
      try {
 var provider = providers.$injector.get(invokeArgs[0]);
 provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
      } catch (e) {
 console.error('load module invokeQueue failed:' + e.message, invokeArgs);
      }
    });
    
    angular.forEach(m._configBlocks, function(invokeArgs) {
      try {
 providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]);
      } catch (e) {
 console.error('load module configBlocks failed:' + e.message, invokeArgs);
      }
    });
    
    angular.forEach(m._runBlocks, function(fn) {
      $injector.invoke(fn);
    });
  };
  app.config(['$injector', '$locationProvider', '$routeProvider', '$controllerProvider', function($injector, $locationProvider, $routeProvider, $controllerProvider) {
    
    app.providers = {
      $injector: $injector,
      $controllerProvider: $controllerProvider
    };
    
    $locationProvider.html5Mode(true);
    
    $routeProvider.when('/test/lazyspa/page1', {
      template: 'page1',
      controller: 'ctrlPage1'
    }).when('/test/lazyspa/page2', {
      template: 'page2',
      resolve: {
 load: ['$q', function($q) {
   var defer = $q.defer();
   
   require(['/test/lazyspa/module1.js'], function(loader) {
     loader.onload && loader.onload(function() {
defer.resolve();
     });
   });
   return defer.promise;
 }]
      }
    }).otherwise({
      template: 'main',
    });
  }]);
  app.controller('ctrlMain', ['$scope', '$location', function($scope, $location) {
    console.log('main controller');
    
    $location.url('/test/lazyspa/page1');
  }]);
  app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) {
    
    
    app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) {
      $scope.openalert = function() {
 alert('page1 alert');
      };
    }]);
    
    $templateCache.put('page1.html', '');
  }]);
  require(['domReady!'], function(document) {
    angular.bootstrap(document, ["app"]);
  });
});

module1.js

'use strict';
define(["angular"], function(angular) {
  var onloads = [];
  var loadCss = function(url) {
    var link, head;
    link = document.createElement('link');
    link.href = url;
    link.rel = 'stylesheet';
    head = document.querySelector('head');
    head.appendChild(link);
  };
  loadCss('//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css');
  require.config({
    paths: {
      'ui-bootstrap-tpls': '//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min'
    },
    shim: {
      "ui-bootstrap-tpls": {
 deps: ['angular']
      }
    }
  });
  require(['ui-bootstrap-tpls'], function() {
    var m1 = angular.module('module1', ['ui.bootstrap']);
    m1.config(['$controllerProvider', function($controllerProvider) {
      console.log('module1 - config begin');
    }]);
    m1.controller('ctrlModule1', ['$scope', '$uibModal', function($scope, $uibModal) {
      console.log('module1 - ctrl begin');
      var dlg = '';
      dlg += '

以上就是本文的全部内容,希望对大家的学习有所帮助。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/98206.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号