AngularJS dynamisches Routing

88

Ich habe derzeit eine AngularJS-Anwendung mit integriertem Routing. Sie funktioniert und alles ist in Ordnung.

Meine app.js-Datei sieht folgendermaßen aus:

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

In meiner App ist ein CMS integriert, mit dem Sie neue HTML-Dateien im Verzeichnis / pages kopieren und hinzufügen können .

Ich möchte trotzdem den Routing-Anbieter durchgehen, auch für die neuen dynamisch hinzugefügten Dateien.

In einer idealen Welt wäre das Routing-Muster:

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

Wenn mein neuer Seitenname "contact.html" wäre, möchte ich, dass Angular "/ contact" aufnimmt und zu "/pages/contact.html" umleitet.

Ist das überhaupt möglich?! und wenn ja wie?!

Aktualisieren

Ich habe dies jetzt in meiner Routing-Konfiguration:

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

und in meinem CMSController:

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

Dadurch wird die aktuelle templateUrl auf den richtigen Wert gesetzt.

Aber ich würde jetzt wie das ändern ng-Ansicht mit dem neuen templateUrl Wert. Wie wird das erreicht?

Greg
quelle

Antworten:

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'
        });
    }
]);
  • Durch Hinzufügen von * können Sie mit mehreren Verzeichnisebenen arbeiten dynamisch . Beispiel: / page / cars / seller / list wird von diesem Anbieter erfasst

Aus den Dokumenten (1.3.0):

"Wenn templateUrl eine Funktion ist, wird sie mit den folgenden Parametern aufgerufen:

{Array.} - Routenparameter, die aus dem aktuellen $ location.path () durch Anwenden der aktuellen Route extrahiert wurden "

Ebenfalls

Wann (Pfad, Route): Methode

  • Der Pfad kann benannte Gruppen enthalten, die mit einem Doppelpunkt beginnen und mit einem Stern enden: zB: Name *. Alle Zeichen werden eifrig in $ routeParams unter dem angegebenen Namen gespeichert, wenn die Route übereinstimmt.
Robin Rizvi
quelle
5
Müssen mit der Zeit gehen ... Die Funktionalität, die ich erstellt habe und die in Version 1.0.2 fehlte, ist jetzt verfügbar. Aktualisierung der akzeptierten Antwort auf diese Frage
Greg
Um ehrlich zu sein, habe ich das mit den aktuellen 1.3 Betas nie geschafft
Archimedes Trajano
Als ich das tat, funktionierte es tatsächlich ... als ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano
Ernsthaft .. Es ist wirklich dumm, dass Angular es nicht als Standardverhalten hat ... Das manuelle Routing ist lächerlich
Guilherme Ferreira
3
Bitte erläutern Sie, woher urlattrkommt oder gesendet wird, ich meine, was urlattr.namewird produziert?
Sami
37

Ok hat es gelöst.

Die Lösung wurde zu GitHub hinzugefügt - http://gregorypratt.github.com/AngularDynamicRouting

In meiner App.js Routing-Konfiguration:

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

Dann in meinem CMS-Controller:

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

Mit #views als mein <div id="views" ng-view></div>

Jetzt funktioniert es mit Standard-Routing und dynamischem Routing.

Um es zu testen, habe ich about.html kopiert, es Portfolio.html genannt, einige seiner Inhalte geändert und /#/pages/portfolioin meinen Browser eingegeben, und hey presto Portfolio.html wurde angezeigt ....

Aktualisiert $ apply und $ compile wurden zum HTML hinzugefügt, damit dynamischer Inhalt eingefügt werden kann.

Greg
quelle
2
Dies funktioniert nur mit statischen Inhalten, wenn ich es richtig verstehe. Dies liegt daran, dass Sie das DOM ändern, nachdem Sie den Controller über jQuery (außerhalb des Bereichs von Angular) instanziiert haben. Variablen wie {{this}} funktionieren möglicherweise (ich müsste es versuchen), Direktiven jedoch höchstwahrscheinlich nicht. Seien Sie vorsichtig, da es jetzt nützlich sein kann, aber später den Code brechen kann.
Tiago Roldão
Das Binden funktioniert zwar nicht, aber ich bin nicht zu aufgeregt, da ich nur möchte, dass Kunden neue Seiten hinzufügen können. Alle Seiten, die ich hinzufüge, würde ich über die Routenkonfiguration entsprechend angeben. Guter Punkt.
Greg
1
Es ist keine schlechte Lösung, wenn Sie statischen HTML-Inhalt rendern möchten. Solange Sie bedenken, überschreiben Sie die ng-Ansicht im Wesentlichen mit statischem (nicht eckigem) Inhalt. In diesem Sinne wäre es sauberer, die beiden zu trennen, so etwas wie ein <div id = "user-views"> </ div> -Element zu erstellen und dort Ihre "Benutzervorlage" einzufügen. Außerdem macht es absolut keinen Sinn, $ route.current.templateUrl zu überschreiben - ein $ scope.userTemplate-Modell zu erstellen und $ ('# user-views ") auszuführen. Load ($ scope.userTemplate) ist sauberer und unterbricht keine Winkel Code.
Tiago Roldão
1
Nein - die ng-view-Direktive ist eher begrenzt - oder besser gesagt, sie ist spezifisch für die Verwendung mit dem nativen Routing-System (das wiederum imho immer noch begrenzt ist). Sie können wie gesagt vorgehen, indem Sie den Inhalt in ein DOM-Element einfügen und dann die $ compile-Methode aufrufen, um angle anzuweisen, die Struktur erneut zu lesen. Dadurch werden alle erforderlichen Bindungen ausgelöst.
Tiago Roldão
2
Funktioniert, aber Sie sollten keine DOM-Manipulation in Ihrem Controller durchführen.
Dmackerman
16

Ich denke, der einfachste Weg, dies zu tun, besteht darin, die Routen später aufzulösen. Sie könnten die Routen beispielsweise über json fragen. Überprüfen Sie, ob ich aus dem $ routeProvider während der Konfigurationsphase eine Factory über $ deploy mache, damit ich das $ routeProvider-Objekt in der Ausführungsphase und sogar in Controllern weiterhin verwenden kann.

'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
quelle
Aliasing $routeProviderals factory(verfügbar nach config) Aliasing spielt nicht gut mit Sicherheit und App-Zusammenhalt - so oder so, coole Technik (Upvote!).
Cody
@Cody Kannst du den Sicherheits- / App-Kohäsionshinweis erklären? Wir sind in einem ähnlichen Boot und so etwas wie die obige Antwort wäre wirklich praktisch.
Virtualandy
2
@virtualandy, dies ist im Allgemeinen kein guter Ansatz, da Sie einen Anbieter über verschiedene Phasen hinweg verfügbar machen. Dies kann wiederum dazu führen, dass hochrangige Dienstprogramme verloren gehen, die in der Konfigurationsphase verborgen bleiben sollten. Mein Vorschlag ist, UI-Router zu verwenden (kümmert sich um viele Schwierigkeiten), "Auflösen", "ControllerAs" und andere Funktionen wie das Setzen von templateUrl auf eine Funktion zu verwenden. Ich habe auch ein "presolve" -Modul erstellt, das ich freigeben kann und das jeden Teil eines Routenschemas (Vorlage, templateUrl, Controller usw.) interpolieren kann. Die obige Lösung ist eleganter - aber was sind Ihre Hauptanliegen?
Cody
1
@virtualandy, hier ist ein Modul für LazyLoaded-Vorlagen, Controller usw. - Entschuldigung, es ist nicht in einem Repo, aber hier ist eine Geige: jsfiddle.net/cCarlson/zdm6gpnb ... Dieses LazyLoads kann die oben genannten - UND - alles interpolieren basierend auf den URL-Parametern. Das Modul könnte etwas Arbeit gebrauchen, aber es ist zumindest ein Ausgangspunkt.
Cody
@Cody - kein Problem, danke für das Beispiel und die weitere Erklärung. Sinn machen. In unserem Fall verwenden wir Winkel-Route-Segment und es ist schön gearbeitet. Aber wir müssen jetzt "dynamische" Routen unterstützen (einige Bereitstellungen verfügen möglicherweise nicht über die vollständigen Funktionen / Routen der Seite oder ändern möglicherweise ihre Namen usw.), und der Gedanke, in einige JSONs zu laden, die Routen definieren, bevor sie tatsächlich implementiert werden, war unser Gedanke Es gab jedoch offensichtlich Probleme mit der Verwendung von $ http / $ -Anbietern in .config ().
Virtualandy
7

In den URI-Mustern von $ routeProvider können Sie variable Parameter wie folgt angeben: $routeProvider.when('/page/:pageNumber' ...und über $ routeParams in Ihrem Controller darauf zugreifen.

Am Ende der $ route-Seite finden Sie ein gutes Beispiel: http://docs.angularjs.org/api/ng.$route

BEARBEITEN (für die bearbeitete Frage):

Das Routing-System ist leider sehr begrenzt - es wird viel über dieses Thema diskutiert, und es wurden einige Lösungen vorgeschlagen, nämlich das Erstellen mehrerer benannter Ansichten usw. Derzeit dient die ngView-Direktive jedoch nur EINER Ansicht pro Route eine Eins-zu-Eins-Basis. Sie können dies auf verschiedene Arten <ng-include src="myTemplateUrl"></ng-include>tun - am einfachsten wäre es, die Vorlage der Ansicht als Loader mit einem Tag darin zu verwenden ($ scope.myTemplateUrl würde im Controller erstellt).

Ich verwende eine komplexere (aber sauberere, für größere und kompliziertere Probleme) Lösung, bei der der $ route-Dienst insgesamt übersprungen wird. Dies wird hier beschrieben:

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

Tiago Roldão
quelle
Ich liebe die ng-includeMethode. Es ist wirklich einfach und dies ist ein viel besserer Ansatz als die Manipulation des DOM.
Neel
5

Ich bin mir nicht sicher, warum dies funktioniert, aber dynamische (oder Platzhalter, wenn Sie es vorziehen) Routen sind in Winkel 1.2.0-rc.2 möglich ...

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 -> lädt "foo" teilweise

example.com/bar-> lädt "bar" teilweise

Keine Anpassungen in der ng-Ansicht erforderlich. Der Fall '/: a' ist die einzige Variable, die ich gefunden habe, die dies erreicht. '/: Foo' funktioniert nur, wenn Ihre Partials alle foo1, foo2 usw. sind. '/: A' funktioniert mit jedem Partial Name.

Alle Werte lösen den dynamischen Controller aus - es gibt also kein "Sonstiges", aber ich denke, es ist das, wonach Sie in einem dynamischen oder Wildcard-Routing-Szenario suchen.

Matthew Luchak
quelle
2

Ab AngularJS 1.1.3 können Sie jetzt mit dem neuen Catch-All-Parameter genau das tun, was Sie wollen.

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

Aus dem Commit:

Auf diese Weise kann routeProvider Parameter akzeptieren, die mit Teilzeichenfolgen übereinstimmen, auch wenn sie Schrägstriche enthalten, wenn ihnen anstelle eines Doppelpunkts ein Sternchen vorangestellt ist. Zum Beispiel edit/color/:color/largecode/*largecode stimmen Routen wie diese mit so etwas überein http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Ich habe es selbst getestet (mit 1.1.5) und es funktioniert großartig. Denken Sie daran, dass jede neue URL Ihren Controller neu lädt. Um also jeden Status beizubehalten, müssen Sie möglicherweise einen benutzerdefinierten Dienst verwenden.

Dave
quelle
0

Hier ist eine andere Lösung, die gut funktioniert.

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