uirouter template start sref change angularjs templates controller angular-ui-router user-roles

template - AngularJS, ui.router, plantilla de carga y controlador en función del rol del usuario



ui router params (9)

¿Estoy pensando en la línea correcta o debería implementar otra solución?

OMI, no deberías hacerlo de esta manera.

Aquí, propongo otras 2 soluciones según cómo se implemente su aplicación.

1) Si se pueden configurar los derechos de sus roles (podría tener una página separada para configurar sus roles, asignar derechos a sus roles, ...). Luego use solo 1 plantilla y 1 controlador para sus roles (usuarios normales, usuarios administradores, y más ......) y use ng-show , ng-class , .. para mostrar su HTML en consecuencia.

En este caso, no nos importa mucho si el usuario es usuario normal o administrador, ese es el nombre de nuestra función. Lo que sí nos importa son los derechos y es dinámico => Por lo tanto, deberíamos mostrar el html dinámicamente en función de los derechos configurados (con seguridad, también hay controles en el lado del servidor cuando los usuarios realizan una acción para evitar que el usuario cree un solicitud HTTP y publicación en el servidor). Si tuviéramos que usar plantillas separadas para eso, hay innumerables casos.

El objetivo de esta solución es que las funciones de la página sean las mismas para sus roles, solo necesita mostrar / ocultar las funciones de la página en función del usuario.

2) Si los derechos de los roles son fijos (no se pueden configurar) y la funcionalidad de las vistas para los usuarios normales y administradores es diferente. Es mejor utilizar estados separados para estas vistas y autorizar el acceso a estas vistas en función del usuario que haya iniciado sesión (con seguridad, también hay autorización en el lado del servidor cuando los usuarios realizan una acción).

El motivo es que la vista del usuario administrador y la vista normal del usuario tienen una funcionalidad diferente (que debe separarse una de otra)

Desarrollé una aplicación de página única que usa una API REST. Los usuarios deben iniciar sesión para acceder a la aplicación. Cuando un usuario inicia sesión, se le redirige a / dashboard. En esta URL / ruta, me gustaría cargar una plantilla y un controlador diferente en función del rol del usuario (por ejemplo, usuario normal o usuario administrador ).

He consultado https://github.com/angular-ui/ui-router/wiki en la sección de plantillas, pero ninguna de las opciones admite lo que intento lograr.

  • Al usar templateUrl y function (stateParams) no puedo inyectar el servicio que me ayuda a determinar el rol del usuario para que pueda cargar la plantilla, por ejemplo, views / user /dashboard.html o views / admin /dashboard.html
  • Al usar templateProvider, debo inyectar el servicio que me ayuda a determinar la función del usuario, pero ¿cómo puedo cargar la plantilla?

Cualquier solución también debería cargar diferentes controladores según la función del usuario, por ejemplo, UserDashboardController o AdminDashboardController.

Así que, lo que realmente necesito es una ruta única que cargue una plantilla diferente Y un controlador basado en una variable de función de usuario que se establece en un servicio cuando un usuario inicia sesión.

¿Estoy pensando en la línea correcta o debería implementar otra solución?

Cualquier ayuda sobre esto sería muy apreciada.


Cargando la plantilla y el controlador en función del rol del usuario

Aunque técnicamente la función ui-router no admite la inyección de servicios, puede usar templateProvider para inyectar el service que contiene la variable de role o la carga de forma asíncrona y luego usa $templateFactory para devolver el contenido HTML. Considera el siguiente ejemplo:

var app = angular.module(''app'', [''ui.router'']); app.service(''session'', function($timeout, $q){ this.role = null; this.loadRole = function(){ //load role using axax request and return promise }; }); app.config(function($stateProvider, $urlRouterProvider){ $stateProvider.state(''dashboard'', { url: ''/dashboard'', templateProvider: function(session, $stateParams, $templateFactory){ return session.loadRole().then(function(role){ if(session.role == ''admin''){ return $templateFactory.fromUrl(''/admin/dashboard.html'', $stateParams); } else { return $templateFactory.fromUrl(''/user/dashboard.html'', $stateParams); } }); } }); $urlRouterProvider.otherwise(''/dashboard''); });

En cuanto al controller , puede indicar que desea usar el controlador específico dentro del elemento raíz de cada plantilla con ng-controller . O de forma similar, puede usar la opción controllerProvider para inyectar el service que ya tendrá un role resuelto por templateProvider . Eche un vistazo al siguiente ejemplo de la opción controllerProvider dentro ui-router la definición de estado ui-router :

controllerProvider: function(session){ if(session.role == ''admin''){ return ''AdminCtrl''; } else { return ''UserCtrl''; } }

Por supuesto, puede eliminar duplicados de este código fácilmente y definir una micro DSL más accesible para facilitar la definición de diferentes reglas para roles y vistas particulares.

La siguiente demo debería ayudarlo a comprender el código.

¿Es este un enfoque correcto?

Como suele ser esto, esto depende en gran medida del contexto. Para ayudarlo a encontrar una respuesta, permítame sugerirle las siguientes preguntas:

  • ¿Cuántas vistas presentadas a los roles difieren?

¿Va a ocultar solo un par de button y otros elementos de acción, básicamente haciendo que una página sea solo para los usuarios normales y editable para superusuarios? Si los cambios serán pequeños, probablemente usaría las mismas vistas y solo ocultaría elementos particulares, probablemente forjando una directiva similar a ng-if eso permitiera habilitar / deshabilitar la funcionalidad particular declarativamente only-role=''operator, admin'' . Por otro lado, si las vistas van a ser muy diferentes, entonces el uso de diferentes plantillas puede simplificar mucho el marcado.

  • ¿Cuántas acciones disponibles en una página particular difieren según el rol?

¿Las acciones que se ven similares en la superficie difieren en el funcionamiento interno para diferentes roles? Por ejemplo, si tiene la acción de Edición disponible para user rol de user y de admin pero en un caso inicia el asistente como UI y en otro forma compleja para usuarios avanzados, entonces tener un controller separado tiene más sentido. Por otro lado, si las acciones de admin son un superconjunto de acciones de user , entonces tener un solo controlador parece más fácil de seguir. Tenga en cuenta que, en ambos casos, mantener controller cosas del controller rinde frutos: solo deberían pegar vistas al comportamiento que está encapsulado en servicios / ver modelos / modelos / elegir un nombre

  • ¿Tendrá muchos enlaces contextualmente separados que conducen a una página particular desde diferentes lugares de la aplicación?

Por ejemplo, ser capaz de proporcionar navegación a una página en particular simplemente escribiendo ui-sref="dashboard" independientemente de la role usuario actual, puede ser beneficioso si existe en varios contextos. Si ese es el caso, entonces tenerlos definidos en una sola ruta / estado parece más ui-sref mantener que una lógica condicional utilizada para construir diferentes ui-sref / ng-href basados ​​en la función. Sin embargo, también podría definir rutas / estados dinámicamente en función del rol de usuario cargado dinámicamente o no

  • ¿Las vistas y acciones disponibles para diferentes roles en una página particular evolucionan por separado o juntas?

A veces, primero creamos características para usuarios regulares, luego premium y luego ultimate. No es inusual dividir el trabajo en las páginas para el user y el admin entre los miembros del equipo, especialmente si se pueden trazar fácilmente límites claros. En tal caso, tener views y controllers separados pueden simplemente los desarrolladores trabajar evitando conflictos. Por supuesto, no todos son arcoíris y unicornios: el equipo debe ser muy disciplinado para eliminar la duplicación que probablemente ocurra.

Espero que mis sugerencias te ayuden a decidir.



He empleado la siguiente solución (que podría no ser ideal, pero me ha funcionado en ese tipo de escenarios):

  1. Especifique el controlador en la plantilla en sí, usando ngController .

  2. Cargue la plantilla con un nombre de vista genérico (por ejemplo, views/dashboard.html ).

  3. Cambie lo que se refiere a views/dashboard.html utilizando $templateCache.put(...) cada vez que cambie la función user-role.

Aquí hay un ejemplo del enfoque:

app.controller(''loginCtrl'', function ($location, $scope, User) { ... $scope.loginAs = function (role) { // First set the user role User.setRole(role); // Then navigate to Dashboard $location.path(''/dashboard''); }; }); // A simplified `User` service that takes care of swapping templates, // based on the role. ("User" is probably not the best name...) app.service(''User'', function ($http, $templateCache) { var guestRole = ''guest''; var facadeUrl = ''views/dashboard.html''; var emptyTmpl = ''''; var errorTmpl = ''Failed to load template !''; var tempTmpl = ''Loading template...''; ... // Upon logout, put an empty template into `$templateCache` this.logout = function () { this.role = guestRole; $templateCache.put(facadeUrl, emptyTmpl); }; // When the role changes (e.g. upon login), set the role as well as the template // (remember that the template itself will specify the appropriate controller) this.setRole = function (role) { this.role = role; // The actual template URL var url = ''views/'' + role + ''/dashboard.html''; // Put a temporary template into `$templateCache` $templateCache.put(facadeUrl, tempTmpl); // Fetch the actual template (from the `$templateCahce` if available) // and store it under the "generic" URL (`views/dashboard.html`) $http.get(url, {cache: $templateCache}). success(function (tmpl) { $templateCache.put(facadeUrl, tmpl); }). error(function () { // Handle errors... $templateCache.put(facadeUrl, errorTmpl); }); }; // Initialize role and template this.logout(); }); // When the user navigates to ''/dashboard'', load the `views/dashboard.html` template. // In a real app, you should of course verify that the user is logged in etc... // (Here I use `ngRoute` for simplicity, but you can use any routing module.) app.config(function ($routeProvider) { $routeProvider. when(''/dashboard'', { templateUrl: ''views/dashboard.html'' }). ... });

Ver, también, esta breve demostración .
(Utilizo ngRoute por simplicidad, pero no hace ninguna diferencia ya que todo el trabajo lo realiza el servicio de User ).


No hay necesidad de una explicación larga aquí.

Utilice resolver y cambie $ route. $$ route.templateUrl, o use routeChangeError pasando la nueva ruta o parámetro relevante a la promesa.

var md = angular.module(''mymodule'', [''ngRoute'']); md.config(function($routeProvider, $locationProvider) { $routeProvider.when(''/common_route/:someparam'', { resolve: { nextRoute: function($q, $route, userService) { defer = $q.defer() userService.currentRole(function(data) { defer.reject({nextRoute: ''user_based_route/''+data) }); return defer.promise; } } }); $rootScope.$on("$routeChangeError", function(evt, current, previous, rejection) { if (rejection.route) { return $location.path(rejection.route).replace(); } }); });


Realmente no necesitas hacerlo con el enrutador.

Lo más simple es usar una única plantilla para todos los roles y usar ng-include dinámico dentro de ella. Supongamos que tiene un inyector en $ scope:

<div ng-include="injector.get(''session'').role+''_dashboard.html''"></div>

Por lo tanto, debe tener las vistas user_dashboard.html y admin_dashboard.html . Dentro de cada uno puede aplicar un controlador por separado, por ejemplo user_dashboard.html :

<div id="user_dashboard" ng-controller="UserDashboardCtrl"> User markup </div>


Sé que esto ha pasado un tiempo desde que se publicó esta pregunta, pero estoy agregando mi respuesta, ya que el método que uso es diferente de las otras respuestas aquí.

En este método, estoy separando por completo la ruta y las URL de la plantilla en función del rol de ese usuario y redirigiendo al usuario a la página de índice si se encuentran en una ruta que no están autorizados a ver.

Con UI Router, básicamente agrego un atributo de datos como este al estado:

.state(''admin'', { url: "/admin", templateUrl: "views/admin.html", data: { requireRole: ''admin'' } })

Cuando el usuario está autenticado, localstorage sus datos de rol en el localstorage y $rootscope desde el controlador de esta manera:

var role = JSON.stringify(response.data); // response from api with role details // Set the stringified user data into local storage localStorage.setItem(''role'', role); // Putting the user''s role on $rootScope for access by other controllers $rootScope.role = response.data;

Por último, uso $stateChangeStart para verificar el rol y redirigir al usuario si el usuario no debe ver la página:

.run([''$rootScope'', ''$state'', function($rootScope, $state) { // $stateChangeStart is fired whenever the state changes. We can use some parameters // such as toState to hook into details about the state as it is changing $rootScope.$on(''$stateChangeStart'', function(event, toState) { var role = JSON.parse(localStorage.getItem(''role'')); $rootScope.role = role; // Redirect user is NOT authenticated and accesing private pages var requireRole = toState.data !== undefined && toState.data.requireRole; if( (requireRole == ''admin'' && role != ''admin'')) ) { $state.go(''index''); event.preventDefault(); return; } } });

Además de lo anterior, aún necesitaría hacer una verificación de autorización del lado del servidor antes de mostrar cualquier información al usuario.


Si está utilizando una versión de angular mayor que 1.2, puede hacer una directiva con una plantillaUrl como función.

Por lo tanto, las ideas básicas es que tiene una vista de panel que tiene una directiva personalizada que determinará la plantilla en función del nivel de usuario. Entonces algo como esto:

(function () { ''use strict''; angular.module(''App.Directives'') .directive(''appDashboard'', [''UserManager'', function (UserManager) { return { restrict: ''EA'', templateUrl: function(ele, attr){ if (UserManager.currentUser.isAdmin){ return ''admin.html''; }else{ return ''user.html''; } } }; }]); })();


I. No use "... una sola ruta que cargue una plantilla diferente ..." , sería mi sugerencia, mi respuesta.

Si es posible:

Trata de dar un paso atrás y reconsiderar todo el diseño y
Intente debilitar la sensación de que a nuestros usuarios de aplicaciones les interesa la url .

Ellos no son. Y si realmente entienden qué es url , barra de direcciones ... lo usan para copy , send y paste ... no para investigar sus partes ...

II. Sugerencia: imponer el uso de los estados del ui-enrutador :

... UI-Router está organizado en torno a estados , que OPCIONALMENTE pueden tener rutas, así como otros comportamientos, adjuntos ...

Eso significa, reconsideremos nuestra aplicación como grupo / jerarquía de estados bien definidos. Pueden tener url definidas, pero no tienen que hacerlo (por ejemplo, estado de error sin url)

III. ¿Cómo podemos beneficiarnos al construir nuestra aplicación en los estados?

Separación de preocupación: debería ser nuestro objetivo.

El estado es una unidad que reúne algunas vistas / controladores , resolutores , datos personalizados ...

Eso significa que podría haber más estados reutilizando vistas , controladores , etc. Dichos estados podrían diferir realmente (la misma vista, diferentes controladores) . Pero tienen un único propósito: están ahí para manejar algunos escenarios:

  • administración del registro de Usuario / Emplyoee
  • lista de usuario / empleado - información ala Lista de teléfonos (solo correo electrónico, teléfono ...)
  • Administración de seguridad: cuáles son los derechos de un usuario ...

Y nuevamente, podría haber muchos muchos estados . Tener incluso cientos de estados no será un problema de rendimiento. Estas son solo definiciones, un conjunto de referencias a otras piezas, que deberían usarse ... más adelante ... si realmente se necesita.

Una vez que hemos definido casos de uso , historias de usuarios en el nivel del estado , podemos agruparlos en conjuntos / jerarquías.
Estos grupos podrían presentarse posteriormente en diferentes roles de usuario en un formato diferente (diferentes elementos de menú)

Pero al final ganamos mucha libertad y simplificamos el mantenimiento

IV. Mantenga la aplicación ejecutándose y creciendo

Si hay pocos estados, el mantenimiento no parece ser un problema. Pero podría suceder, que la aplicación tendrá éxito. Triunfa y crece ... dentro de su diseño.

La división de las definiciones de estado (como una unidad de trabajo) y sus jerarquías (qué función de usuario puede acceder a qué estados) simplificaría su gestión.

Aplicar la seguridad fuera de los estados (escuchas de eventos ala ''$stateChangeStart'' ) es mucho más fácil, y luego interminable refactorización de proveedores de plantillas. Además, la parte principal de la seguridad debe aplicarse aún en un servidor, independientemente de lo que la interfaz de usuario permita.

V. Resumen:

Si bien hay una característica tan genial como templateProvider , que podría hacer algunas cosas interesantes para nosotros (por ejemplo, aquí: Cambiar el menú de navegación utilizando UI-Router en AngularJs ) ...

... no deberíamos usarlo por seguridad. Eso podría implementarse como algún menú / jerarquía construida a partir de estados existentes, en función del Rol actual. Los oyentes de eventos deben verificar si el usuario está obteniendo el estado otorgado, pero la verificación principal debe aplicarse en un servidor ...