欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

探索angularjs+requirejs全面實(shí)現(xiàn)按需加載的套路

 更新時(shí)間:2016年02月26日 11:30:17   投稿:lijiao  
這篇文章主要探索了angularjs+requirejs全面實(shí)現(xiàn)按需加載的套路,圍繞angularjs提供的各種機(jī)制進(jìn)行研究,感興趣的小伙伴們可以參考一下

在進(jìn)行有一定規(guī)模的項(xiàng)目時(shí),通常希望實(shí)現(xiàn)以下目標(biāo):1、支持復(fù)雜的頁(yè)面邏輯(根據(jù)業(yè)務(wù)規(guī)則動(dòng)態(tài)展現(xiàn)內(nèi)容,例如:權(quán)限,數(shù)據(jù)狀態(tài)等);2、堅(jiān)持前后端分離的基本原則(不分離的時(shí)候,可以在后端用模版引擎直接生成好頁(yè)面);3、頁(yè)面加載時(shí)間短(業(yè)務(wù)邏輯復(fù)雜就需要引用第三方的庫(kù),但很可能加載的庫(kù)和用戶本次操作沒關(guān)系);4,還要代碼好維護(hù)(加入新的邏輯時(shí),影響的文件盡量少)。

想同時(shí)實(shí)現(xiàn)這些目標(biāo),就必須有一套按需加載的機(jī)制,頁(yè)面上展現(xiàn)的內(nèi)容和所有需要依賴的文件,都可以根據(jù)業(yè)務(wù)邏輯需要按需加載。最近都是基于angularjs做開發(fā),所以本文主要圍繞angularjs提供的各種機(jī)制,探索全面實(shí)現(xiàn)按需加載的套路。

一、一步一步實(shí)現(xiàn)
基本思路:1、先開發(fā)一個(gè)框架頁(yè)面,它可以完成一些基本的業(yè)務(wù)邏輯,并且支持?jǐn)U展的機(jī)制;2、業(yè)務(wù)邏輯變復(fù)雜,需要把部分邏輯拆分到子頁(yè)面中,子頁(yè)面按需加載;3、子頁(yè)面中的展現(xiàn)內(nèi)容也變了復(fù)雜,又需要進(jìn)行拆分,按需加載;4、子頁(yè)面的內(nèi)容復(fù)雜到依賴外部模塊,需要按需加載angular模塊。

1、框架頁(yè)
提到前端的按需加載,就會(huì)想到AMD( Asynchronous Module Definition),現(xiàn)在用requirejs的非常多,所以首先考慮引入requires。

index.html

<script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js"></script>

注意:采用手動(dòng)啟動(dòng)angular的方式,因此html中沒有ng-app。

spa-loader.js

require.config({
  paths: {
    "domReady": '/static/js/domReady',
    "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min",
    "angular-route": "http://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"]); /*手工啟動(dòng)angular*/
    window.loading.finish();
  });
});

2、按需加載子頁(yè)面
angular的routeProvider+ng-view已經(jīng)提供完整的子頁(yè)面加載的方法,直接用。
注意必須設(shè)置html5Mode,否則url變化以后,routeProvider不截獲。

index.html

<div>
  <a href="/test/lazyspa/page1">page1</a>
  <a href="/test/lazyspa/page2">page2</a>
  <a href="/test/lazyspa/">main</a>
</div>
<div ng-view></div>

spa.js

app.config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {
  /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */
  $locationProvider.html5Mode(true);
  /* 根據(jù)url的變化加載內(nèi)容 */
  $routeProvider.when('/test/lazyspa/page1', {
    template: '<div>page1</div>',
  }).when('/test/lazyspa/page2', {
    template: '<div>page2</div>',
  }).otherwise({
    template: '<div>main</div>',
  });
}]);

3、按需加載子頁(yè)面中的內(nèi)容
用routeProvider的前提是url要發(fā)生變化,但是有的時(shí)候只是子頁(yè)面中的局部要發(fā)生變化。如果這些變化主要是和綁定的數(shù)據(jù)相關(guān),不影響頁(yè)面布局,或者影響很小,那么通過ng-if一類的標(biāo)簽基本就解決了。但是有的時(shí)候要根據(jù)頁(yè)面狀態(tài),完全改變局部的內(nèi)容,例如:用戶登錄前和登錄后局部要發(fā)生的變化等,這就意味著局部的布局可能也挺復(fù)雜,需要作為獨(dú)立的單元來(lái)對(duì)待。

利用ng-include可以解決頁(yè)面局部?jī)?nèi)容加載的問題。但是,我們可以再考慮更復(fù)雜一些的情況。這個(gè)頁(yè)面片段對(duì)應(yīng)的代碼是后端動(dòng)態(tài)生成的,而且不僅僅有html還有js,js中定義了代碼片段對(duì)應(yīng)的controller。這種情況下,不僅僅要考慮動(dòng)態(tài)加載html的問題,還要考慮動(dòng)態(tài)定義controller的問題。controller是通過angular的controllerProvider的register方法注冊(cè),因此需要獲得controllerProvider的實(shí)例。

spa.js

app.config(['$locationProvider', '$routeProvider', '$controllerProvider', function($locationProvider, $routeProvider, $controllerProvider) {
  app.providers = {
    $controllerProvider: $controllerProvider //注意這里?。?!
  };
  /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */
  $locationProvider.html5Mode(true);
  /* 根據(jù)url的變化加載內(nèi)容 */
  $routeProvider.when('/test/lazyspa/page1', {
    /*!!!頁(yè)面中引入動(dòng)態(tài)內(nèi)容!!!*/
    template: '<div>page1</div><div ng-include="\'page1.html\'"></div>',
    controller: 'ctrlPage1'
  }).when('/test/lazyspa/page2', {
    template: '<div>page2</div>',
  }).otherwise({
    template: '<div>main</div>',
  });
  app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) {
    /* 用這種方式,ng-include配合,根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)獲取頁(yè)面內(nèi)容 */
    /* !!!動(dòng)態(tài)的定義controller!!! */
    app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) {
      $scope.openAlert = function() {
        alert('page1 alert');
      };
    }]);
    /* !!!動(dòng)態(tài)定義頁(yè)面的內(nèi)容!!! */
    $templateCache.put('page1.html', '<div ng-controller="ctrlPage1Dyna"><button ng-click="openAlert()">alert</button></div>');
  }]);
}]);

4、動(dòng)態(tài)加載模塊
采用上面子頁(yè)面片段的加載方式存在一個(gè)局限,就是各種邏輯(js)要加入到啟動(dòng)模塊中,這樣還是限制子頁(yè)面片段的獨(dú)立封裝。特別是,如果子頁(yè)面片段需要使用第三方模塊,且這個(gè)模塊在啟動(dòng)模塊中沒有事先加載時(shí),就沒有辦法了。所以,必須要能夠?qū)崿F(xiàn)模塊的動(dòng)態(tài)加載。實(shí)現(xiàn)模塊的動(dòng)態(tài)加載就是把a(bǔ)ngular啟動(dòng)過程中加載模塊的方式提取出來(lái),再處理一些特殊情況。

但是,實(shí)際跑起來(lái)發(fā)現(xiàn)文章中的代碼有問題,就是“$injector”到底是什么?研究了angular的源代碼injector.js才大概搞明白是怎么回事。

一個(gè)應(yīng)用有兩個(gè)$injector,providerInjector和instanceInjector。invokeQueue和用providerInjector,runBlocks用instanceProvider。如果$injector用錯(cuò)了,就會(huì)找到需要的服務(wù)。

routeProvider中動(dòng)態(tài)加載模塊文件。

template: '<div ng-controller="ctrlModule1"><div>page2</div><div><button ng-click="openDialog()">open dialog</button></div></div>',
resolve: {
  load: ['$q', function($q) {
    var defer = $q.defer();
    /* 動(dòng)態(tài)加載angular模塊 */
    require(['/test/lazyspa/module1.js'], function(loader) {
      loader.onload && loader.onload(function() {
        defer.resolve();
      });
    });
    return defer.promise;
  }]
}

動(dòng)態(tài)加載angular模塊

angular._lazyLoadModule = function(moduleName) {
  var m = angular.module(moduleName);
  console.log('register module:' + moduleName);
  /* 應(yīng)用的injector,和config中的injector不是同一個(gè),是instanceInject,返回的是通過provider.$get創(chuàng)建的實(shí)例 */
  var $injector = angular.element(document).injector();
  /* 遞歸加載依賴的模塊 */
  angular.forEach(m.requires, function(r) {
    angular._lazyLoadModule(r);
  });
  /* 用provider的injector運(yùn)行模塊的controller,directive等等 */
  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);
    }
  });
  /* 用provider的injector運(yùn)行模塊的config */
  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);
    }
  });
  /* 用應(yīng)用的injector運(yùn)行模塊的run */
  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');
  /* !!! 動(dòng)態(tài)定義requirejs !!!*/
  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']
      }
    }
  });
  /*!!! 模塊中需要引用第三方的庫(kù),加載模塊依賴的模塊 !!!*/
  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');
      /*!!! 打開angular ui的對(duì)話框 !!!*/
      var dlg = '<div class="modal-header">';
      dlg += '<h3 class="modal-title">I\'m a modal!</h3>';
      dlg += '</div>';
      dlg += '<div class="modal-body">content</div>';
      dlg += '<div class="modal-footer">';
      dlg += '<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>';
      dlg += '<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>';
      dlg += '</div>';
      $scope.openDialog = function() {
        $uibModal.open({
          template: dlg,
          controller: ['$scope', '$uibModalInstance', function($scope, $mi) {
            $scope.cancel = function() {
              $mi.dismiss();
            };
            $scope.ok = function() {
              $mi.close();
            };
          }],
          backdrop: 'static'
        });
      };
    }]);
    /* !!!動(dòng)態(tài)加載模塊!!! */
    angular._lazyLoadModule('module1');
    console.log('module1 loaded');
    angular.forEach(onloads, function(onload) {
      angular.isFunction(onload) && onload();
    });
  });
  return {
    onload: function(callback) {
      onloads.push(callback);
    }
  };
});

二、完整的代碼
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta content="width=device-width,user-scalable=no,initial-scale=1.0" name="viewport">
    <base href='/'>
    <title>SPA</title>
  </head>
  <body>
    <div ng-controller='ctrlMain'>
      <div>
        <a href="/test/lazyspa/page1">page1</a>
        <a href="/test/lazyspa/page2">page2</a>
        <a href="/test/lazyspa/">main</a>
      </div>
      <div ng-view></div>
    </div>
    <div class="loading"><div class='loading-indicator'><i></i></div></div>
    <script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js?_=3"></script>
  </body>
</html>

spa-loader.js

window.loading = {
  finish: function() {
    /* 保留個(gè)方法做一些加載完成后的處理,我實(shí)際的項(xiàng)目中會(huì)在這里結(jié)束加載動(dòng)畫 */
  },
  load: function() {
    require.config({
      paths: {
        "domReady": '/static/js/domReady',
        "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min",
        "angular-route": "http://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);
    /* 應(yīng)用的injector,和config中的injector不是同一個(gè),是instanceInject,返回的是通過provider.$get創(chuàng)建的實(shí)例 */
    var $injector = angular.element(document).injector();
    /* 遞歸加載依賴的模塊 */
    angular.forEach(m.requires, function(r) {
      angular._lazyLoadModule(r);
    });
    /* 用provider的injector運(yùn)行模塊的controller,directive等等 */
    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);
      }
    });
    /* 用provider的injector運(yùn)行模塊的config */
    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);
      }
    });
    /* 用應(yīng)用的injector運(yùn)行模塊的run */
    angular.forEach(m._runBlocks, function(fn) {
      $injector.invoke(fn);
    });
  };
  app.config(['$injector', '$locationProvider', '$routeProvider', '$controllerProvider', function($injector, $locationProvider, $routeProvider, $controllerProvider) {
    /**
     * config中的injector和應(yīng)用的injector不是同一個(gè),是providerInjector,獲得的是provider,而不是通過provider創(chuàng)建的實(shí)例
     * 這個(gè)injector通過angular無(wú)法獲得,所以在執(zhí)行config的時(shí)候把它保存下來(lái)
    */
    app.providers = {
      $injector: $injector,
      $controllerProvider: $controllerProvider
    };
    /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */
    $locationProvider.html5Mode(true);
    /* 根據(jù)url的變化加載內(nèi)容 */
    $routeProvider.when('/test/lazyspa/page1', {
      template: '<div>page1</div><div ng-include="\'page1.html\'"></div>',
      controller: 'ctrlPage1'
    }).when('/test/lazyspa/page2', {
      template: '<div ng-controller="ctrlModule1"><div>page2</div><div><button ng-click="openDialog()">open dialog</button></div></div>',
      resolve: {
        load: ['$q', function($q) {
          var defer = $q.defer();
          /* 動(dòng)態(tài)加載angular模塊 */
          require(['/test/lazyspa/module1.js'], function(loader) {
            loader.onload && loader.onload(function() {
              defer.resolve();
            });
          });
          return defer.promise;
        }]
      }
    }).otherwise({
      template: '<div>main</div>',
    });
  }]);
  app.controller('ctrlMain', ['$scope', '$location', function($scope, $location) {
    console.log('main controller');
    /* 根據(jù)業(yè)務(wù)邏輯自動(dòng)到缺省的視圖 */
    $location.url('/test/lazyspa/page1');
  }]);
  app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) {
    /* 用這種方式,ng-include配合,根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)獲取頁(yè)面內(nèi)容 */
    /* 動(dòng)態(tài)的定義controller */
    app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) {
      $scope.openAlert = function() {
        alert('page1 alert');
      };
    }]);
    /* 動(dòng)態(tài)定義頁(yè)面內(nèi)容 */
    $templateCache.put('page1.html', '<div ng-controller="ctrlPage1Dyna"><button ng-click="openAlert()">alert</button></div>');
  }]);
  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 = '<div class="modal-header">';
      dlg += '<h3 class="modal-title">I\'m a modal!</h3>';
      dlg += '</div>';
      dlg += '<div class="modal-body">content</div>';
      dlg += '<div class="modal-footer">';
      dlg += '<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>';
      dlg += '<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>';
      dlg += '</div>';
      $scope.openDialog = function() {
        $uibModal.open({
          template: dlg,
          controller: ['$scope', '$uibModalInstance', function($scope, $mi) {
            $scope.cancel = function() {
              $mi.dismiss();
            };
            $scope.ok = function() {
              $mi.close();
            };
          }],
          backdrop: 'static'
        });
      };
    }]);
    angular._lazyLoadModule('module1');
    console.log('module1 loaded');
    angular.forEach(onloads, function(onload) {
      angular.isFunction(onload) && onload();
    });
  });
  return {
    onload: function(callback) {
      onloads.push(callback);
    }
  };
});

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。

相關(guān)文章

最新評(píng)論