【问题标题】:Loading html and Controller from server and creating dynamic states UI - router从服务器加载 html 和 Controller 并创建动态 UI - 路由器
【发布时间】:2016-02-17 02:27:25
【问题描述】:

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

我的场景:

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

我用什么:

  1. 科尔多瓦
  2. AngularJs
  3. 离子框架

为什么我需要它是动态的:

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部分:

//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 ,用 Controller 创建了 State 一切似乎都工作正常,除了控制器似乎根本不工作,没有错误,但似乎不执行任何控制器逻辑。目前,从之前下载的文件中注册控制器的唯一解决方案是使用 eval(),,这更像是一种 hack,而不是正确的解决方案。

代码如下:

.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();

        }
    }
}])

任何帮助表示赞赏

【问题讨论】:

  • 为什么不加载所有内容并根据用户访问权限隐藏未使用的菜单项?模板并没有占用太多空间。
  • 好吧,在这种情况下,他们的应用程序会变得非常大,所以我有必要这样做。
  • 我个人不会那样做。现在已经不是 2000 年代初期了。现在都是胖客户端 html5+ajax 调用。我把所有的东西都放了。客户端上的所有模块。和 50 个模块,尤其是 html/css/javascript 是个笑话。甚至在移动设备上。
  • 这不是问题,目标是为每个用户提供 1 个应用程序,并且能够在不重新部署应用程序的情况下更改、修改、添加、删除内容(不通过应用程序商店) ,目前的问题是我必须将应用程序重新部署到每台设备,每次进行错误修复、更改、添加时

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


【解决方案1】:

好的,让我们从头开始。

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

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

如果您坚持不一次加载所有布局,当然可以将它们分成部分,在登录后根据用户权限加载。通过这样做,您可以减少内存中的数据量,尽管这种改进充其量是值得怀疑的。

【讨论】:

  • 请看我上面的评论
  • 逻辑还在设备上,只是从服务器预加载,通过加载控制器文件注册为控制器
  • 好吧,我的答案保持不变。但是,您应该考虑将客户端应用程序作为一个简单的浏览器包装器并重新下载 html/js 文件,以防发布日期与本地缓存中的日期不同。
  • 是的,这就是我想要做的,但不知何故我可以下载所有东西,但我无法注册下载的控制器,这是当前的问题
【解决方案2】:

我可以建议您对加载状态的方式进行一些更改吗?
编写一个脚本,返回一个包含用户可以访问的状态的 json。
例如
资源/路由配置.yourLangage?user=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 个不同的用户门户,我可以轻松扩展到我想要的所有用户门户。

【讨论】:

  • 这看起来很有希望,必须尝试一下,我已经找到了一种通过 OcLazyLoad 实现它的方法,这非常好,因为我可以加载整个模块控制器和 html 文件,但我会试一试星期一,让你知道结果如何
【解决方案3】:

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

文件夹结构:

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 页面实际上都是一个独立的 Angular 应用程序。使用以下代码。

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

    });

【讨论】:

  • 您甚至可以为所有创建一个通用的 index.jsp 页面,并在菜单 URL 上为不同的模块名称传递一个查询参数。并取决于来自服务器端的模块名称控制 ng 应用程序 data-ng-app="&lt;%= moduleName %&gt;"
  • 这看起来很像我正在寻找的东西,对我来说问题是,我不知道如何在从服务器加载控制器后注册我的控制器,我能够加载 html 和使用它作为模板,我能够动态创建状态,我能够加载控制器,但我无法正确注册它,因为它是一个字符串,即使使用 eval('ctrlstring') 也不是一切正常......
【解决方案4】:

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

一、文件夹结构

  • 核心应用

config.js

  • 模块 1 (/module1)

模块.js

controllers.js

  • 模块 2 (/module2)

模块.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'
        });
    });
}]);

然后我们配置模块如下:
module1/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"
        }
    }
})

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多