bootstrap javascript angularjs requirejs angular-ui-router oclazyload

javascript - bootstrap - Cargando html y Controller desde el servidor y creando estados dinámicos UI-enrutador



ol html (4)

Estoy buscando una solución para cargar dinámicamente el contenido de mi aplicación desde el servidor.

Mi Escenario:

Digamos que tenemos 2 usuarios (A y B), mi aplicación consta de diferentes módulos como, por ejemplo, una lista de compras y una calculadora, ahora mi objetivo sería que el usuario inicie sesión en mi aplicación desde la base de datos. Tengo los derechos de usuario y depende de qué derechos. él tiene, yo cargaría el html para las vistas y los archivos del controlador para la parte lógica del servidor, y al hacerlo, crearía los estados necesarios para el html y ctrl. Así que, básicamente, mi aplicación es muy pequeña y coherente con el inicio de sesión y todo lo demás se extrae del servidor en función de los Userrights.

Lo que yo uso:

  1. Córdoba
  2. Angulares
  3. Marco jónico

Por qué necesito que sea todo dinámico:

1) La posibilidad de tener una aplicación que contenga solo la lógica de inicio de sesión, por lo que al corregir errores o agregar módulos, solo tengo que agregar los archivos al servidor, así que el usuario tiene derecho a ello y está allí sin necesidad de actualizar la aplicación.

2) El usuario solo tiene la funcionalidad que necesita, no necesita tener todo cuando solo tiene el derecho para 1 módulo.

3) La aplicación crece mucho en este momento, lo que significa que cada módulo tiene como 5-10 estados, con su propio html y controladores. actualmente hay 50 módulos diferentes planificados para que puedas hacer los cálculos.

Miré esto para inspirarme:

AngularJS, ocLazyLargar y cargar estados dinámicos

Lo que intenté hasta ahora:

Creé 1 archivo Html que contiene todo el módulo, así que solo tengo 1 solicitud http:

Digamos que esta es mi respuesta del servidor después de que el Usuario haya iniciado sesión

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

Parte del controlador:

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

Después de cargar los Controladores, trato de registrarlos:

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

Lo que no funciona, ya que esto es solo una cadena ...

Definiendo los Proveedores:

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

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

Situación hasta ahora:

Me las arreglé para cargar los archivos html y almacenarlos en el Caché de plantillas, incluso cargarlos, pero solo si los estados estaban predefinidos. Lo que noté aquí fue que a veces digamos cuándo elimino un elemento de una Lista y vuelvo a la Vista El elemento estaba allí otra vez, quizás esto tenga algo que ver con el caché. No estoy realmente seguro ...

Me las arreglé para cargar los archivos del controlador y guardar los controladores en el Cache de la plantilla, pero no sé cómo usar $ ControllerPrioviderRef.register con mis cadenas almacenadas ...

La creación de los estados funcionó pero el controlador no encajaba, así que no pude abrir ninguna vista ...

PS: También observé require.js y OCLazyLoad, así como este ejemplo de controlador dinámico.

Actualizar:

Bueno, me las arreglé para cargar el Html , crear el State con el Controller Todo parece funcionar bien, excepto que el Controlador no parece funcionar en absoluto, no hay errores, pero parece que no se ejecuta nada de la lógica del Controlador. Actualmente, la única solución para registrar el controlador del archivo descargado anteriormente era usar eval(), que es más un truco que una solución adecuada.

Aquí el código:

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

Cualquier ayuda apreciada


¿Puedo sugerirle que haga algunos cambios en la forma en que carga los estados?
Escriba un script que le devuelva un json con los estados a los que el usuario puede acceder.
Ex.
resources / routing-config.yourLangage? user = user-id-12345
esto devolverá un archivo json que depende del usuario que haya iniciado sesión. La estructura puede ser algo como esto:

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

Luego, escribamos un servicio que lea los estados a los que el usuario puede acceder:

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

Entonces vamos a configurar la aplicación:

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

Ahora vamos a usar este servicio en la aplicación.

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

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

De esta forma todo es súper flexible y cargado perezoso.

Ya escribí 3 portales de usuarios diferentes y puedo escalar fácilmente a todo el portal de usuarios que deseo.


Creo que estoy haciendo lo que me estás pidiendo. Logro esto utilizando los estados de UI-Router, ocLazyLoad y ui-routers. Esencialmente, nuestra configuración nos permite tener más de 50 módulos, todos en el mismo código base, pero cuando un usuario abre la aplicación. se inicia cargando solo los archivos base requeridos por la aplicación. Luego, a medida que el usuario se mueve entre los estados, la aplicación cargará los archivos necesarios para esa parte, según sea necesario. (disculpas por el código fragmentado, tuve que arrancarlo del código base, pero intenté proporcionar solo lo que realmente es relevante para la solución).

En primer lugar, la estructura de carpetas.

  • App Core

config.js

  • Módulo 1 (/ módulo1)

módulo.js

controllers.js

  • Módulo 2 (/ módulo2)

módulo.js

controllers.js

etc

Config.js:

Lo primero que hacemos es crear el estado base, este es un estado abstracto, por lo que el usuario nunca puede simplemente golpearlo.

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

Luego configuramos los módulos en ocLazyLoad. Esto nos permite simplemente decirle a ocLazyLoad que cargue el módulo, y carga todos los archivos requeridos (aunque en este caso, es solo un archivo único, pero permite que cada módulo tenga diferentes rutas).

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

A continuación, creamos una función para permitir que ui-router cargue los módulos cuando se solicite (a través de estados futuros).

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

Luego configuramos los estados futuros actuales. Estos son estados que pueden cargarse en el futuro, pero no queremos configurarlos ahora.

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

Si desea que la lista de estados futuros se proporcione de forma asíncrona:

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

Luego configuramos los módulos de la siguiente manera:
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" } } })


He desarrollado una aplicación teniendo en cuenta esas cosas. Aquí está mi arquitectura.

Estructura de la carpeta:

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

Nota : mayúscula denota la carpeta. Al lado del Módulo A habrá un LoginModule para su caso, creo, o podría usar CommonModule para ello.

Mehu será como sigue.

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

Cada una de esas páginas JSP es en realidad una aplicación angular independiente. Usando los siguientes códigos.

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>

Pero el principal problema de este enfoque es cuando el usuario cambiará el Módulo, la página se actualizará.

EDITAR: Hay un archivo ModuleA.module.js que en realidad contiene la desaceleración del módulo de la siguiente manera.

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


Ok, así que vamos a empezar desde el principio.

Toda la lógica de la aplicación debe estar contenida en el servidor y servirse a través de llamadas de API a través de REST, SOAP o similar. Al hacerlo, reduce la cantidad de lógica incorporada en la interfaz de usuario, lo que reduce la tensión en el cliente. Básicamente, esto hace que su aplicación cliente sea un agente de representación, que contiene solo modelos y vistas para los datos y la lógica que proporciona la API de back-end.

Como foreyez declaró en su comentario, esto no es un problema para ningún dispositivo moderno (o medio moderno).

Si insiste en no cargar todos los diseños a la vez, podría, por supuesto, separarlos en parciales, que cargará después del inicio de sesión en función de los privilegios del usuario. Al hacerlo, reduce la cantidad de datos en memoria, aunque la mejora sea dudosa, en el mejor de los casos.