Routing dynamiczny AngularJS

88

Obecnie mam aplikację AngularJS z wbudowanym routingiem. Działa i wszystko jest w porządku.

Mój plik app.js wygląda następująco:

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

Moja aplikacja ma wbudowany CMS, w którym możesz kopiować i dodawać nowe pliki html w katalogu / pages .

Chciałbym nadal przechodzić przez dostawcę routingu, chociaż nawet dla nowych dynamicznie dodawanych plików.

W idealnym świecie wzorzec routingu wyglądałby tak:

$ routeProvider.when ('/ nazwa strony ', {templateUrl: '/ strony / nazwa strony .html', kontroler: CMSController});

Jeśli więc moja nowa nazwa strony brzmiała „contact.html”, chciałbym, aby angular odebrał „/ contact” i przekierował do „/pages/contact.html”.

Czy to w ogóle możliwe ?! a jeśli tak, to jak ?!

Aktualizacja

Mam teraz to w mojej konfiguracji routingu:

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

aw moim CMSController:

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

Spowoduje to ustawienie bieżącego templateUrl na właściwą wartość.

Jednak chciałbym teraz zmienić widok ng z nową wartością templateUrl. Jak to się robi?

Greg
źródło

Odpowiedzi:

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'
        });
    }
]);
  • Dodanie * umożliwia dynamiczną pracę z wieloma poziomami katalogów . Przykład: / page / cars / selling / list zostanie złapana na tym dostawcy

Z dokumentów (1.3.0):

„Jeśli templateUrl jest funkcją, zostanie wywołana z następującymi parametrami:

{Array.} - parametry trasy wyodrębnione z bieżącej $ location.path () przez zastosowanie bieżącej trasy "

Również

kiedy (ścieżka, trasa): Metoda

  • ścieżka może zawierać nazwane grupy zaczynające się od dwukropka i kończące się gwiazdką: np. nazwa *. Wszystkie znaki są chętnie przechowywane w $ routeParams pod podaną nazwą, gdy trasa pasuje.
Robin Rizvi
źródło
5
Muszę iść z duchem czasu ... funkcjonalność, której brakowało w wersji 1.0.2, jest już dostępna. Aktualizacja zaakceptowanej odpowiedzi do tej
Greg
Szczerze mówiąc, nigdy nie działało to z obecnymi wersjami beta 1.3
Archimedes Trajano.
Właściwie to działało, kiedy to zrobiłem ... kiedy ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano
Poważnie ... To naprawdę głupie, że Angular nie ma tego jako domyślnego zachowania ... To ręczne trasowanie jest śmieszne
Guilherme Ferreira
3
Proszę wyjaśnić, skąd urlattrjest ustawione lub wysłane, mam na myśli, co urlattr.namewyprodukuje?
Sami
37

Ok, rozwiązałem to.

Dodano rozwiązanie do GitHub - http://gregorypratt.github.com/AngularDynamicRouting

W mojej konfiguracji routingu app.js:

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

Następnie w moim kontrolerze 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'];

Z #views to my <div id="views" ng-view></div>

Więc teraz działa z routingiem standardowym i routingiem dynamicznym.

Aby to przetestować, skopiowałem plik about.html nazwałem go portfolio.html, zmieniłem część jego zawartości i wprowadziłem /#/pages/portfoliodo przeglądarki, a hej presto portfolio.html zostało wyświetlone ....

Zaktualizowano Dodano $ Apply i $ compile do kodu HTML, aby można było wstrzyknąć zawartość dynamiczną.

Greg
źródło
2
Działa to tylko z zawartością statyczną, jeśli dobrze ją rozumiem. Dzieje się tak, ponieważ zmieniasz DOM po utworzeniu instancji kontrolera za pośrednictwem jQuery (poza zakresem kątowym). Zmienne takie jak {{this}} mogą działać (musiałbym spróbować), ale dyrektywy najprawdopodobniej nie. Uważaj na to, ponieważ może to być przydatne teraz, ale może złamać kod później ..
Tiago Roldão
To prawda, oprawianie nie działa, ale dla mnie nie jestem zbyt zdenerwowany, ponieważ chcę, aby klienci mogli tylko dodawać nowe strony. Wszelkie strony, które dodam, określiłbym odpowiednio w konfiguracji trasy. Słuszna uwaga.
Greg
1
Nie jest to złe rozwiązanie, jeśli chcesz renderować statyczną zawartość html. O ile pamiętasz, zasadniczo nadpisujesz widok ng-view statyczną (niekątową) treścią. Mając to na uwadze, łatwiej byłoby oddzielić te dwa elementy, utworzyć coś w rodzaju elementu <div id = "user-views"> </div> i wstawić tam swój „szablon użytkownika”. Nie ma też absolutnie sensu zastępowanie $ route.current.templateUrl - tworzenie modelu $ scope.userTemplate i wykonywanie $ ('# user-views "). Load ($ scope.userTemplate) jest czystsze i nie psuje żadnego z angularów kod.
Tiago Roldão
1
Nie - dyrektywa ng-view jest raczej ograniczona - lub lepiej mówiąc, jest specyficzna do użytku z natywnym systemem routingu (który z kolei jest nadal ograniczony, imho). Możesz zrobić tak, jak powiedziałeś, wstrzykując zawartość do elementu DOM, a następnie wywołać metodę $ compile, aby nakazać angularowi ponowne przeczytanie struktury. Spowoduje to wyzwolenie wszelkich powiązań, które należy wykonać.
Tiago Roldão
2
Działa, ale nie powinieneś wykonywać manipulacji DOM w kontrolerze.
dmackerman
16

Myślę, że najłatwiejszym sposobem na zrobienie tego jest rozwiązanie tras później, możesz na przykład zapytać o trasy przez json. Sprawdź, czy robię fabrykę z $ routeProvider podczas fazy konfiguracji, za pośrednictwem $ provider, dzięki czemu mogę nadal używać obiektu $ routeProvider w fazie uruchamiania, a nawet w kontrolerach.

'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
źródło
Aliasing $routeProviderużywany jako factory(dostępny później config) nie będzie dobrze współgrał z bezpieczeństwem i spójnością aplikacji - tak czy inaczej, fajna technika (upvote!).
Cody
@Cody Czy możesz wyjaśnić notatkę dotyczącą spójności bezpieczeństwa / aplikacji? Jesteśmy na podobnej łodzi i coś podobnego do powyższej odpowiedzi byłoby naprawdę przydatne.
virtualandy,
2
@virtualandy, generalnie nie jest to świetne podejście, ponieważ udostępniasz dostawcę w różnych fazach - co z kolei może wyciekać narzędzia wysokiego poziomu, które powinny zostać ukryte w fazie konfiguracji. Sugeruję, aby zastosować UI-Router (rozwiązuje wiele problemów), użyć funkcji „solution”, „controllerAs” i innych, takich jak ustawienie templateUrl na funkcję. Zbudowałem również moduł "presolve", który mogę udostępnić, który może interpolować dowolną część schematu trasy (template, templateUrl, kontroler, itp.). Powyższe rozwiązanie jest bardziej eleganckie - ale jakie są Twoje główne obawy?
Cody
1
@virtualandy, tutaj jest moduł dla lazyLoaded szablonów, kontrolerów itp. - przepraszam, że nie jest w repozytorium, ale tutaj jest skrzypce: jsfiddle.net/cCarlson/zdm6gpnb ... To lazyLoads powyższe - AND - może interpolować wszystko na podstawie parametrów adresu URL. Modułowi przydałoby się trochę pracy, ale to przynajmniej punkt wyjścia.
Cody
@Cody - nie ma problemu, dzięki za próbkę i dalsze wyjaśnienia. Zmysł Makese. W naszym przypadku używamy segmentu trasy kątowej i działa dobrze. Ale teraz musimy obsługiwać trasy „dynamiczne” (niektóre wdrożenia mogą nie mieć pełnych funkcji / tras strony lub mogą zmieniać ich nazwy itp.). Pomyśleliśmy o ładowaniu w niektórych kodach JSON, które definiują trasy przed ich wdrożeniem ale oczywiście napotkałem problemy z używaniem dostawców $ http / $ w .config ().
virtualandy
7

We wzorcach $ routeProvider URI możesz określić parametry zmiennych, na przykład: $routeProvider.when('/page/:pageNumber' ...i uzyskać do nich dostęp w kontrolerze za pośrednictwem $ routeParams.

Dobry przykład znajduje się na końcu strony $ route: http://docs.angularjs.org/api/ng.$route

EDYCJA (dla edytowanego pytania):

System routingu jest niestety bardzo ograniczony - toczy się wiele dyskusji na ten temat, a niektóre rozwiązania zostały zaproponowane, mianowicie poprzez utworzenie wielu nazwanych widoków itp. Ale obecnie dyrektywa ngView obsługuje tylko JEDEN widok na trasę, na na zasadzie jeden do jednego. Możesz to zrobić na wiele sposobów - prostszym byłoby użycie szablonu widoku jako modułu ładującego, z <ng-include src="myTemplateUrl"></ng-include>tagiem w nim ($ scope.myTemplateUrl zostałby utworzony w kontrolerze).

Używam bardziej złożonego (ale czystszego, dla większych i bardziej skomplikowanych problemów) rozwiązania, w zasadzie całkowicie pomijając usługę $ route, która jest szczegółowo opisana tutaj:

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

Tiago Roldão
źródło
Uwielbiam tę ng-includemetodę. Jest to naprawdę proste i jest to znacznie lepsze podejście niż manipulowanie DOM.
Neel
5

Nie wiem, dlaczego to działa, ale dynamiczne (lub wieloznaczne, jeśli wolisz) trasy są możliwe w kątowej 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 -> ładuje część „foo”

example.com/bar-> ładuje część „bar”

Nie ma potrzeby dokonywania żadnych zmian w widoku ng. Przypadek '/: a' jest jedyną zmienną, jaką znalazłem, która to osiągnie .. '/: foo' nie działa, chyba że wszystkie twoje części składowe to foo1, foo2, etc ... '/: a' działa z dowolną podrzędną Nazwa.

Wszystkie wartości uruchamiają dynamiczny kontroler - więc nie ma „inaczej”, ale myślę, że właśnie tego szukasz w scenariuszu routingu dynamicznego lub wieloznacznego.

Matthew Luchak
źródło
2

Począwszy od AngularJS 1.1.3, możesz teraz robić dokładnie to, co chcesz, używając nowego parametru catch-all.

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

Z zatwierdzenia:

Dzięki temu routeProvider może akceptować parametry pasujące do podciągów, nawet jeśli zawierają ukośniki, jeśli są poprzedzone gwiazdką zamiast dwukropka. Na przykład trasy takie jak edit/color/:color/largecode/*largecode będą pasować do czegoś takiego http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Sam go przetestowałem (używając 1.1.5) i działa świetnie. Pamiętaj tylko, że każdy nowy adres URL przeładuje kontroler, więc aby zachować jakikolwiek stan, może być konieczne użycie usługi niestandardowej.

Dave
źródło
0

Oto inne rozwiązanie, które działa dobrze.

(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
źródło