Loading html and Controller from server and creating dynamic states UI - router Loading html and Controller from server and creating dynamic states UI - router angularjs angularjs

Loading html and Controller from server and creating dynamic states UI - router


Ok, so let's start from the beginning.

All the application logic should be contained on the server and served via API-calls through REST, SOAP or similar. By doing so, you reduce the amount of logic built into the UI, which reduces the stress on the client. This basically makes your client app a rendering agent, containing only models and views for the data and logic served by the backend API.

As foreyez stated in his/her comment, this isn't an issue for any modern (or half-modern) device.

If you insist on not loading all of the layouts at once, you could of course separate them into partials, which you load after the login based on the user privileges. By doing so, you reduce the amount of in-memory data, even though the improvement would be doubtable, at best.


Can I suggest you to do some changes to the way you load the states?
Write a script that give you back a json with the states the user can access.
Ex.
resources/routing-config.yourLangage?user=user-id-12345
this will return a json file that depends on the user logged in. The structure can be something like this:

    [      {        "name": "home",        "url": "/home",        "templateUrl": "views/home.html",        "controller": "HomeController",        "dependencies": ["scripts/home/controllers.js", "scripts/home/services.js", "scripts/home/directives.js"]      },      {        "name": "user",        "url": "/user",        "templateUrl": "views/user.html",        "controller": "UserController",        "dependencies": ["scripts/user/controllers.js", "scripts/user/services.js", "scripts/home/directives.js"]      }    ]

Then let's write a service that will read the states the user is allowed to access:

app.factory('routingConfig', ['$resource',  function ($resource) {    return $resource('resources/routing-config.yourLangage', {}, {      query: {method: 'GET',              params: {},              isArray: true,              transformResponse: function (data) {                  // before that we give the states data to the app, let's load all the dependencies                  var states = [];                  angular.forEach(angular.fromJson(data), function(value, key) {                    value.resolve = {                        deps: ['$q', '$rootScope', function($q, $rootScope){                          // this will be resolved only when the user will go to the relative state defined in the var value                          var deferred = $q.defer();                          /*                            now we need to load the dependencies. I use the script.js javascript loader to load the dependencies for each page.                            It is very small and easy to be used                            http://www.dustindiaz.com/scriptjs                          */                          $script(value.dependencies, function(){ //here we will load what is defined in the dependencies field. ex: "dependencies": ["scripts/user/controllers.js", "scripts/user/services.js", "scripts/home/directives.js"]                            // all dependencies have now been loaded by so resolve the promise                            $rootScope.$apply(function(){                              deferred.resolve();                            });                          });                          return deferred.promise;                        }]                      };                    states.push(value);                  });                  return states;                }            }    });  }]);

Then let's configure the app:

app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', '$filterProvider', '$provide', '$compileProvider',  function ($stateProvider, $urlRouterProvider, $locationProvider, $filterProvider, $provide, $compileProvider) {    // this will be the default state where to go as far as the states aren't loaded    var loading = {        name: 'loading',        url: '/loading',        templateUrl: '/views/loading.html',        controller: 'LoadingController'    };    // if the user ask for a page that he cannot access    var _404 = {        name: '_404',        url: '/404',        templateUrl: 'views/404.html',        controller: '404Controller'    };    $stateProvider      .state(loading)      .state(_404);    // save a reference to all of the providers to register everything lazily    $stateProviderRef = $stateProvider;    $urlRouterProviderRef = $urlRouterProvider;    $controllerProviderRef = $controllerProvider;    $filterProviderRef = $filterProvider;    $provideRef = $provide;    $compileProviderRef = $compileProvider;    //redirect the not found urls    $urlRouterProvider.otherwise('/404');  }]);

Now let's use this service in the app.run:

app.run(function ($location, $rootScope, $state, $q, routingConfig) {  // We need to attach a promise to the rootScope. This will tell us when all of the states are loaded.  var myDeferredObj = $q.defer();  $rootScope.promiseRoutingConfigEnd = myDeferredObj.promise;  // Query the config file  var remoteStates = routingConfig.query(function() {    angular.forEach(remoteStates, function(value, key) {      // the state becomes the value      $stateProviderRef.state(value);    });      // resolve the promise.      myDeferredObj.resolve();  });  //redirect to the loading page until all of the states are completely loaded and store the original path requested  $rootScope.myPath = $location.path();  $location.path('/loading'); //and then (in the loading controller) we will redirect to the right state  //check for routing errors  $rootScope.$on('$stateChangeError',     function(event, toState, toParams, fromState, fromParams, error){      console.log.bind(console);  });  $rootScope.$on('$stateNotFound',     function(event, unfoundState, fromState, fromParams){         console.error(unfoundState.to); // "lazy.state"        console.error(unfoundState.toParams); // {a:1, b:2}        console.error(unfoundState.options); // {inherit:false} + default options  });});

Eventually, the LoadingController:

app.controller('LoadingController', ['$scope', '$location', '$rootScope',  function($scope, $location, $rootScope) {    //when all of the states are loaded, redirect to the requested state    $rootScope.promiseRoutingConfigEnd.then(function(){      //if the user requested the page /loading then redirect him to the home page      if($rootScope.myPath === '/loading'){        $rootScope.myPath = '/home';      }      $location.path($rootScope.myPath);    });}]);

In this way everything is super flexible and lazy loaded.

I wrote 3 different user portals already and I can easily scale to all of the user portal I want.


I have developed an application with keeping those things in mind. Here is my architecture.

Folder Structure:

WebApp|---CommonModule    |---common-module.js //Angular Module Defination    |---Controllers     //Generally Nothing, but if you have a plan to                        //extend from one CommonController logic to several                         //module then it is usefull    |---Services        //Common Service Call Like BaseService for all $http                         //call, So no Module Specific Service will not use                         //$http directly. Then you can do several common                         //things in this BaseService.                         //Like Error Handling,                         //CSRF token Implementation,                         //Encryption/Decryption of AJAX req/res etc.    |---Directives      //Common Directives which you are going to use                         //in different Modules    |---Filters         //Common Filters    |---Templates       //Templates for those common directives    |---index.jsp       //Nothing, Basically Redirect to                         //Login or Default Module    |---scripts.jsp     //JQuery, AngularJS and Other Framworks scripts tag.                        //Along with those, common controlers, services,                         //directives and filtes.     |---templates.jsp   //Include all common templates.    |---ng-include.jsp  //will be used in templates.jsp to create angular                         //template script tag.|---ModuleA    |---moduleA-module.js //Angular Module Definition,                           //Use Common Module as Sub Module    |---Controllers    |---Services    |---Directives    |---Filters    |---Templates    |---index.jsp     |---scripts.jsp     |---templates.jsp|---ModuleB    |--- Same as above ...

Note: Capital Case denotes folder. Beside ModuleA there will a LoginModule for your case I think or You could Use CommonModule for it.

Mehu will be as follows.

<a href="/ModuleA/">Module A</a> <!--Note: index.jsp are indexed file                                  //for a directive --><a href="/ModuleB/">Module B</a>

Each of those JSP page are actually a independent angular application. Using those following code.

ModuleA/index.jsp

<!-- Check User Permission Here also for Security      If permission does not have show Module Unavailable Kind of JSP.     Also do not send any JS files for this module.     If permission is there then use this following JSP--><!DOCTYPE HTML><html  lang="en" data-ng-app="ModuleA">    <head>         <title>Dynamic Rule Engine</title>        <%@ include file="scripts.jsp" %>         <%@ include file="templates.jsp" %> <!-- Can be cached it in                                                 different way -->     </head>    <body>        <%@ include file="../common.jsp" %>        <div id="ngView" data-ng-view></div>        <%@ include file="../footer.jsp" %>    </body></html>

ModuleA/scripts.jsp

<%@ include file="../CommonModule/scripts.jsp" %> <!-- Include Common Things                                              Like Jquery Angular etc  --><scripts src="Controlers/ModlueAController1.js"></script>.....

ModuleA/templates.jsp

<%@ include file="../CommonModule/templates.jsp" %> <!-- Include Common Templates for common directives --><jsp:include page="../CommonModule/ng-include.jsp"><jsp:param name="src" value="ModuleA/Templates/template1.jsp" /></jsp:include>.....

CommonModule/ng-include.jsp

<script type="text/ng-template" id="${param.src}">    <jsp:include page="${param.src}" /></script>

But main problem of this approach is When user will change Module, Page will get refreshed.

EDIT:There is a ModuleA.module.js file which actually contain module deceleration as follows.

angular.module('ModuleA.controllers', []);angular.module('ModuleA.services', []);angular.module('ModuleA.directives', []);angular.module('ModuleA.filters', []);angular.module('ModuleA',        ['Common',         'ModuleA.controllers' ,         'ModuleA.services' ,         'ModuleA.directives' ,         'ModuleA.filters'])    .config(['$routeProvider', function($routeProvider) {        //$routeProvider state setup    }])    .run (function () {    });