从服务器加载html和Controller并创建动态UI - 路由器

时间:2016-01-27 16:05:47

标签: javascript angularjs requirejs angular-ui-router oclazyload

我正在寻找一种从服务器动态加载我的应用内容的解决方案。

我的情景:

假设我们有2个用户(A和B),我的应用程序包含不同的模块,比如说一个shoppingList和一个计算器,现在我的目标是用户从数据库登录我的应用程序我获得用户权限和根据他拥有的权限,我将从服务器加载视图的html和逻辑部分的控制器文件,同时这样做我将创建html和ctrl所需的状态。因此,基本上我的应用程序与Login一致,并且其他所有内容都会根据Userrights从服务器中提取。

我用的是什么:

  1. Cordova
  2. AngularJs
  3. 离子框架
  4. 为什么我需要它全部动态:

    1)拥有一个仅包含登录逻辑的应用程序的可能性,因此在修复错误或添加模块时,我只需要将文件添加到服务器,即可为用户提供权利,而且无需更新应用程序。

    2)用户只有他需要的功能,当他只有1个模块的权利时,他不需要拥有所有东西。

    3)该应用程序目前变得非常大,这意味着每个模块都有5-10个状态,拥有自己的html和控制器。目前计划有50个不同的模块,因此您可以进行数学计算。

    我看了这个以获得灵感:

    AngularJS, ocLazyLoad & loading dynamic States

    到目前为止我尝试了什么:

    我创建了1个包含整个模块的Html文件,因此我只有1个http请求:

    让我们说这是用户登录后来自服务器的响应

    HTML部分:

    var rights= [A,B,C,D]
    
    angular.forEach(rights, function (value, key) {
         $http.get('http://myServer.com/templates/' + value + '.html').then(function (response) {
            //HTML file for the whole module
            splits = response.data.split('#');
            //Array off HTMl strings
            for (var l = 1; l <= splits.length; l++) {  
              //Putting all Html strings into templateCache                              
              $templateCache.put('templates/' + value +'.html', splits[l - 1]);
    
              }
            }
         });
    

    控制器部分:

    var rights= [A,B,C,D]
    
    angular.forEach(rights, function (value, key) {
         $http.get('http://myServer.com/controller/' + value + '.js').then(function (response) {
            // 1 file for the whole module with all controllers
            splits = response.data.split('#');
            //Array off controller strings
            for (var l = 1; l <= splits.length; l++) {  
              //Putting all Controller strings into templateCache                              
              $templateCache.put('controllers/' + value +'.js', splits[l - 1]);
    
              }
            }
         });
    

    加载控制器后,我尝试注册它们:

    $controllerProvider.register('SomeName', $templateCache.get('controllers/someController));
    

    哪个不起作用,因为这只是一个字符串......

    定义提供者:

    .config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider, $controllerProvider) {
    
      // turns of the page transition globally
        $ionicConfigProvider.views.transition('none');
        $stateProviderRef = $stateProvider;
        $urlRouterProviderRef = $urlRouterProvider;
        $controllerProviderRef = $controllerProvider;
    
    
        $stateProvider
    
        //the login state is static for every user
      .state('login', {
          url: "/login",
          templateUrl: "templates/login.html",
          controller: "LoginCtrl"
      });
    
       //all the other states are missing and should be created depending on rights
    
    $urlRouterProvider.otherwise('/login');
    
    
    });
    

    Ui-Router Part:

    //Lets assume here the Rights Array contains more information like name, url...
        angular.forEach(rights, function (value, key) {
           //Checks if the state was already added
             var getExistingState = $state.get(value.name)
    
             if (getExistingState !== null) {
                  return;
             }
    
              var state = {
                 'lang': value.lang,
                 'params': value.param,
                 'url': value.url,
                 'templateProvider': function ($timeout, $templateCache, Ls) {
                   return $timeout(function () {
                   return $templateCache.get("templates" + value.url + ".html")
                                        }, 100);
                                    },
                 'ControllerProvider': function ($timeout, $templateCache, Ls) {
                    return $timeout(function () {
                    return $templateCache.get("controllers" + value.url + ".js")
                                            }, 100);
                                        }
    
                                $stateProviderRef.state(value.name, state);
                            });
    
                            $urlRouter.sync();
                            $urlRouter.listen();
    

    到目前为止的情况:

    我已经设法加载html文件并将它们存储在templateCache中,甚至加载它们但仅在状态是预定义的时候加载它们。我在这里注意到有时可以说当我从列表中删除项目并返回到再次查看该项目可能这与缓存有关我不太确定...

    我已经设法加载控制器文件并将控制器保存在templateCache中,但我真的不知道如何将$ ControllerPrioviderRef.register与我存储的字符串一起使用...

    创建状态确实有效但控制器不合适所以我无法打开任何视图......

    PS:我还查看了require.js和OCLazyLoad以及此示例dynamic controller example

    更新

    好的,所以我设法加载Html,用State创建Controller一切似乎都运行正常,除了控制器似乎根本不起作用,还有没有错误,但似乎没有执行Controller逻辑。目前,从先前下载的文件注册控制器的唯一解决方案是使用eval(),,这更像是一个黑客,然后是一个正确的解决方案。

    这里是代码:

    .factory('ModularService', ['$http', ....., function ( $http, ...., ) {
        return {
            LoadModularContent: function () {
                //var $state = $rootScope.$state;
    
                var json = [
                {
                    module: 'Calc',
                    name: 'ca10',
                    lang: [],
                    params: 9,
                    url: '/ca10',
                    templateUrl: "templates/ca/ca10.html",
                    controller: ["Ca10"]
    
                },
                {
                    module: 'SL',
                    name: 'sl10',
                    lang: [],
                    params: 9,
                    url: '/sl10',
                    templateUrl: "templates/sl/sl10.html",
                    controller: ['Sl10', 'Sl20', 'Sl25', 'Sl30', 'Sl40', 'Sl50', 'Sl60', 'Sl70']
    
                }
                ];
    
                //Load the html 
                angular.forEach(json, function (value, key) {
                $http.get('http://myserver.com/' + value.module + '.html')
                .then(function (response) {
                   var splits = response.data.split('#');
                   for (var l = 1; l <= value.controller.length; l++) {
                     $templateCache.put('templates/' + value.controller[l - 1] + '.html', splits[l - 1]);
                        if (l == value.controller.length) {
                           $http.get('http://myserver.com//'+value.module+'.js')
                           .then(function (response2) {
                              var ctrls = response2.data.split('##');
                              var fullctrl;
                              for (var m = 1; m <= value.controller.length; m++){
    
                                var ctrlName = value.controller[m - 1] + 'Ctrl';                                                                             
    
                                $controllerProviderRef
                                .register(ctrlName, ['$scope',...., function ($scope, ...,) {    
                                       eval(ctrls[m - 1]);
                                }])
                                if (m == value.controller.length) {
    
                                  for (var o = 1; o <= value.controller.length; o++) {
                                   var html = $templateCache
                                  .get("templates/" + value.controller[o - 1] + ".html");
    
                                      var getExistingState = $state.get(value.controller[o - 1].toLowerCase());
    
                                     if (getExistingState !== null) {
                                                                return;
                                                            }
    
                                    var state = {
                                     'lang': value.lang,
                                     'params': value.param,
                                     'url': '/' + value.controller[o - 1].toLowerCase(),
                                     'template': html,
                                     'controller': value.controller[o - 1] + 'Ctrl'
                                     };
    
    
                                      $stateProviderRef.state(value.controller[o - 1].toLowerCase(), state);
                                     }
                                   }
                                 }
                              });
                            }
                         }                            
                     });                      
                });
                // Configures $urlRouter's listener *after* your custom listener
    
                $urlRouter.sync();
                $urlRouter.listen();
    
            }
        }
    }])
    

    任何帮助表示赞赏

4 个答案:

答案 0 :(得分:4)

好的,让我们从头开始。

所有应用程序逻辑都应包含在服务器上,并通过REST,SOAP或类似的API调用提供服务。通过这样做,您可以减少UI中内置的逻辑量,从而减轻客户端的压力。这基本上使您的客户端应用程序成为渲染代理,仅包含后端API所服务的数据和逻辑的模型和视图。

正如foreyez在他/她的评论中所说,对于任何现代(或半现代)设备来说,这都不是问题。

如果您坚持不立即加载所有布局,您当然可以将它们分成部分,您可以根据用户权限在登录后加载它们。通过这样做,您可以减少内存中数据的数量,即使这种改进最多也是可以怀疑的。

答案 1 :(得分:3)

我可以建议您对加载状态的方式进行一些更改吗? 编写一个脚本,返回一个json,其中包含用户可以访问的状态 例
资源/路由config.yourLangage?USER =用户ID-12345
这将返回一个json文件,该文件取决于登录的用户。结构可以是这样的:

    [
      {
        "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"]
      }
    ]

然后让我们编写一个服务来读取用户可以访问的状态:

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;
                }
            }
    });
  }]);

然后让我们配置应用程序:

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');

  }]);

现在让我们在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
  });

});

最后,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);
    });

}]);

通过这种方式,一切都是超级灵活和懒惰的。

我已经编写了3个不同的用户门户网站,我可以轻松扩展到我想要的所有用户门户网站。

答案 2 :(得分:2)

我开发了一个应用程序,记住这些事情。这是我的架构。

文件夹结构:

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 ...

注意 :大写字母表示文件夹。除了ModuleA之外,我还会为您的案例提供一个LoginModule,或者您可以使用CommonModule。

Mehu将如下。

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

每个JSP页面实际上都是一个独立的角度应用程序。使用以下代码。

<强> 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>

但这种方法的主要问题是当用户更改模块时,页面会刷新。

修改 有一个 ModuleA.module.js 文件实际上包含模块减速如下。

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 () {

    });

答案 3 :(得分:1)

我想我正在做你要问的事。我通过使用UI-Router,ocLazyLoad和ui-routers未来状态来实现这一点。基本上我们的设置允许我们拥有50多个模块,所有模块都在相同的代码库中,但是当用户打开应用程序时。它首先只加载应用程序所需的基本文件。然后,当用户在状态之间移动时,应用程序将根据需要加载该部分所需的文件。 (对于碎片化的代码道歉,我不得不将其从代码库中删除,但试图只提供与解决方案实际相关的东西)。

首先是文件夹结构

  • 核心应用

config.js

  • 第1单元(/ module1)

module.js

controllers.js

  • 第2单元(/ module2)

module.js

controllers.js

Config.js:

我们要做的第一件事是创建基本状态,这是一个抽象状态,因此用户实际上永远不会只是命中它。

$stateProvider.state('index', {
    abstract: true,
    url: "/index",
    views: {
        '': {
            templateUrl: "views/content.html" // a base template to have sections replaced via ui-view elements
        }
    },
    ...
});

然后我们在ocLazyLoad中配置模块。这允许我们告诉ocLazyLoad加载模块,并加载所有必需的文件(尽管在这个例子中,它只有一个文件,但它允许每个模块有不同的路径)。

$ocLazyLoadProvider.config({
    loadedModules: ['futureStates'],
    modules: [
        {
            name: 'module1',
            files: ['module1/module.js']
        },
        {
            name: 'module2',
            files: ['module2/module.js']
        }
    ]
});

接下来,我们创建一个函数,允许ui-router在请求时加载模块(通过未来状态)。

function ocLazyLoadStateFactory($q, $ocLazyLoad, futureState) {
    var deferred = $q.defer();
    // this loads the module set in the future state
    $ocLazyLoad.load(futureState.module).then(function () {
        deferred.resolve();
    }, function (error) {
        deferred.reject(error);
    });
    return deferred.promise;
}
$futureStateProvider.stateFactory('ocLazyLoad', ['$q', '$ocLazyLoad', 'futureState', ocLazyLoadStateFactory]);

然后我们配置实际的未来状态。这些是将来可能加载的状态,但我们现在不想配置它们。

$futureStateProvider.futureState({
    'stateName': 'index.module1', // the state name
    'urlPrefix': '/index/module1', // the url to the state
    'module': 'module1', // the name of the module, configured in ocLazyLoad above
    'type': 'ocLazyLoad' // the future state factory to use.
});
$futureStateProvider.futureState({
    'stateName': 'index.module2',
    'urlPrefix': '/index/module2',
    'module': 'module2',
    'type': 'ocLazyLoad'
});

如果您希望以异步方式提供未来状态列表:

$futureStateProvider.addResolve(['$http', function ($http) {
    return $http({method: 'GET', url: '/url'}).then(function (states) {
        $futureStateProvider.futureState({
            'stateName': 'index.module2',
            'urlPrefix': '/index/module2',
            'module': 'module2',
            'type': 'ocLazyLoad'
        });
    });
}]);

然后我们按如下方式配置模块:
模块1 / module.js

$stateProvider.state('index.module1', {
    url: "/module1",
    abstract: true,
    resolve: {
        loadFiles: ['$ocLazyLoad', function($ocLazyLoad){
            return  return $ocLazyLoad.load(['list of all your required files']);
        }]
    }
})
$stateProvider.state('index.module1.sub1', {
    url: "/sub1",
    views: {
       // override your ui-views in here. this one overrides the view named 'main-content' from the 'index' state
       'main-content@index': {
            templateUrl: "module1/views/sub1.html"
        }
    }
})