Explore the Angularjs+requirejs to realize on-demand loading routines _angularjs

Source: Internet
Author: User


When you are doing a project of a certain size, you typically want to achieve the following goals: 1, support the complex page logic (according to the business rules of the dynamic presentation of content, such as: permissions, data status, etc.) 2, adhere to the principle of the separation of the front and back end (when not separated, you can use the template engine directly generate a good page); 3, The page load time is short (the business logic is complex to need to refer to a third party library, but it is likely to load the library and the user this operation does not matter); 4, but also the Code good maintenance (add new logic, the impact of the file as little as possible).



To achieve these goals at the same time, you must have a mechanism to load on demand, the content displayed on the page and all the files that need to be relied on, can be loaded on demand according to the business logic. Recently are based on ANGULARJS to do development, so this article mainly around Angularjs provide a variety of mechanisms to explore the full implementation of the required loading routines.



One or one step-by-step implementation
Basic idea: 1, first develop a frame page, it can complete some basic business logic, and support the extension of the mechanism; 2, the business logic becomes complex, need to split part of the logic into the sub page, the child page on demand loading; 3, the child page of the display content has become complex, and need to be split, on-demand loading; 4, The contents of a subpage are complex to rely on external modules, and the angular module needs to be loaded on demand.



1. Frames page
Referring to the front-end of the on-demand loading, you will think of AMD (asynchronous Module Definition), now with a lot of requirejs, so first consider introducing requires.



Index.html


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


Note: There is no ng-app in HTML because the method of manually starting the angular is used.



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"]);/* Manual start angular*/
    Window.loading.finish ();
  });


2, on-Demand child page loading
Angular's routeprovider+ng-view has already provided a complete sub page loading method that is used directly.
Note must set Html5mode, otherwise the URL changes, routeprovider not intercept.



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) {/
  * must be set to take effect, Otherwise the setting below does not take effect
  /$locationProvider. Html5mode (true);
  /* Load content according to the change of URL * * *
  $routeProvider. When ('/test/lazyspa/page1 ', {
    template: ' <div>page1</div> ') ,
  }). When ('/test/lazyspa/page2 ', {
    template: ' <div>page2</div> ',
  }). Otherwise ({
    Template: ' <div>main</div> ',
  }];


3, on-demand loading the contents of the child pages
The premise of using Routeprovider is that the URL will change, but sometimes only the part of the child page will change. If these changes are mainly related to the binding data, do not affect the layout of the page, or the impact is very small, then through the Ng-if a class of tags are basically solved. But sometimes according to the status of the page, completely change the content of the local, such as: User login before and after the local changes to occur, and so on, which means that the local layout may also be quite complex, need to be treated as an independent unit.



Using Ng-include can solve the problem of loading the local content of the page. However, we can consider a more complicated situation. The corresponding code for this page fragment is dynamically generated at the back-end, and not only in HTML but also in Js,js, which defines the corresponding controller of the code fragment. In this case, we need to consider not only the problem of dynamically loading HTML, but also the dynamic definition of controller. The controller is registered through the angular Controllerprovider register, so an instance of Controllerprovider needs to be obtained.



Spa.js


app.config (['$ locationProvider', '$ routeProvider', '$ controllerProvider', function ($ locationProvider, $ routeProvider, $ controllerProvider) {
  app.providers = {
    $ controllerProvider: $ controllerProvider // Note here! !! !!
  };
  / * Must be set to take effect, otherwise the following settings will not take effect * /
  $ locationProvider.html5Mode (true);
  / * Load content based on changes in url * /
  $ routeProvider.when ('/ test / lazyspa / page1', {
    / * !!! Introduce dynamic content to the page !!! * /
    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) {
    / * In this way, ng-include cooperates to dynamically get page content based on business logic * /
    / * !!! Dynamic controller !!! * /
    app.providers. $ controllerProvider.register ('ctrlPage1Dyna', ['$ scope', function ($ scope) {
      $ scope.openAlert = function () {
        alert ('page1 alert');
      };
    }]);
    / * !!! Dynamically define the content of the page !!! * /
    $ templateCache.put ('page1.html', '<div ng-controller = "ctrlPage1Dyna"> <button ng-click = "openAlert ()"> alert </ button> </ div>');
  }]);
}]);


4. Dynamic Loading Module
the use of Face page fragment loading method exists a limitation, is a variety of logic (JS) to add to the boot module, which is still limited to the independent packaging of child page fragments. In particular, if a child page fragment needs to use a Third-party module, and the module does not have to be loaded in the boot module, there is no way. Therefore, we must be able to implement the dynamic loading of the module. The dynamic loading of the module is to extract the module in the angular startup process, and then to deal with some special cases.



But, actually run up to find the code in the article has a problem, is "$injector" what is it? Study the source code of angular injector.js just to figure out what's going on.



One application has two $injector,providerinjector and Instanceinjector. Invokequeue and Providerinjector,runblocks with Instanceprovider. If the $injector is wrong, you will find the service you need.



Dynamically load module files in Routeprovider.


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 ();
    /* Dynamic Loading angular module * *
    require (['/test/lazyspa/module1.js '], function (loader) {
      loader.onload && Loader.onload (function () {
        defer.resolve ();});
    });
    Return Defer.promise
  }]
}


Dynamic load Angular Module


angular._lazyLoadModule = function (moduleName) {
  var m = angular.module (moduleName);
  console.log ('register module:' + moduleName);
  / * The injector of the application is not the same as the injector in the config, it is an instanceInject, and it returns an instance created by provider. $ Get * /
  var $ injector = angular.element (document) .injector ();
  / * Load dependent modules recursively * /
  angular.forEach (m.requires, function (r) {
    angular._lazyLoadModule (r);
  });
  / * Use the provider's injector to run the module's controller, directive, etc. * /
  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);
    }
  });
  / * Run the module's config with the provider's injector * /
  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);
    }
  });
  / * Run the module run with the application's injector * /
  angular.forEach (m._runBlocks, function (fn) {
    $ injector.invoke (fn);
  });
};


defines the module
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');
  / * !!! dynamically define 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']
      }
    }
  });
  / * !!! Modules need to reference third-party libraries and load modules that the module depends on !!!!!!
  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');
      / * !!! Open the dialog of angular ui !!! * /
      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'
        });
      };
    }]);
    / * !!! Dynamic loading module !!! * /
    angular._lazyLoadModule ('module1');
    console.log ('module1 loaded');
    angular.forEach (onloads, function (onload) {
      angular.isFunction (onload) && onload ();
    });
  });
  return {
    onload: function (callback) {
      onloads.push (callback);
    }
  };
});


Second, the complete code
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 () {
     / * Reserve a method to do some processing after loading is finished, in my actual project, the loading animation will end here * /
   },
   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']);
  / * Lazy loading module * /
  angular._lazyLoadModule = function (moduleName) {
    var m = angular.module (moduleName);
    console.log ('register module:' + moduleName);
    / * The injector of the application is not the same as the injector in the config, it is an instanceInject, and it returns an instance created by provider. $ Get * /
    var $ injector = angular.element (document) .injector ();
    / * Load dependent modules recursively * /
    angular.forEach (m.requires, function (r) {
      angular._lazyLoadModule (r);
    });
    / * Use the provider's injector to run the module's controller, directive, etc. * /
    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);
      }
    });
    / * Run the module's config with the provider's injector * /
    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);
      }
    });
    / * Run the module run with the application's injector * /
    angular.forEach (m._runBlocks, function (fn) {
      $ injector.invoke (fn);
    });
  };
  app.config (['$ injector', '$ locationProvider', '$ routeProvider', '$ controllerProvider', function ($ injector, $ locationProvider, $ routeProvider, $ controllerProvider) {
    / **
     * The injector in the config is not the same as the injector of the application, it is the providerInjector, and the provider is obtained, not the instance created by the provider
     * This injector is not available through angular, so save it when executing config
    * /
    app.providers = {
      $ injector: $ injector,
      $ controllerProvider: $ controllerProvider
    };
    / * Must be set to take effect, otherwise the following settings will not take effect * /
    $ locationProvider.html5Mode (true);
    / * Load content based on changes in url * /
    $ 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 ();
          / * Load angular module dynamically * /
          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');
    / * Automatically go to the default view according to business logic * /
    $ location.url ('/ test / lazyspa / page1');
  }]);
  app.controller ('ctrlPage1', ['$ scope', '$ templateCache', function ($ scope, $ templateCache) {
    / * In this way, ng-include cooperates to dynamically get page content based on business logic * /
    / * Dynamically define controller * /
    app.providers. $ controllerProvider.register ('ctrlPage1Dyna', ['$ scope', function ($ scope) {
      $ scope.openAlert = function () {
        alert ('page1 alert');
      };
    }]);
    / * Dynamically define page content * /
    $ 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);
    }
  };
});


The above is the entire content of this article, I hope to help you learn.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.