Routage dynamique AngularJS

88

J'ai actuellement une application AngularJS avec routage intégré. Cela fonctionne et tout va bien.

Mon fichier app.js ressemble à ceci:

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

Mon application dispose d'un CMS intégré dans lequel vous pouvez copier et ajouter de nouveaux fichiers html dans le répertoire / pages .

Je voudrais toujours passer par le fournisseur de routage même pour les nouveaux fichiers ajoutés dynamiquement.

Dans un monde idéal, le modèle de routage serait:

$ routeProvider.when ('/ nom de page ', {templateUrl: '/ pages / nom de page .html', contrôleur: CMSController});

Donc, si mon nouveau nom de page était "contact.html", j'aimerais que angular prenne "/ contact" et redirige vers "/pages/contact.html".

Est-ce seulement possible?! et si oui comment?!

Mettre à jour

J'ai maintenant ceci dans ma configuration de routage:

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

et dans mon CMSController:

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

Cela définit le templateUrl actuel sur la bonne valeur.

Cependant, je voudrais maintenant changer le ng-view avec la nouvelle valeur templateUrl. Comment cela est-il accompli?

Greg
la source

Réponses:

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'
        });
    }
]);
  • L'ajout * vous permet de travailler dynamiquement avec plusieurs niveaux de répertoires . Exemple: / page / cars / selling / list sera capturé sur ce fournisseur

À partir de la documentation (1.3.0):

"Si templateUrl est une fonction, elle sera appelée avec les paramètres suivants:

{Array.} - paramètres de route extraits du $ location.path () actuel en appliquant la route actuelle "

Aussi

when (chemin, route): Méthode

  • path peut contenir des groupes nommés commençant par deux-points et se terminant par une étoile: par exemple: nom *. Tous les caractères sont stockés avec empressement dans $ routeParams sous le nom donné lorsque l'itinéraire correspond.
Robin Rizvi
la source
5
Besoin d'évoluer avec le temps ... la fonctionnalité que j'ai construite qui manquait dans la v1.0.2 est maintenant disponible. Mise à jour de la réponse acceptée à celle-ci
Greg
Pour être honnête, je n'ai jamais fait fonctionner cela avec la version 1.3 bêta actuelle
Archimedes Trajano
En fait, cela fonctionnait quand j'ai fait ça ... quand ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano
Sérieusement .. C'est vraiment stupide qu'Angular ne l'ait pas comme comportement par défaut ... Ce routage manuel est ridicule
Guilherme Ferreira
3
Veuillez expliquer, d'où urlattrest défini ou envoyé, je veux dire ce urlattr.namequi produira?
Sami
37

Ok l'a résolu.

Ajout de la solution à GitHub - http://gregorypratt.github.com/AngularDynamicRouting

Dans ma configuration de routage app.js:

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

Puis dans mon contrôleur 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'];

Avec #views étant mon <div id="views" ng-view></div>

Alors maintenant, cela fonctionne avec le routage standard et le routage dynamique.

Pour le tester, j'ai copié about.html appelé portfolio.html, changé une partie de son contenu et entré /#/pages/portfoliodans mon navigateur et hé presto portfolio.html a été affiché ....

Mise à jour Ajout de $ apply et $ compile au html afin que le contenu dynamique puisse être injecté.

Greg
la source
2
Cela ne fonctionne qu'avec du contenu statique, si je comprends bien. C'est parce que vous modifiez le DOM après avoir instancié le contrôleur, via jQuery (en dehors de la portée d'angular). Des variables comme {{this}} peuvent fonctionner (je devrais l'essayer) mais les directives ne le seront probablement pas. Méfiez-vous de cela, car cela peut être utile maintenant, mais vous pouvez casser le code plus tard ..
Tiago Roldão
Certes, la liaison ne fonctionne pas, mais pour moi, je ne suis pas trop inquiet car je veux seulement que les clients puissent ajouter de nouvelles pages. Toutes les pages que j'ajoute je spécifierais de manière appropriée via la configuration de la route. Bon point cependant.
Greg
1
Ce n'est pas une mauvaise solution si vous souhaitez rendre du contenu html statique. Tant que vous gardez à l'esprit que vous remplacez essentiellement la vue ng avec un contenu statique (non angulaire). Dans cet esprit, il serait plus propre de séparer les deux, en créant quelque chose comme un élément <div id = "user-views"> </div>, et en y injectant votre "modèle utilisateur". De plus, il n'y a absolument aucun sens à surcharger $ route.current.templateUrl - créer un modèle $ scope.userTemplate et faire $ ('# user-views "). Load ($ scope.userTemplate) est plus propre et ne rompt aucun des angles angulaires. code.
Tiago Roldão
1
Non - la directive ng-view est plutôt limitée - ou mieux dit, elle est spécifique pour une utilisation avec le système de routage natif (qui est, à son tour, toujours limité, à mon humble avis). Vous pouvez faire comme vous l'avez dit, injecter le contenu dans un élément DOM, puis appeler la méthode $ compile, pour dire à angular de relire la structure. Cela déclenchera toutes les liaisons qui doivent être effectuées.
Tiago Roldão
2
Fonctionne, mais vous ne devriez pas faire de manipulation DOM dans votre contrôleur.
dmackerman
16

Je pense que le moyen le plus simple de faire une telle chose est de résoudre les routes plus tard, vous pouvez demander les routes via json, par exemple. Vérifiez que je crée une usine à partir de $ routeProvider pendant la phase de configuration, via $ provide, afin que je puisse continuer à utiliser l'objet $ routeProvider dans la phase d'exécution, et même dans les contrôleurs.

'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
la source
Les alias $routeProviderà utiliser en tant que factory(disponible après config) ne joueront pas bien avec la sécurité et la cohésion des applications - de toute façon, technique cool (vote positif!).
Cody
@Cody pouvez-vous expliquer la note de sécurité / cohésion de l'application? Nous sommes dans un bateau similaire et quelque chose comme la réponse ci-dessus serait vraiment pratique.
virtualandy
2
@virtualandy, ce n'est généralement pas une bonne approche car vous rendez un fournisseur disponible à travers différentes phases - ce qui à son tour peut fuir des utilitaires de haut niveau qui devraient être dissimulés dans la phase de configuration. Ma suggestion est d'employer UI-Router (s'occupe de beaucoup de difficultés), d'utiliser «résoudre», «controllerAs» et d'autres facultés comme la définition de templateUrl sur une fonction. J'ai également construit un module "presolve" que je peux partager qui peut $ interpoler n'importe quelle partie d'un schéma d'itinéraire (template, templateUrl, controller, etc.). La solution ci-dessus est plus élégante - mais quelles sont vos principales préoccupations?
Cody
1
@virtualandy, voici un module pour les modèles, les contrôleurs, etc. lazyLoaded - excuses ce n'est pas dans un dépôt, mais voici un violon: jsfiddle.net/cCarlson/zdm6gpnb ... Ce lazyLoads ce qui précède - ET - peut interpoler n'importe quoi basé sur les paramètres d'URL. Le module pourrait nécessiter du travail, mais c'est au moins un point de départ.
Cody
@Cody - pas de problème, merci pour l'échantillon et plus d'explications. Sens Makese. Dans notre cas, nous utilisons le segment angular-route-segment et cela a bien fonctionné. Mais nous devons maintenant prendre en charge les routes «dynamiques» (certains déploiements peuvent ne pas avoir toutes les fonctionnalités / routes de la page, ou peuvent changer leurs noms, etc.) et la notion de chargement dans certains JSON qui définit les routes avant de mettre en œuvre réellement était notre pensée mais a rencontré des problèmes avec les fournisseurs $ http / $ dans .config () évidemment.
virtualandy
7

Dans les patters URI $ routeProvider, vous pouvez spécifier des paramètres variables, comme ceci $routeProvider.when('/page/:pageNumber' ...:, et y accéder dans votre contrôleur via $ routeParams.

Il y a un bon exemple à la fin de la page $ route: http://docs.angularjs.org/api/ng.$route

EDIT (pour la question éditée):

Le système de routage est malheureusement très limité - il y a beaucoup de discussions sur ce sujet, et certaines solutions ont été proposées, notamment via la création de plusieurs vues nommées, etc. Mais pour l'instant, la directive ngView ne sert qu'une vue par route, sur sur une base individuelle. Vous pouvez procéder de plusieurs manières - la plus simple serait d'utiliser le modèle de la vue comme chargeur, avec une <ng-include src="myTemplateUrl"></ng-include>balise à l'intérieur ($ scope.myTemplateUrl serait créé dans le contrôleur).

J'utilise une solution plus complexe (mais plus propre, pour des problèmes plus grands et plus compliqués), en ignorant complètement le service $ route, qui est détaillée ici:

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

Tiago Roldão
la source
J'adore la ng-includeméthode. C'est vraiment simple et c'est une bien meilleure approche que de manipuler le DOM.
Neel
5

Je ne sais pas pourquoi cela fonctionne, mais des routes dynamiques (ou joker si vous préférez) sont possibles dans 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 -> charge "toto" partiel

example.com/bar-> charge partielle "bar"

Aucun ajustement nécessaire dans la vue ng. Le cas '/: a' est la seule variable que j'ai trouvée qui atteindra cela .. '/: foo' ne fonctionne pas à moins que vos partiels soient tous foo1, foo2, etc ... '/: a' fonctionne avec n'importe quel partiel Nom.

Toutes les valeurs déclenchent le contrôleur dynamique - il n'y a donc pas de «autrement», mais je pense que c'est ce que vous recherchez dans un scénario de routage dynamique ou générique.

Matthieu Luchak
la source
2

Depuis AngularJS 1.1.3, vous pouvez maintenant faire exactement ce que vous voulez en utilisant le nouveau paramètre fourre-tout.

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

Depuis le commit:

Cela permet à routeProvider d'accepter les paramètres qui correspondent aux sous-chaînes, même s'ils contiennent des barres obliques, s'ils sont précédés d'un astérisque au lieu de deux points. Par exemple, des itinéraires comme edit/color/:color/largecode/*largecode correspondent à quelque chose comme ceci http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Je l'ai testé moi-même (en utilisant 1.1.5) et cela fonctionne très bien. Gardez simplement à l'esprit que chaque nouvelle URL rechargera votre contrôleur, donc pour conserver tout type d'état, vous devrez peut-être utiliser un service personnalisé.

Dave
la source
0

Voici une autre solution qui fonctionne 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
la source