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

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

本文关键字:状态 动态 UI 路由器 创建 加载 服务器 html 控制器      更新时间:2023-09-26

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

我的场景:

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

我使用什么:

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

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

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

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

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

我看这个是为了得到一些灵感:

AngularJS, ocLazyLoad & load dynamic states

到目前为止我尝试过:

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

假设这是我在用户登录后来自服务器的响应

网页部分:

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,用Controller创建了State,一切似乎都很好,除了控制器似乎根本不起作用,没有错误,但似乎没有执行任何控制器逻辑。目前,从先前下载的文件注册控制器的唯一解决方案是使用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();
        }
    }
}])

任何帮助表示赞赏

好的,让我们从头开始。

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

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

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

我可以建议你对加载状态的方式做一些改变吗?
编写一个脚本,返回一个包含用户可以访问的状态的 json。
前任。
resources/routeting-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
  });
});

最终,加载控制器:

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 个不同的用户门户,我可以轻松地扩展到我想要的所有用户门户。

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

文件夹结构:

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

注意:大写表示文件夹。在模块A旁边,我认为会有一个LoginModule适合您的情况,或者您可以使用CommonModule。

梅胡将如下所示。

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

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

模块 A/索引.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/脚本.jsp

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

模块 A/模板.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 () {
    });
我想

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

一、文件夹结构

  • 核心应用

配置.js

    模块 1
  • (/模块 1)

模块.js

控制器.js

    模块 2
  • (/模块 2)

模块.js

控制器.js

配置.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/模块.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"
        }
    }
})