Отмена изменения пути в AngularJS

Original author: Dan Wahlin
  • Translation
  • Tutorial
Давно полюбив Angular, я только сейчас добрался до блога автора фреймворка Dan Wahlin. А зря — там можно найти много интересных и нужных каждому приложению мыслей. Одну из них я и перевёл в этой статье. В ней вы узнаете как можно остановить переход на другую страницу в приложении Angular.




Словарь переводчика:
— view — шаблон
— route — маршрут, путь
— navigation — переход

Маршрутизация является отличным средством, которое позволяет с наименьшим количеством необходимого кода связать шаблоны с контроллерами AngularJS.
В то время как пользователь может переходить прямо к заданному пути, существует возможность того, что он инициирует переход до завершения какого-нибудь важного действия (например сохранение данных). В таких ситуациях Вы можете захотеть отменить переход и спросить пользователя, хочет ли он закончить свою работу или предупредить о потере его данных. В этой статье я хочу поговорить о приёме, который может быть использован для реализации такого задания.

Событие $locationChangeStart


Когда происходит переход в приложении AngularJS возникает несколько событий. Одно из них называется $locationChangeStart, а другое — $routeChangeStart (на самом деле существует ещё несколько). В настоящее время (версия 1.2) событие $routeChangeStart не предоставляет возможности отменить переход, зато $locationChangeStart вполне пригоден для этого. Если Вы посмотрите на код AngularJS, то найдёте следующий участок, который показывает возникновение события $locationChangeStart при вызове метода onUrlChange() объекта $browser:
$browser.onUrlChange(function (newUrl) {
    if ($location.absUrl() != newUrl) {
        if ($rootScope.$broadcast('$locationChangeStart', newUrl, 
            $location.absUrl()).defaultPrevented) {
            $browser.url($location.absUrl());
            return;
        }
        $rootScope.$evalAsync(function () {
            var oldUrl = $location.absUrl();

            $location.$$parse(newUrl);
            afterLocationChange(oldUrl);
        });
        if (!$rootScope.$$phase) $rootScope.$digest();
    }
});

Самая важная часть кода — это вызов $broadcast. Этот вызов передаёт событие $locationChangeStart всем дочерним scope, благодаря чему они могут быть оповещены о смене location. Чтобы обработать это событие, Вы можете использовать функцию $rootScope.on(). Для примера я добавил обращение к $on() в функцию, которая вызывается сразу после запуска контроллера:
 function init() {
    
    //initialize data here..
    //Make sure they're warned if they made a change but didn't save it
    //Call to $on returns a "deregistration" function that can be called to
    //remove the listener (see routeChange() for an example of using it)
    onRouteChangeOff = $rootScope.$on('$locationChangeStart', routeChange);
}

Этот код ожидает событие $locationChangeStart и вызывает функцию routeChange() при его возникновении. Возвращаемым значением функции $on является функция «отмены ожидания», которая может быть вызвана для того, чтобы прекратить ожидание события. В этом примере она называется onRouteChangeOff (доступна в контроллере). Очень скоро вы увидите как она используется.

Отмена перехода по маршруту


Callback-функция routeChange() срабатывает благодаря событию $locationChangeStart и показывает предупреждающее пользователя диалоговое окно:


Вот код функции routeChange():
function routeChange(event, newUrl) {
    //Navigate to newUrl if the form isn't dirty
    if (!$scope.editForm.$dirty) return;

    var modalOptions = {
        closeButtonText: 'Cancel',
        actionButtonText: 'Ignore Changes',
        headerText: 'Unsaved Changes',
        bodyText: 'You have unsaved changes. Leave the page?'
    };

    modalService.showModal({}, modalOptions).then(function (result) {
        if (result === 'ok') {
            onRouteChangeOff(); //Stop listening for location changes
            $location.path(newUrl); //Go to page they're interested in
        }
    });

    //prevent navigation by default since we'll handle it
    //once the user selects a dialog option
    event.preventDefault();
    return;
}

В параметрах этой функции Вы можете заметить объект события и новый маршрут, по которому пользователь пытается перейти. Так как до смены шаблона нужно напомнить пользователю о потере данных, объект события используется для отмены перехода. Обратите внимание на вызов event.preventDefault() в конце функции.
Диалоговое окно показано вызовом функции modalService.showModal() (взгляните на мой предыдущий пост о персонализированном modalService который работает обёрткой вокруг $modal сервиса из Angular UI Bootstrap).
Если пользователь выберет «Ignore Changes» то его изменения будут отменены и приложение перейдёт по необходимому маршруту. Это произойдёт благодаря отключению ожидания события $locationChangeStart вызовом функции onRouteChangeOff() (вспомните, что эта функция возвращена вызовом функции $on), так что мы не застрянем в бесконечном цикле появления диалогового окна при клике на кнопку «Ignore Changes».
Затем вызывается $location.path(newUrl) для обработки перехода к заданному шаблону. Если пользователь отменит действие, он останется на текущем шаблоне.

Выводы


Ключ к отмене перехода — это понимание принципа работы с событием $locationChangeStart.
Я надеюсь в будущем подобное задание можно будет решить и с помощью события $routeChangeStart, но на данный момент приведённый код выполняет эту работу в полном объёме.
Вы можете увидеть пример работы этого кода в приложении управления клиентами, которое доступно на Github (особенно шаблон customerEdit). Изучайте больше о приложении тут.

Only registered users can participate in poll. Log in, please.

Интересны ли Вам переводы статей из блога Dan Wahlin?

  • 91.4%Да180
  • 8.6%Нет17
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 15

    0
    Всё что касается Angular, достойно тихой благодарности. Раньше даже не задумывался о том, что пользователь может перейти на другую страницу не дождавшись длинных процедур. Спасибо.
      +4
      Ну не знаю, иногда архитектура ангулара просто выносит мозг. Особенно при переходе с традиционных решений.
        0
        Традиционные решения — это jQuery?
        Сам изучал angular попутно с js (любовь к нему появилась из-за angular), так вот с angular сложности не было, а вот возможности js крышу как раз таки сносят(после java). Но они замечательны!
          0
          Извините, но причем тут DOM-манипулятор jQuery? Это как коров с стульями сравнивать.
            0
            Я во front-end раньше не варился, понятия не имею что может быть традиционным решением кроме спагетти из js+jQuery. О том и вопрос.
              0
              Ну так бы и написали — JS-Спагетти, а до сих пор бывает и без jQuery :)
          0
          А какие это — традиционные решения?
        0
        Примерчик бы… По типу как для uiRouter github.com/tamtakoe/angular-demos/blob/master/delayed_routing/js/app.js
          0
          В разделе выводы есть ссылка на гит репозиторий примера приложения.
            0
            Видел, но лень копаться в куче кода :)
          0
          Ничего против Angular, но именно такую плюшку проще сделать на чистом javascript:
          window.onbeforeunload = function () { return "You have unsaved changes. Leave the page?"; }
          

            0
            Приложения AngularJS — это одностраничные приложения, которые отрисовывают вид в HTML дереве (div например). Соответственно, в одностраничных приложениях вызов этой функции производиться не будет.
              0
              Кто же тогда меняет URL? Если приложение само, то зачем городить огород, когда можно просто перед сменой URL проверить изменения и простым confirm диалогом спросить тоже самое.
              Если все таки пользователь, то вызов event'а «beforeunload» происходит как всегда…
              PS. Что такое Angular я в курсе.
                0
                Навигация в контексте Angular происходит не изменением URL, а её части, находящейся после хэш-тега. Я проверил, и в данном случае эта функция не вызывается. Возможно это сработает в режиме html5mode, но сомнительно, ведь в данном случае придётся перезагружать всё окно(скрипты, стили), и приложение уже не будет одностраничным?
                Если происходит переход пользователем из-за изменения адресной строки — то да, ваш подход должен сработать, но, как я понимаю, без адаптации Вашего кода в Angular контекст — это антипаттерн.
                В итоге — да, приложение само меняет часть URL после хэш-тега, а данная статья как раз и показывает, как это сделать средствами приложения.
                0
                Хотите сказать, что AngularJS — отбирает у пользователя возможность нажать на кнопку F5? =)

                Правильным ходом было бы обработать оба случая.

            Only users with full accounts can post comments. Log in, please.