AngularJS динамическая маршрутизация

88

Сейчас у меня есть приложение AngularJS со встроенной маршрутизацией. Оно работает, и все в порядке.

Мой файл app.js выглядит так:

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

В мое приложение встроена CMS, где вы можете копировать и добавлять новые html-файлы в каталог / pages .

Я хотел бы по-прежнему пройти через поставщика маршрутизации, хотя даже для новых динамически добавляемых файлов.

В идеальном мире схема маршрутизации была бы такой:

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

Так что, если бы мое новое имя страницы было «contact.html», я бы хотел, чтобы angular взял «/ contact» и перенаправил на «/pages/contact.html».

Возможно ли такое ?! и если да, то как ?!

Обновить

Теперь у меня есть это в моей конфигурации маршрутизации:

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

и в моем CMSController:

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

Это устанавливает для текущего templateUrl правильное значение.

Однако теперь я хотел бы изменить ng-view с новым значением templateUrl. Как это достигается?

Грег
источник

Ответы:

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'
        });
    }
]);
  • Добавление * позволяет динамически работать с несколькими уровнями каталогов . Пример: / page / cars / sales / list будет улавливаться этим провайдером

Из документов (1.3.0):

"Если templateUrl является функцией, она будет вызываться со следующими параметрами:

{Array.} - параметры маршрута, извлеченные из текущего $ location.path () путем применения текущего маршрута "

Также

when (путь, маршрут): Метод

  • path может содержать именованные группы, начинающиеся с двоеточия и заканчивающиеся звездочкой: например: name *. Когда маршрут совпадает, все символы с готовностью сохраняются в $ routeParams под заданным именем.
Робин Ризви
источник
5
Необходимо идти в ногу со временем ... функциональность, которую я создал, которой не хватало в версии 1.0.2, теперь доступна. Обновление принятого ответа на этот
Грег
Честно говоря, у меня никогда не получалось, что это работает с текущими бета-
версиями
На самом деле он заработал, когда я сделал это ... when ('/: page', {templateUrl: function (parameters) {return parameters.page + '.html';}
Архимед Траяно
Серьезно ... Это действительно глупо, что Angular не использует его по умолчанию ... Эта ручная маршрутизация просто смешна
Гильерме Феррейра
3
Поясните, пожалуйста, откуда urlattrставится или отправляется, то есть что urlattr.nameбудет производить?
Sami
37

Хорошо решил это.

Добавлено решение в GitHub - http://gregorypratt.github.com/AngularDynamicRouting

В моей конфигурации маршрутизации app.js:

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

Затем в моем контроллере 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'];

Поскольку #views являются моими <div id="views" ng-view></div>

Итак, теперь он работает со стандартной маршрутизацией и динамической маршрутизацией.

Чтобы проверить это, я скопировал файл about.html, назвал его портфолио.html, изменил его содержимое и ввел /#/pages/portfolioв свой браузер, и на экране появилось сообщение hey prestofolio.html ....

Обновлено Добавлены $ apply и $ compile в html, чтобы можно было вводить динамический контент.

Грег
источник
2
Это работает только со статическим контентом, если я правильно понимаю. Это связано с тем, что вы изменяете DOM после создания экземпляра контроллера через jQuery (за пределами области действия angular). Такие переменные, как {{this}}, могут работать (я бы попробовал), но директивы, скорее всего, нет. Остерегайтесь этого, так как это может быть полезно сейчас, но может взломать код позже ..
Тьяго Рольдао,
Правда, привязка не работает, но я не слишком волнуюсь, так как хочу, чтобы клиенты могли только добавлять новые страницы. Любые страницы, которые я добавляю, я бы указал соответствующим образом в конфигурации маршрута. Но хороший момент.
Грег
1
Это неплохое решение, если вы хотите отображать статический html-контент. Пока вы помните, вы по существу замещаете ng-view статическим (не угловым) контентом. Имея это в виду, было бы проще разделить их, создав что-то вроде элемента <div id = "user-views"> </div> и вставив туда свой "пользовательский шаблон". Кроме того, нет абсолютно никакого смысла в переопределении $ route.current.templateUrl - создании модели $ scope.userTemplate и выполнении $ ('# user-views "). Load ($ scope.userTemplate) чище и не нарушает ни один из angular. код
Tiago Roldão
1
Нет - директива ng-view довольно ограничена - или, лучше сказать, она предназначена для использования с собственной системой маршрутизации (которая, в свою очередь, все еще ограничена, imho). Вы можете сделать, как вы сказали, внедрить контент в элемент DOM, а затем вызвать метод $ compile, чтобы сообщить angular о необходимости перечитать структуру. Это вызовет любые необходимые привязки.
Тьягу Рольдао,
2
Работает, но вам не следует выполнять манипуляции с DOM в вашем контроллере.
dmackerman
16

Я думаю, что самый простой способ сделать это - разрешить маршруты позже, например, вы можете запросить маршруты через json. Обратите внимание, что я создаю фабрику из $ routeProvider на этапе настройки с помощью $ provide, поэтому я могу продолжать использовать объект $ routeProvider на этапе выполнения и даже в контроллерах.

'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
источник
$routeProviderИспользование псевдонима в качестве factory(доступного после config) не будет хорошо работать с безопасностью и связностью приложений - в любом случае, крутая техника (голос за!).
Коди
@Cody, не могли бы вы объяснить примечание о безопасности / связности приложений? Мы находимся в похожей лодке, и что-то вроде приведенного выше ответа было бы действительно кстати.
virtualandy
2
@virtualandy, как правило, это не лучший подход, поскольку вы делаете поставщика доступным на разных этапах, что, в свою очередь, может привести к утечке утилит высокого уровня, которые должны быть скрыты на этапе настройки. Я предлагаю использовать UI-Router (решает множество проблем), использовать «resolve», «controllerAs» и другие возможности, такие как установка templateUrl для функции. Я также создал модуль «предварительного решения», которым я могу поделиться и который может $ интерполировать любую часть схемы маршрута (шаблон, templateUrl, контроллер и т. Д.). Приведенное выше решение более элегантно, но что вас беспокоит?
Cody
1
@virtualandy, вот модуль для шаблонов lazyLoaded, контроллеров и т.д. - извиняюсь, его нет в репо, но вот скрипка: jsfiddle.net/cCarlson/zdm6gpnb ... Этот lazyLoads выше - И - может интерполировать что угодно на основе параметров URL. Над модулем можно поработать, но это, по крайней мере, отправная точка.
Cody
@Cody - без проблем, спасибо за образец и дальнейшие объяснения. Обретите смысл. В нашем случае мы используем angular-route-segment, и он отлично работает. Но теперь нам необходимо поддерживать «динамические» маршруты (некоторые развертывания могут не иметь полных функций / маршрутов страницы или могут изменять свои имена и т. Д.), И мы думали, что идея загрузки в некотором JSON, который определяет маршруты до фактической реализации, была но, очевидно, столкнулись с проблемами при использовании провайдеров $ http / $ в .config ().
virtualandy
7

В шаблонах URI $ routeProvider вы можете указать переменные параметры, например:, $routeProvider.when('/page/:pageNumber' ...и получить к нему доступ в своем контроллере через $ routeParams.

В конце страницы $ route есть хороший пример: http://docs.angularjs.org/api/ng.$route

РЕДАКТИРОВАТЬ (для отредактированного вопроса):

Система маршрутизации, к сожалению, очень ограничена - по этой теме ведется много дискуссий, и были предложены некоторые решения, а именно создание нескольких именованных представлений и т. Д. Но прямо сейчас директива ngView обслуживает только ОДНО представление для каждого маршрута на взаимно однозначная основа. Вы можете сделать это несколькими способами - проще было бы использовать шаблон представления в качестве загрузчика с <ng-include src="myTemplateUrl"></ng-include>тегом в нем ($ scope.myTemplateUrl будет создан в контроллере).

Я использую более сложное (но более чистое, для более крупных и сложных проблем) решение, в основном полностью пропуская службу $ route, которая подробно описана здесь:

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

Тьяго Рольдао
источник
Мне нравится этот ng-includeметод. Это действительно просто, и это намного лучший подход, чем манипулирование DOM.
Нил
5

Не уверен, почему это работает, но динамические (или подстановочные знаки, если хотите) маршруты возможны в 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 -> загружает частичную "foo"

example.com/bar-> загружает "бар" частично

Никаких настроек в ng-view не требуется. Случай '/: a' - единственная переменная, которую я обнаружил, которая добьется этого. '/: Foo' не будет работать, если все ваши частичные данные не являются foo1, foo2 и т.д ... '/: a' работает с любыми частичными название.

Все значения запускают динамический контроллер - так что нет никакого «другого», но я думаю, что это то, что вы ищете в сценарии динамической или групповой маршрутизации.

Мэтью Лучак
источник
2

Начиная с AngularJS 1.1.3, вы можете делать именно то, что хотите, используя новый универсальный параметр.

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

Из коммита:

Это позволяет routeProvider принимать параметры, соответствующие подстрокам, даже если они содержат косую черту, если они начинаются со звездочки вместо двоеточия. Например, маршруты вроде edit/color/:color/largecode/*largecode будут соответствовать примерно этому http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

Я тестировал это сам (используя 1.1.5), и он отлично работает. Просто имейте в виду, что каждый новый URL-адрес перезагружает ваш контроллер, поэтому для сохранения любого состояния вам может потребоваться использовать настраиваемую службу.

Дэйв
источник
0

Вот еще одно решение, которое хорошо работает.

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

    }

})();
Кевиниус
источник