16

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

我的场景:

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

我用什么:

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

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

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

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

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

我看了这个以获得一些灵感:

AngularJS、ocLazyLoad 和加载动态状态

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

我创建了 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路由器部分:

//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 中,甚至加载它们,但前提是状态是预定义的。我在这里注意到的是,有时可以说当我从列表中删除一个项目并返回查看项目又在那里了,也许这与缓存有关我不太确定......

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

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

PS:我还查看了 require.js 和 OCLazyLoad 以及这个示例动态控制器示例

更新:

好的,所以我设法加载,Html创建一切似乎工作正常,除了控制器似乎根本不工作,没有错误,但似乎没有执行控制器逻辑。目前,从先前下载的文件中注册控制器的唯一解决方案是使用这更像是一种破解而不是正确的解决方案。StateControllereval(),

这里的代码:

.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

4 回答 4

4

好的,让我们从头开始。

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

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

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

于 2016-02-02T08:34:59.423 回答
3

我可以建议您对加载状态的方式进行一些更改吗?
编写一个脚本,返回一个包含用户可以访问的状态的 json。
前任。
resources/routing-config.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 个不同的用户门户,并且可以轻松扩展到我想要的所有用户门户。

于 2016-02-12T18:14:55.593 回答
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 页面中的每一个实际上都是一个独立的 Angular 应用程序。使用以下代码。

模块A/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>

模块A/scripts.jsp

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

模块A/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 () {

    });
于 2016-02-09T06:50:07.547 回答
1

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

一、文件夹结构

  • 核心应用

配置.js

  • 模块 1 (/module1)

模块.js

控制器.js

  • 模块 2 (/module2)

模块.js

控制器.js

ETC

配置.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"
        }
    }
})
于 2016-02-16T18:27:25.270 回答