Perutean dinamis AngularJS

88

Saat ini saya memiliki aplikasi AngularJS dengan perutean bawaan. Ia bekerja dan semuanya baik-baik saja.

File app.js saya terlihat seperti ini:

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

Aplikasi saya memiliki CMS bawaan tempat Anda dapat menyalin dan menambahkan file html baru di dalam direktori / pages .

Saya ingin tetap melalui penyedia perutean meskipun untuk file baru yang ditambahkan secara dinamis.

Dalam dunia yang ideal pola peruteannya adalah:

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

Jadi jika nama halaman baru saya adalah "contact.html", saya ingin angular untuk mengambil "/ contact" dan dialihkan ke "/pages/contact.html".

Apakah ini mungkin ?! dan jika demikian bagaimana ?!

Memperbarui

Saya sekarang memiliki ini di konfigurasi perutean saya:

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

dan di CMSController saya:

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

Ini menyetel templateUrl saat ini ke nilai yang benar.

Namun sekarang saya ingin mengubah ng-view dengan nilai templateUrl baru. Bagaimana ini tercapai?

Greg
sumber

Jawaban:

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'
        });
    }
]);
  • Menambahkan * memungkinkan Anda bekerja dengan beberapa level direktori secara dinamis. Contoh: / halaman / mobil / penjualan / daftar akan ditangkap di penyedia ini

Dari dokumen (1.3.0):

"Jika templateUrl adalah sebuah fungsi, itu akan dipanggil dengan parameter berikut:

{Array.} - parameter rute yang diekstrak dari $ location.path () saat ini dengan menerapkan rute saat ini "

Juga

when (path, route): Metode

  • path dapat berisi grup bernama dimulai dengan titik dua dan diakhiri dengan bintang: mis .: nama *. Semua karakter disimpan dengan penuh semangat di $ routeParams di bawah nama yang diberikan saat rute cocok.
Robin Rizvi
sumber
5
Perlu bergerak seiring waktu ... fungsionalitas yang saya bangun yang tadinya hilang di v1.0.2 sekarang tersedia. Memperbarui jawaban yang diterima untuk yang satu ini
Greg
Sejujurnya saya tidak pernah mendapatkan ini berfungsi dengan 1,3 beta saat ini
Archimedes Trajano
Sebenarnya berhasil ketika saya melakukan ini ... ketika ('/: page', {templateUrl: function (parameter) {return parameter.page + '.html';}
Archimedes Trajano
Serius .. Benar-benar bodoh bahwa Angular tidak menjadikannya sebagai perilaku default ... Perutean manual itu konyol
Guilherme Ferreira
3
Tolong jelaskan, Dari mana asal urlattrditetapkan atau dikirim, maksud saya apa yang urlattr.nameakan diproduksi?
Sami
37

Ok menyelesaikannya.

Menambahkan solusi ke GitHub - http://gregorypratt.github.com/AngularDynamicRouting

Di konfigurasi perutean app.js saya:

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

Kemudian di pengontrol CMS saya:

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

Dengan #views menjadi milikku <div id="views" ng-view></div>

Jadi sekarang ini berfungsi dengan perutean standar dan perutean dinamis.

Untuk mengujinya, saya menyalin about.html menyebutnya portfolio.html, mengubah beberapa isinya dan masuk /#/pages/portfolioke browser saya dan hei presto portfolio.html ditampilkan ....

Diperbarui Menambahkan $ apply dan $ compile ke html sehingga konten dinamis dapat dimasukkan.

Greg
sumber
2
Ini hanya berfungsi dengan konten statis, jika saya memahaminya dengan benar. Ini karena Anda mengubah DOM setelah Anda memberi contoh pengontrol, melalui jQuery (di luar cakupan sudut). Variabel seperti {{this}} mungkin berfungsi (saya harus mencobanya) tetapi kemungkinan besar tidak akan berfungsi. Berhati-hatilah dengan ini, karena mungkin berguna sekarang, tetapi dapat memecahkan kode nanti ..
Tiago Roldão
Benar, penjilidan tidak berfungsi, namun bagi saya saya tidak terlalu sibuk karena saya hanya ingin klien dapat menambahkan halaman baru. Halaman apa pun yang saya tambahkan akan saya tentukan dengan tepat melalui konfigurasi rute. Poin yang bagus.
Greg
1
Ini bukan solusi yang buruk jika Anda ingin merender konten html statis. Selama Anda ingat, pada dasarnya Anda menimpa ng-view dengan konten statis (non angular). Dengan pemikiran tersebut, akan lebih mudah untuk memisahkan keduanya, membuat sesuatu seperti elemen <div id = "user-views"> </div>, dan memasukkan "template pengguna" Anda di sana. Selain itu, sama sekali tidak ada gunanya menimpa $ route.current.templateUrl - membuat model $ scope.userTemplate dan melakukan $ ('# user-views "). Load ($ scope.userTemplate) lebih bersih, dan tidak merusak sudut apa pun kode.
Tiago Roldão
1
Tidak - direktif ng-view agak terbatas - atau lebih baik dikatakan, ini khusus untuk digunakan dengan sistem perutean asli (yang, pada gilirannya, masih terbatas, imho). Anda dapat melakukan apa yang Anda katakan, memasukkan konten ke dalam elemen DOM, lalu memanggil metode $ compile, untuk memberi tahu angular untuk membaca ulang strukturnya. Itu akan memicu pengikatan apa pun yang perlu dilakukan.
Tiago Roldão
2
Berfungsi, tetapi Anda tidak boleh melakukan manipulasi DOM di pengontrol Anda.
dmackerman
16

Menurut saya cara termudah untuk melakukannya adalah dengan menyelesaikan rute nanti, Anda bisa menanyakan rute melalui json, misalnya. Periksa bahwa saya membuat pabrik dari $ routeProvider selama fase konfigurasi, melalui $ menyediakan, jadi saya dapat tetap menggunakan objek $ routeProvider dalam fase run, dan bahkan di pengontrol.

'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
sumber
Aliasing $routeProvideruntuk digunakan sebagai factory(tersedia setelah config) tidak akan berfungsi baik dengan keamanan dan kohesi aplikasi - bagaimanapun juga, teknik yang keren (suara positif!).
Cody
@Cody Bisakah Anda menjelaskan catatan kohesi keamanan / aplikasi? Kami berada di perahu yang sama dan sesuatu seperti jawaban di atas akan sangat berguna.
virtualandy
2
@virtualandy, ini umumnya bukan pendekatan yang bagus karena Anda membuat penyedia tersedia di berbagai fase - yang pada gilirannya dapat membocorkan utilitas tingkat tinggi yang harus disembunyikan dalam fase konfigurasi. Saran saya adalah menggunakan UI-Router (menangani banyak kesulitan), gunakan "resolus", "controllerAs", dan fakultas lain seperti pengaturan templateUrl ke suatu fungsi. Saya juga membangun modul "presolve" yang dapat saya bagikan yang dapat $ interpolasi bagian mana pun dari skema rute (template, templateUrl, controller, dll). Solusi di atas lebih elegan - tapi apa perhatian utama Anda?
Cody
1
@virtualandy, ini adalah modul untuk template lazyLoaded, pengontrol, dll - maaf tidak ada dalam repo, tapi ini biola: jsfiddle.net/cCarlson/zdm6gpnb ... LazyLoad ini di atas - DAN - dapat menginterpolasi apa pun berdasarkan parameter URL. Modul ini dapat menggunakan beberapa pekerjaan, tetapi setidaknya ini merupakan titik awal.
Cody
@Cody - tidak masalah, terima kasih atas sampel dan penjelasan lebih lanjut. Masuk akal. Dalam kasus kami, kami menggunakan segmen-rute-sudut dan itu bekerja dengan baik. Namun sekarang kami perlu mendukung rute "dinamis" (beberapa penerapan mungkin tidak memiliki fitur / rute laman yang lengkap, atau mungkin mengubah namanya, dll.) Dan gagasan memuat di beberapa JSON yang menentukan rute sebelum benar-benar menerapkan adalah pemikiran kami tetapi mengalami masalah saat menggunakan $ http / $ provider dalam .config () jelas.
virtualandy
7

Dalam pola URI $ routeProvider, Anda dapat menetapkan parameter variabel, seperti $routeProvider.when('/page/:pageNumber' ...:, dan mengaksesnya di pengontrol Anda melalui $ routeParams.

Ada contoh yang bagus di akhir halaman $ route: http://docs.angularjs.org/api/ng.$route

EDIT (untuk pertanyaan yang diedit):

Sayangnya, sistem perutean sangat terbatas - ada banyak diskusi tentang topik ini, dan beberapa solusi telah diusulkan, yaitu melalui pembuatan beberapa tampilan bernama, dll. Tetapi saat ini, arahan ngView hanya melayani SATU tampilan per rute, di basis satu-ke-satu. Anda dapat melakukannya dengan berbagai cara - cara yang lebih sederhana adalah menggunakan template tampilan sebagai loader, dengan <ng-include src="myTemplateUrl"></ng-include>tag di dalamnya ($ scope.myTemplateUrl akan dibuat di controller).

Saya menggunakan solusi yang lebih kompleks (tapi lebih bersih, untuk masalah yang lebih besar dan lebih rumit), pada dasarnya melewatkan layanan $ route sama sekali, yang dirinci di sini:

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

Tiago Roldão
sumber
Saya suka ng-includemetodenya. Ini sangat sederhana dan ini adalah pendekatan yang jauh lebih baik daripada memanipulasi DOM.
Neel
5

Tidak yakin mengapa ini berfungsi tetapi rute dinamis (atau karakter pengganti jika Anda lebih suka) dimungkinkan dalam sudut 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 -> memuat sebagian "foo"

example.com/bar-> memuat sebagian "bar"

Tidak perlu penyesuaian apa pun dalam ng-view. Kasus '/:' adalah satu-satunya variabel yang saya temukan yang akan mencapai hal ini .. '/: foo' tidak berfungsi kecuali jika sebagian Anda adalah foo1, foo2, dll ... '/: a' berfungsi dengan sebagian nama.

Semua nilai mengaktifkan pengontrol dinamis - jadi tidak ada "sebaliknya" tetapi, menurut saya itulah yang Anda cari dalam skenario perutean dinamis atau karakter pengganti ..

Matthew Luchak
sumber
2

Mulai AngularJS 1.1.3, Anda sekarang dapat melakukan apa yang Anda inginkan menggunakan parameter catch-all yang baru.

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

Dari komit:

Ini memungkinkan routeProvider menerima parameter yang cocok dengan substring meskipun berisi garis miring jika diawali dengan tanda bintang, bukan titik dua. Misalnya, rute suka edit/color/:color/largecode/*largecode akan cocok dengan yang seperti ini http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Saya telah mengujinya sendiri (menggunakan 1.1.5) dan berhasil dengan baik. Perlu diingat bahwa setiap URL baru akan memuat ulang pengontrol Anda, jadi untuk mempertahankan status apa pun, Anda mungkin perlu menggunakan layanan khusus.

Dave
sumber
0

Berikut adalah solusi lain yang berfungsi dengan baik.

(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.dll
sumber