Instradamento dinamico AngularJS


88

Al momento ho un'applicazione AngularJS con routing integrato. Funziona e va tutto bene.

Il mio file app.js ha questo aspetto:

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

La mia app ha un CMS integrato in cui puoi copiare e aggiungere nuovi file html nella directory / pages .

Vorrei comunque passare attraverso il provider di routing anche se anche per i nuovi file aggiunti dinamicamente.

In un mondo ideale il modello di instradamento sarebbe:

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

Quindi, se il nome della mia nuova pagina fosse "contact.html", vorrei che angular prenda "/ contact" e reindirizzi a "/pages/contact.html".

È anche possibile ?! e se sì come ?!

Aggiornare

Ora ho questo nella mia configurazione di routing:

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

e nel mio CMSController:

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

Questo imposta l'attuale templateUrl sul valore corretto.

Tuttavia , ora vorrei modificare ng-view con il nuovo valore templateUrl. Come si ottiene ciò?

Risposte:


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'aggiunta di * ti consente di lavorare dinamicamente con più livelli di directory . Esempio: / page / cars / selling / list verrà catturato da questo provider

Dai documenti (1.3.0):

"Se templateUrl è una funzione, verrà chiamata con i seguenti parametri:

{Array.} - parametri della rotta estratti dall'attuale $ location.path () applicando la rotta corrente "

Anche

quando (percorso, rotta): metodo

  • percorso può contenere gruppi denominati che iniziano con due punti e finiscono con una stella: ad esempio: nome *. Tutti i caratteri vengono memorizzati con entusiasmo in $ routeParams sotto il nome dato quando la rotta corrisponde.

5
Bisogna essere al passo con i tempi ... la funzionalità che ho costruito che mancava nella v1.0.2 è ora disponibile. Aggiornamento della risposta accettata a questa
Greg,

Ad essere onesto non l'ho mai fatto funzionare con le attuali 1.3 beta
Archimedes Trajano

In realtà ha funzionato quando l'ho fatto ... quando ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Archimedes Trajano

Scherzi a parte .. È davvero stupido che Angular non lo abbia come comportamento predefinito ... Quel routing manuale è ridicolo
Guilherme Ferreira

3
Si prega di spiegare, da dove urlattrviene impostato o inviato, voglio dire cosa urlattr.nameprodurrà?
Sami

37

Ok risolto.

Aggiunta la soluzione a GitHub - http://gregorypratt.github.com/AngularDynamicRouting

Nella mia configurazione del routing app.js:

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

Quindi nel mio controller 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 come mio <div id="views" ng-view></div>

Quindi ora funziona con il routing standard e il routing dinamico.

Per provarlo ho copiato about.html chiamato portfolio.html, ho cambiato alcuni dei suoi contenuti e sono entrato /#/pages/portfolionel mio browser e presto è stato visualizzato portfolio.html ....

Aggiornato $ apply e $ compile aggiunti all'html in modo che il contenuto dinamico possa essere iniettato.


2
Funziona solo con contenuti statici, se lo capisco correttamente. Questo perché stai alterando il DOM dopo aver istanziato il controller, tramite jQuery (al di fuori dell'ambito di angular). Variabili come {{this}} possono funzionare (dovrei provarlo) ma le direttive molto probabilmente no. Diffidare di questo, in quanto potrebbe essere utile ora, ma può rompere il codice in seguito ..
Tiago Roldão

È vero, il binding non funziona, per me tuttavia non sono troppo preoccupato perché voglio solo che i clienti siano in grado di aggiungere nuove pagine. Tutte le pagine che aggiungo, le specificherei in modo appropriato tramite la configurazione del percorso. Buon punto però.
Greg

1
Non è una cattiva soluzione se vuoi rendere il contenuto html statico. Finché tieni a mente che stai essenzialmente sovrascrivendo la visualizzazione ng con contenuto statico (non angolare). Con questo in mente, sarebbe più pulito separare i due, creando qualcosa di simile a un elemento <div id = "user-views"> </div> e inserendo il tuo "modello utente" lì. Inoltre, non ha assolutamente senso sovrascrivere $ route.current.templateUrl - creare un modello $ scope.userTemplate e fare $ ('# user-views "). Load ($ scope.userTemplate) è più pulito e non interrompe nessuno dei codice
Tiago Roldão

1
No - la direttiva ng-view è piuttosto limitata - o meglio, è specifica per l'uso con il sistema di routing nativo (che è, a sua volta, ancora limitato, imho). Puoi fare come hai detto, iniettando il contenuto in un elemento DOM, e quindi chiamare il metodo $ compile, per dire ad angular di rileggere la struttura. Ciò attiverà tutte le associazioni che devono essere eseguite.
Tiago Roldão

2
Funziona, ma non dovresti manipolare DOM nel tuo controller.
dmackerman

16

Penso che il modo più semplice per farlo sia risolvere le rotte in un secondo momento, potresti chiedere le rotte tramite json, ad esempio. Controlla che creo una factory da $ routeProvider durante la fase di configurazione, tramite $ provide, in modo da poter continuare a utilizzare l'oggetto $ routeProvider nella fase di esecuzione e anche nei controller.

'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();
    });
});

L'aliasing $routeProviderda utilizzare come factory(disponibile dopo config) non funzionerà bene con la sicurezza e la coesione dell'app, in ogni caso, tecnica interessante (voto positivo!).
Cody

@Cody puoi spiegare la nota di coesione sulla sicurezza / app? Siamo su una barca simile e qualcosa come la risposta sopra sarebbe davvero utile.
virtualandy

2
@virtualandy, generalmente non è un ottimo approccio in quanto stai rendendo disponibile un provider in diverse fasi, il che a sua volta può far trapelare utilità di alto livello che dovrebbero essere nascoste durante la fase di configurazione. Il mio suggerimento è di utilizzare UI-Router (si prende cura di molte difficoltà), usa "ubleshoot "," controllerAs "e altre facoltà come impostare templateUrl su una funzione. Ho anche creato un modulo "presolve" che posso condividere che può $ interpolare qualsiasi parte di uno schema di route (template, templateUrl, controller, ecc.). La soluzione di cui sopra è più elegante, ma quali sono le tue maggiori preoccupazioni?
Cody

1
@virtualandy, ecco un modulo per modelli, controller, ecc. lazyLoaded - mi scuso non è in un repository, ma ecco un violino: jsfiddle.net/cCarlson/zdm6gpnb ... Questo lazyLoads - E - può interpolare qualsiasi cosa in base ai parametri URL. Il modulo potrebbe richiedere del lavoro, ma è almeno un punto di partenza.
Cody

@Cody - nessun problema, grazie per il campione e ulteriori spiegazioni. Senso Makese. Nel nostro caso, stiamo usando un segmento di percorso angolare e ha funzionato bene. Ma ora dobbiamo supportare le rotte "dinamiche" (alcune distribuzioni potrebbero non avere tutte le funzionalità / rotte della pagina, o potrebbero cambiare i loro nomi, ecc.) E la nozione di caricamento in alcuni JSON che definiscono le rotte prima dell'effettiva implementazione era il nostro pensiero ma ovviamente si sono imbattuti in problemi utilizzando $ http / $ provider all'interno di .config ().
virtualandy

7

Negli schemi URI $ routeProvider, puoi specificare parametri variabili, in questo modo $routeProvider.when('/page/:pageNumber' ...:, e accedervi nel tuo controller tramite $ routeParams.

C'è un buon esempio alla fine della pagina $ route: http://docs.angularjs.org/api/ng.$route

MODIFICA (per la domanda modificata):

Il sistema di routing è purtroppo molto limitato - c'è molta discussione su questo argomento e sono state proposte alcune soluzioni, vale a dire tramite la creazione di più viste con nome, ecc. Ma al momento, la direttiva ngView serve solo UNA vista per rotta, su una base uno a uno. Puoi farlo in diversi modi: il più semplice sarebbe usare il modello della vista come caricatore, con un <ng-include src="myTemplateUrl"></ng-include>tag al suo interno ($ scope.myTemplateUrl verrebbe creato nel controller).

Uso una soluzione più complessa (ma più pulita, per problemi più grandi e complicati), in pratica saltando del tutto il servizio $ route, che è dettagliato qui:

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


Amo il ng-includemetodo. È davvero semplice e questo è un approccio molto migliore rispetto alla manipolazione del DOM.
Neel

5

Non sono sicuro del motivo per cui funziona, ma sono possibili percorsi dinamici (o jolly se preferisci) in angolare 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 -> carica "foo" parziale

example.com/bar-> carica la "barra" parziale

Non è necessario alcun aggiustamento nella visualizzazione ng. Il caso '/: a' è l'unica variabile che ho trovato che otterrà questo .. '/: foo' non funziona a meno che i tuoi parziali non siano tutti foo1, foo2, ecc ... '/: a' funziona con qualsiasi parziale nome.

Tutti i valori attivano il controller dinamico, quindi non esiste un "altrimenti", ma penso che sia quello che stai cercando in uno scenario di routing dinamico o con caratteri jolly ..


2

A partire da AngularJS 1.1.3, ora puoi fare esattamente quello che vuoi usando il nuovo parametro catch-all.

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

Dal commit:

Ciò consente a routeProvider di accettare parametri che corrispondono a sottostringhe anche quando contengono barre se sono precedute da un asterisco anziché da due punti. Ad esempio, percorsi simili edit/color/:color/largecode/*largecode corrisponderanno a qualcosa di simile http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

L'ho testato io stesso (usando 1.1.5) e funziona benissimo. Tieni presente che ogni nuovo URL ricaricherà il tuo controller, quindi per mantenere qualsiasi tipo di stato, potresti dover utilizzare un servizio personalizzato.


0

Ecco un'altra soluzione che funziona bene.

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

    }

})();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.