Подтвердить что ты не робот

Модальное окно с настраиваемым URL в AngularJS

Мне нужно отобразить модальное окно (в основном просто скрытый div, который будет загружен скомпилированным шаблоном) в моем приложении Angular. Проблема в том, что мне нужен URL-адрес для изменения, когда модаль открывается, чтобы пользователи могли скопировать ссылку и перейти непосредственно в модальное окно, а также использовать кнопку "Назад", чтобы закрыть модальное окно и вернуться на предыдущую страницу. Это похоже на то, как Pinterest обрабатывает модальные окна, когда вы нажимаете на вывод.

До сих пор я создал директиву, которая загружает шаблон, компилирует его с помощью компиляции $, внедряет область $scope и затем отображает скомпилированный шаблон. Это прекрасно работает.

Проблема заключается в том, как только я использую $location для изменения пути, контроллер маршрута запускает и загружает шаблон в ng-view.

Я подумал о двух способах преодоления этого, но не смог реализовать:

  • Как-то предотвратить запуск контроллера маршрута, когда я изменяю URL с помощью $location. Я добавил слушателя в $routeChangeStart, чтобы предотвратить дефолт, но это не работает.

  • Как-то добавить на страницу еще один обработчик вида (в основном на странице есть 2 имени ng-view) и каждый из них может обрабатывать разные маршруты. Не могу видеть, что Angular поддерживает это в данный момент.

URL должен иметь формат /item/item _id, а не /item? item_id = 12345.

Любая помощь будет оценена.

4b9b3361

Ответ 1

Теперь вы можете сделать это, если используете модуль ui.router, как описано здесь: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state.

Пример:

$stateProvider.state("items.add", {
    url: "/add",
    onEnter: function($stateParams, $state, $modal, $resource) {
        $modal.open({
            templateUrl: "items/add",
            resolve: {
              item: function() { new Item(123).get(); }
            },
            controller: ['$scope', 'item', function($scope, item) {
                $scope.dismiss = function() {
                    $scope.$dismiss();
                };

                $scope.save = function() {
                    item.update().then(function() {
                        $scope.$close(true);
                    });
                };
            }]
        }).result.then(function(result) {
            if (result) {
                return $state.transitionTo("items");
            }
        });
    }
});

Я также передаю функцию errorCallback функции then() для обработки модального увольнения, нажав Escape или щелкнув по фону.

Ответ 2

У меня были аналогичные требования. Мне нужно:

  • Модальное окно, чтобы вызвать изменение маршрута, поэтому у него есть собственный хэш-адрес.
  • Когда вы посещаете модальный url, загружается основной экран, а затем запускает модальный путь к popop.
  • Кнопка "Назад" закрывает модальную (предполагается, что вы начали на главном экране).
  • При нажатии кнопки "ОК" или "Отмена" модаль закрывается, и маршрут возвращается на главный экран.
  • Когда вы нажмете фон, чтобы закрыть модальный, маршрут изменится на главный экран.

Я не мог найти хороший пример всей этой совместной работы, но мне удалось найти примеры бит и кусков, поэтому я собрал их вместе в рабочий пример следующим образом:

<!DOCTYPE html>
<html lang="en" ng-app="app">

<head>
  <meta charset="UTF-8">
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.11/angular-ui-router.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.2/ui-bootstrap-tpls.js"></script>
  <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.css" rel="stylesheet">
  <script>

    // init the app with ui.router and ui.bootstrap modules
    var app = angular.module('app', ['ui.router', 'ui.bootstrap']);

    // make back button handle closing the modal
    app.run(['$rootScope', '$modalStack',
      function($rootScope, $modalStack) {
        $rootScope.$on('$stateChangeStart', function() {
          var top = $modalStack.getTop();
          if (top) {
            $modalStack.dismiss(top.key);
          }
        });
      }
    ]);

    // configure the stateProvider
    app.config(['$stateProvider', '$urlRouterProvider',
      function($stateProvider, $urlRouterProvider) {

        // default page to be "/" (home)
        $urlRouterProvider.otherwise('/');

        // configure the route states
        $stateProvider

          // define home route "/"
          .state('home', {
            url: '/'
          })

          // define modal route "/modal"
          .state('modal', {
            url: '/modal',

            // trigger the modal to open when this route is active
            onEnter: ['$stateParams', '$state', '$modal',
              function($stateParams, $state, $modal) {
                $modal

                  // handle modal open
                  .open({
                    template: '<div class="modal-header"><h3 class="modal-title">Modal</h3></div><div class="modal-body">The modal body...</div><div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>',
                    controller: ['$scope',
                      function($scope) {
                        // handle after clicking Cancel button
                        $scope.cancel = function() {
                          $scope.$dismiss();
                        };
                        // close modal after clicking OK button
                        $scope.ok = function() {
                          $scope.$close(true);
                        };
                      }
                    ]
                  })

                  // change route after modal result
                  .result.then(function() {
                    // change route after clicking OK button
                    $state.transitionTo('home');
                  }, function() {
                    // change route after clicking Cancel button or clicking background
                    $state.transitionTo('home');
                  });

              }
            ]

          });
      }
    ]);
  </script>
</head>

<body>

  <a href="#/modal" class="btn btn-default">POPUP!</a>

  <div ui-view></div>

</body>

</html>

Вы можете просмотреть его в действии на plunker: http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

Нажмите ссылку "Открыть встроенный просмотр" в плункере, чтобы увидеть, как работает хэш-тег.

Ответ 3

Решение hermanschutte работало (см. комментарий к сообщению Tiago Roldão). Я отправляю шаг за шагом, чтобы помочь уточнить.

Сначала в config:

.when('/posts', {
  templateUrl: 'views/main.html',
  controller: 'MainCtrl',
  reloadOnSearch: false     // this allows us to use the back button to get out of modals.
})

Затем, при открытии модального (вам понадобится модуль местоположения):

$location.search('modal');

И в модальном контроллере:

$scope.$on('$locationChangeSuccess', function() {
  $scope.close(); // call your close function
});

Ответ 4

Упомянутое решение названий, которое вы упомянули, отлично обсудило список рассылки angularJS, но на данный момент (и почти в середине будущего) система $route не обрабатывает несколько просмотров.

Как я уже сказал в нескольких местах, и здесь, в SO, несколько раз, система $route является одной из ситуаций, когда angular очень упрямая позиция может стать препятствием: она должна быть простой, но он не очень настраиваемый. Я лично не использую его напрямую, а скорее обхожу его через решение, которое я нашел здесь.

Это немного усложняет ситуацию, но это дает вам большую гибкость - идея состоит в том, что маршрут $only служит для запуска (ручной) функции рендеринга, где вы можете назначать значения и запускать широковещательные передачи $, как вы пожелаете (обычно, от главного контроллера).

Одна вещь, которую я не пробовал, - это "гибридное" решение, то есть, используя стандартную систему $route, и настраивая маршрут /item/item _id, чтобы НЕ иметь параметр представления (чтобы не изменять основной вид, инициированный другими маршрутами) и сделать что-то через событие $routeChangeStart (помните, что вы можете назначить любое значение маршрутам, как вы можете видеть на странице, на которую я ссылался)

Опять же, лично я предпочитаю полностью использовать альтернативный метод.

Ответ 5

Возможно, вам захочется попытаться захватить исходный код AngularJS, настроить и загрузить собственное пивоварение $route и $routeProvider, а затем ввести их через Angular туземцев.

Вероятно, вам нужно добавить флаг, чтобы перехватить триггер изменения маршрута.

Другим более простым вариантом было бы подключиться к системе событий, используя $scope.$on(event_name, function callback(ev) {...}) и вызывая .preventDefault() в переданном событии, надеясь, что он остановит внутренний триггер $route. Система определения местоположения передает событие $locationChangeStart и проверяет атрибут defaultPrevented перед получением события $locationChangeSuccess.

Не забудьте опубликовать свои результаты!