Roteamento dinâmico AngularJS

88

Atualmente, tenho um aplicativo AngularJS com roteamento integrado. Funciona e está tudo ok.

Meu arquivo app.js tem esta aparência:

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

Meu aplicativo tem um CMS integrado onde você pode copiar e adicionar novos arquivos html dentro do diretório / pages .

Eu gostaria ainda de passar pelo provedor de roteamento, embora até mesmo para os novos arquivos adicionados dinamicamente.

Em um mundo ideal, o padrão de roteamento seria:

$ routeProvider.when ('/ pagename ', {templateUrl: '/ pages / pagename .html', controlador: CMSController});

Portanto, se o nome da minha nova página fosse "contact.html", gostaria que o angular pegasse "/ contact" e redirecionasse para "/pages/contact.html".

Isso é mesmo possível ?! e se sim como ?!

Atualizar

Agora tenho isso em minha configuração de roteamento:

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

e no meu CMSController:

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

Isso define o templateUrl atual com o valor correto.

No entanto , agora gostaria de alterar o ng-view com o novo valor templateUrl. Como isso é feito?

Greg
fonte

Respostas:

132
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'
        });
    }
]);
  • Adicionar * permite trabalhar com vários níveis de diretórios dinamicamente. Exemplo: / page / cars / selling / list serão pegos neste provedor

Dos documentos (1.3.0):

"Se templateUrl for uma função, será chamada com os seguintes parâmetros:

{Array.} - parâmetros de rota extraídos do $ location.path () atual aplicando a rota atual "

Além disso

quando (caminho, rota): Método

  • path pode conter grupos nomeados começando com dois pontos e terminando com uma estrela: por exemplo: nome *. Todos os caracteres são armazenados em $ routeParams com o nome fornecido quando a rota corresponde.
Robin Rizvi
fonte
5
Precisa mudar com o tempo ... a funcionalidade que eu construí que faltava na v1.0.2 já está disponível. Atualizando a resposta aceita para esta
Greg
Para ser sincero, nunca consegui funcionar com os 1.3 betas atuais
Archimedes Trajano
Na verdade, ele estava funcionando quando fiz isso ... when ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano
Sério .. É realmente estúpido que o Angular não tenha isso como comportamento padrão ... Esse roteamento manual é ridículo
Guilherme Ferreira
3
Por favor, explique de onde urlattré definido ou enviado, quero dizer, o que urlattr.namevai produzir?
Sami
37

Ok resolvi.

Adicionada a solução ao GitHub - http://gregorypratt.github.com/AngularDynamicRouting

Na minha configuração de roteamento app.js:

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

Então, no meu 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'];

Com #views sendo meu <div id="views" ng-view></div>

Agora ele funciona com roteamento padrão e roteamento dinâmico.

Para testá-lo, copiei about.html chamei-o de portfolio.html, mudei alguns de seus conteúdos e entrei /#/pages/portfolioem meu navegador e ei presto portfolio.html foi exibido ....

Atualizado Adicionado $ apply e $ compile ao html para que o conteúdo dinâmico possa ser injetado.

Greg
fonte
2
Isso funciona apenas com conteúdo estático, se bem entendi. Isso ocorre porque você está alterando o DOM depois de instanciar o controlador, via jQuery (fora do escopo do angular). Variáveis ​​como {{this}} podem funcionar (eu teria que tentar), mas as diretivas provavelmente não. Fique atento, pois pode ser útil agora, mas pode quebrar o código depois ..
Tiago Roldão
É verdade que a vinculação não funciona, para mim, no entanto, não estou muito preocupado, pois só quero que os clientes possam adicionar novas páginas. Qualquer página que eu adicionar, eu especificaria apropriadamente por meio da configuração da rota. Bom ponto, entretanto.
Greg
1
Não é uma solução ruim se você deseja renderizar conteúdo html estático. Contanto que você tenha em mente, você está essencialmente substituindo a visualização ng com conteúdo estático (não angular). Com isso em mente, seria mais limpo separar os dois, criando algo como um elemento <div id = "user-views"> </div> e injetando seu "template de usuário" lá. Além disso, não há absolutamente nenhum sentido em substituir $ route.current.templateUrl - criar um modelo $ scope.userTemplate e fazer $ ('# user-views "). Load ($ scope.userTemplate) é mais limpo e não quebra nenhum do angular código.
Tiago Roldão
1
Não - a diretiva ng-view é bastante limitada - ou melhor, é específica para uso com o sistema de roteamento nativo (que, por sua vez, ainda é limitado, imho). Você pode fazer como disse, injetando o conteúdo em um elemento DOM e, em seguida, chamar o método $ compile para dizer ao angular para reler a estrutura. Isso acionará quaisquer ligações que precisem ser feitas.
Tiago Roldão
2
Funciona, mas você não deve fazer manipulação de DOM em seu controlador.
dmackerman
16

Acho que a maneira mais fácil de fazer isso é resolver as rotas depois, você poderia perguntar as rotas via json, por exemplo. Verifique se eu faço uma fábrica do $ routeProvider durante a fase de configuração, via $ fornecer, para que eu possa continuar usando o objeto $ routeProvider na fase de execução e até mesmo nos 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
fonte
O aliasing $routeProviderpara usar como factory(disponível depois config) não funcionará bem com a segurança e a coesão do aplicativo - de qualquer forma, é uma técnica legal (upvote!).
Cody
@Cody, você pode explicar a nota de segurança / coesão do aplicativo? Estamos em um barco semelhante e algo como a resposta acima seria muito útil.
virtualandy
2
@virtualandy, geralmente não é uma ótima abordagem, já que você está disponibilizando um provedor em diferentes fases - o que, por sua vez, pode vazar utilitários de alto nível que deveriam ser ocultados na fase de configuração. Minha sugestão é empregar UI-Router (cuida de muitas dificuldades), usar "resolve", "controllerAs" e outras faculdades como definir templateUrl para uma função. Eu também construí um módulo de "pré-resolução" que posso compartilhar que pode $ interpolar qualquer parte de um esquema de rota (template, templateUrl, controlador, etc). A solução acima é mais elegante - mas quais são suas principais preocupações?
Cody
1
@virtualandy, aqui está um módulo para modelos lazyLoaded, controladores, etc - desculpas, não está em um repo, mas aqui está um violino: jsfiddle.net/cCarlson/zdm6gpnb ... Este lazyLoads o acima - AND - pode interpolar qualquer coisa com base nos parâmetros de URL. O módulo pode precisar de algum trabalho, mas é pelo menos um ponto de partida.
Cody
@Cody - sem problemas, obrigado pela amostra e maiores explicações. Sentido Makese. Em nosso caso, estamos usando um segmento de rota angular e funcionou muito bem. Mas agora precisamos oferecer suporte a rotas "dinâmicas" (algumas implantações podem não ter todos os recursos / rotas da página, ou podem mudar seus nomes, etc) e a noção de carregamento em algum JSON que define rotas antes de realmente implementar foi nosso pensamento mas teve problemas ao usar $ http / $ Providers em .config () obviamente.
virtualandy
7

Nos padrões de URI $ routeProvider, você pode especificar parâmetros de variáveis, como $routeProvider.when('/page/:pageNumber' ...:, e acessá-los em seu controlador via $ routeParams.

Há um bom exemplo no final da página $ route: http://docs.angularjs.org/api/ng.$route

EDITAR (para a questão editada):

O sistema de roteamento é, infelizmente, muito limitado - há muita discussão sobre este assunto, e algumas soluções foram propostas, nomeadamente através da criação de múltiplas visualizações nomeadas, etc. Mas, agora, a diretiva ngView serve apenas uma visualização por rota, em uma base de um para um. Você pode fazer isso de várias maneiras - a mais simples seria usar o modelo da visão como um carregador, com uma <ng-include src="myTemplateUrl"></ng-include>tag nele ($ scope.myTemplateUrl seria criado no controlador).

Eu uso uma solução mais complexa (mas mais limpa, para problemas maiores e mais complicados), basicamente ignorando o serviço $ route completamente, que é detalhado aqui:

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

Tiago Roldão
fonte
Eu amo o ng-includemétodo. É realmente simples e é uma abordagem muito melhor do que manipular o DOM.
Neel
5

Não sei por que isso funciona, mas rotas dinâmicas (ou curinga, se você preferir) são possíveis no 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 -> carrega "foo" parcial

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

Não há necessidade de ajustes na visualização do ng. O caso '/: a' é a única variável que descobri que conseguirá isso .. '/: foo' não funciona a menos que seus parciais sejam todos foo1, foo2, etc ... '/: a' funciona com qualquer parcial nome.

Todos os valores disparam o controlador dinâmico - portanto, não há "caso contrário", mas acho que é o que você está procurando em um cenário de roteamento dinâmico ou curinga.

Matthew Luchak
fonte
2

A partir do AngularJS 1.1.3, agora você pode fazer exatamente o que quiser usando o novo parâmetro pega-tudo.

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

Do commit:

Isso permite que routeProvider aceite parâmetros que correspondem a substrings mesmo quando contêm barras, se forem prefixados com um asterisco em vez de dois-pontos. Por exemplo, rotas como edit/color/:color/largecode/*largecode corresponderão a algo assim http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Eu mesmo testei (usando 1.1.5) e funciona muito bem. Lembre-se de que cada novo URL recarregará seu controlador, portanto, para manter qualquer tipo de estado, pode ser necessário usar um serviço personalizado.

Dave
fonte
0

Aqui está outra solução que funciona bem.

(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
fonte