Enrutamiento dinámico AngularJS

88

Actualmente tengo una aplicación AngularJS con enrutamiento integrado. Funciona y todo está bien.

Mi archivo app.js se ve así:

angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
  config(['$routeProvider', function ($routeProvider) {
      $routeProvider.when('/', { templateUrl: '/pages/home.html', controller: HomeController });
      $routeProvider.when('/about', { templateUrl: '/pages/about.html', controller: AboutController });
      $routeProvider.when('/privacy', { templateUrl: '/pages/privacy.html', controller: AboutController });
      $routeProvider.when('/terms', { templateUrl: '/pages/terms.html', controller: AboutController });
      $routeProvider.otherwise({ redirectTo: '/' });
  }]);

Mi aplicación tiene un CMS integrado donde puede copiar y agregar nuevos archivos html dentro del directorio / pages .

Me gustaría seguir pasando por el proveedor de enrutamiento, incluso para los nuevos archivos agregados dinámicamente.

En un mundo ideal, el patrón de enrutamiento sería:

$ routeProvider.when ( '/ nombre de la página ', {templateUrl: '/ pages / nombre de la página html', el controlador: CMSController});

Entonces, si mi nuevo nombre de página era "contact.html", me gustaría que angular seleccionara "/ contact" y lo redireccionara a "/pages/contact.html".

¿Es esto siquiera posible? y si es así, ¿cómo?

Actualizar

Ahora tengo esto en mi configuración de enrutamiento:

$routeProvider.when('/page/:name', { templateUrl: '/pages/home.html', controller: CMSController })

y en mi CMSController:

function CMSController($scope, $route, $routeParams) {
    $route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
    alert($route.current.templateUrl);
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];

Esto establece el templateUrl actual en el valor correcto.

Sin embargo , ahora me gustaría cambiar la vista ng con el nuevo valor templateUrl. ¿Cómo se logra esto?

Greg
fuente

Respuestas:

133
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
        config(['$routeProvider', function($routeProvider) {
        $routeProvider.when('/page/:name*', {
            templateUrl: function(urlattr){
                return '/pages/' + urlattr.name + '.html';
            },
            controller: 'CMSController'
        });
    }
]);
  • Agregar * le permite trabajar con múltiples niveles de directorios de forma dinámica. Ejemplo: / page / cars / selling / list será capturado en este proveedor

De los documentos (1.3.0):

"Si templateUrl es una función, se llamará con los siguientes parámetros:

{Array.} - parámetros de ruta extraídos del $ location.path () actual aplicando la ruta actual "

también

cuando (ruta, ruta): Método

  • La ruta puede contener grupos con nombre que comienzan con dos puntos y terminan con una estrella: por ejemplo: nombre *. Todos los caracteres se almacenan con entusiasmo en $ routeParams con el nombre de pila cuando la ruta coincide.
Robin Rizvi
fuente
5
Necesito moverme con los tiempos ... la funcionalidad que construí que faltaba en v1.0.2 ya está disponible. Actualizando la respuesta aceptada a esta
Greg
Para ser honesto, nunca logré que esto funcionara con las versiones beta 1.3 actuales
Archimedes Trajano
En realidad lo hice funcionar cuando hice esto ... cuando ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano
En serio ... Es realmente estúpido que Angular no lo tenga como comportamiento predeterminado ... Ese enrutamiento manual es ridículo
Guilherme Ferreira
3
Por favor explique, ¿de dónde urlattrse establece o se envía, me refiero a qué urlattr.nameproducirá?
Sami
37

Ok, lo resolvió.

Se agregó la solución a GitHub : http://gregorypratt.github.com/AngularDynamicRouting

En mi configuración de enrutamiento app.js:

$routeProvider.when('/pages/:name', {
    templateUrl: '/pages/home.html', 
    controller: CMSController 
});

Luego, en mi controlador CMS:

function CMSController($scope, $route, $routeParams) {

    $route.current.templateUrl = '/pages/' + $routeParams.name + ".html";

    $.get($route.current.templateUrl, function (data) {
        $scope.$apply(function () {
            $('#views').html($compile(data)($scope));
        });
    });
    ...
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];

Con #views siendo mi <div id="views" ng-view></div>

Así que ahora funciona con enrutamiento estándar y enrutamiento dinámico.

Para probarlo, copié about.html lo llamé portfolio.html, cambié algunos de sus contenidos y entré /#/pages/portfolioen mi navegador y hey presto portfolio.html se mostró ....

Actualizado Se agregó $ apply y $ compile al html para que se pueda inyectar contenido dinámico.

Greg
fuente
2
Esto solo funciona con contenido estático, si lo entiendo correctamente. Esto se debe a que está alterando el DOM después de haber creado una instancia del controlador, a través de jQuery (fuera del alcance de angular). Variables como {{this}} pueden funcionar (tendría que probarlo) pero las directivas probablemente no lo harán. Tenga cuidado con esto, ya que puede ser útil ahora, pero puede romper el código más tarde ..
Tiago Roldão
Es cierto que la encuadernación no funciona, para mí, sin embargo, no estoy demasiado preocupado porque solo quiero que los clientes puedan agregar nuevas páginas. Cualquier página que agregue, la especificaría apropiadamente a través de la configuración de ruta. Aunque es un buen punto.
Greg
1
No es una mala solución si desea renderizar contenido html estático. Siempre que tenga en cuenta, esencialmente está anulando la vista ng con contenido estático (no angular). Con eso en mente, sería más limpio separar los dos, creando algo como un elemento <div id = "user-views"> </div> e inyectando su "plantilla de usuario" allí. Además, no tiene absolutamente ningún sentido anular $ route.current.templateUrl: crear un modelo $ scope.userTemplate y hacer $ ('# user-views "). Load ($ scope.userTemplate) es más limpio y no rompe ninguno de los angulares código
Tiago Roldão
1
No, la directiva ng-view es bastante limitada, o mejor dicho, es específica para su uso con el sistema de enrutamiento nativo (que, a su vez, sigue siendo limitado, en mi humilde opinión). Puede hacer lo que dijo, inyectando el contenido en un elemento DOM y luego llamar al método $ compile, para decirle a angular que vuelva a leer la estructura. Eso activará cualquier enlace que deba hacerse.
Tiago Roldão
2
Funciona, pero no deberías manipular DOM en tu controlador.
dmackerman
16

Creo que la forma más fácil de hacer tal cosa es resolver las rutas más tarde, podría preguntar las rutas a través de json, por ejemplo. Compruebe que hago una fábrica a partir de $ routeProvider durante la fase de configuración, a través de $ provide, para poder seguir usando el objeto $ routeProvider en la fase de ejecución, e incluso en los controladores.

'use strict';

angular.module('myapp', []).config(function($provide, $routeProvider) {
    $provide.factory('$routeProvider', function () {
        return $routeProvider;
    });
}).run(function($routeProvider, $http) {
    $routeProvider.when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
    }).otherwise({
        redirectTo: '/'
    });

    $http.get('/dynamic-routes.json').success(function(data) {
        $routeProvider.when('/', {
            templateUrl: 'views/main.html',
            controller: 'MainCtrl'
        });
        // you might need to call $route.reload() if the route changed
        $route.reload();
    });
});
eazel7
fuente
El $routeProvideruso de alias para usar como factory(disponible después config) no funcionará bien con la seguridad y la cohesión de la aplicación, de cualquier manera, técnica genial (¡voto a favor!).
Cody
@Cody, ¿puedes explicar la nota de cohesión de seguridad / aplicación? Estamos en un barco similar y algo como la respuesta anterior sería realmente útil.
virtualandy
2
@virtualandy, generalmente no es un gran enfoque, ya que hace que un proveedor esté disponible en diferentes fases, lo que a su vez puede filtrar utilidades de alto nivel que deberían ocultarse dentro de la fase de configuración. Mi sugerencia es emplear UI-Router (se ocupa de muchas dificultades), usar "resolve", "controllerAs" y otras facultades como configurar templateUrl en una función. También construí un módulo de "resolución previa" que puedo compartir y que puede $ interpolar cualquier parte de un esquema de ruta (plantilla, templateUrl, controlador, etc.). La solución anterior es más elegante, pero ¿cuáles son sus principales preocupaciones?
Cody
1
@virtualandy, aquí hay un módulo para plantillas, controladores, etc. lazyLoaded; disculpas, no está en un repositorio, pero aquí hay un violín: jsfiddle.net/cCarlson/zdm6gpnb ... Este lazyLoads lo anterior - Y - puede interpolar cualquier cosa basado en los parámetros de URL. El módulo podría necesitar algo de trabajo, pero es al menos un punto de partida.
Cody
@Cody: no hay problema, gracias por la muestra y una explicación adicional. Tiene sentido. En nuestro caso, estamos usando segmento de ruta angular y ha funcionado muy bien. Pero ahora necesitamos admitir rutas "dinámicas" (algunas implementaciones pueden no tener las características / rutas completas de la página, o pueden cambiar sus nombres, etc.) y pensamos en la noción de cargar en algún JSON que define las rutas antes de implementarlas. pero tuve problemas al usar $ http / $ proveedores dentro de .config () obviamente.
virtualandy
7

En los patrones de URI $ routeProvider, puede especificar parámetros variables, así: $routeProvider.when('/page/:pageNumber' ...y acceder a ellos en su controlador a través de $ routeParams.

Hay un buen ejemplo al final de la página $ route: http://docs.angularjs.org/api/ng.$route

EDITAR (para la pregunta editada):

Desafortunadamente, el sistema de enrutamiento es muy limitado: hay mucha discusión sobre este tema y se han propuesto algunas soluciones, a saber, mediante la creación de múltiples vistas con nombre, etc. Pero en este momento, la directiva ngView solo sirve UNA vista por ruta, en una base uno a uno. Puede hacer esto de varias maneras: la más simple sería usar la plantilla de la vista como cargador, con una <ng-include src="myTemplateUrl"></ng-include>etiqueta en ella (se crearía $ scope.myTemplateUrl en el controlador).

Utilizo una solución más compleja (pero más limpia, para problemas más grandes y complicados), básicamente omitiendo el servicio $ route por completo, que se detalla aquí:

http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm

Tiago Roldão
fuente
Amo el ng-includemetodo Es realmente simple y este es un enfoque mucho mejor que manipular el DOM.
Neel
5

No estoy seguro de por qué esto funciona, pero las rutas dinámicas (o comodines si lo prefiere) son posibles en angular 1.2.0-rc.2 ...

http://code.angularjs.org/1.2.0-rc.2/angular.min.js
http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js

angular.module('yadda', [
  'ngRoute'
]).

config(function ($routeProvider, $locationProvider) {
  $routeProvider.
    when('/:a', {
  template: '<div ng-include="templateUrl">Loading...</div>',
  controller: 'DynamicController'
}).


controller('DynamicController', function ($scope, $routeParams) {
console.log($routeParams);
$scope.templateUrl = 'partials/' + $routeParams.a;
}).

example.com/foo -> carga "foo" parcial

example.com/bar-> carga "barra" parcial

No es necesario realizar ningún ajuste en la vista ng. El caso '/: a' es la única variable que he encontrado que logrará esto .. '/: foo' no funciona a menos que sus parciales sean todos foo1, foo2, etc. nombre.

Todos los valores activan el controlador dinámico, por lo que no hay "lo contrario", pero creo que es lo que está buscando en un escenario de enrutamiento dinámico o comodín.

Matthew Luchak
fuente
2

A partir de AngularJS 1.1.3, ahora puede hacer exactamente lo que quiera usando el nuevo parámetro catch-all.

https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5

Desde el compromiso:

Esto permite que routeProvider acepte parámetros que coincidan con subcadenas incluso cuando contengan barras inclinadas si tienen el prefijo de asterisco en lugar de dos puntos. Por ejemplo, rutas como edit/color/:color/largecode/*largecode coincidirán con algo como esto http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Lo he probado yo mismo (usando 1.1.5) y funciona muy bien. Solo tenga en cuenta que cada nueva URL recargará su controlador, por lo que para mantener cualquier tipo de estado, es posible que deba usar un servicio personalizado.

Dave
fuente
0

Aquí hay otra solución que funciona bien.

(function() {
    'use strict';

    angular.module('cms').config(route);
    route.$inject = ['$routeProvider'];

    function route($routeProvider) {

        $routeProvider
            .when('/:section', {
                templateUrl: buildPath
            })
            .when('/:section/:page', {
                templateUrl: buildPath
            })
            .when('/:section/:page/:task', {
                templateUrl: buildPath
            });



    }

    function buildPath(path) {

        var layout = 'layout';

        angular.forEach(path, function(value) {

            value = value.charAt(0).toUpperCase() + value.substring(1);
            layout += value;

        });

        layout += '.tpl';

        return 'client/app/layouts/' + layout;

    }

})();
Kevinius
fuente