Angular.js: Опция $location, позволяющая изменить хеш/путь без перезагрузки маршрута

Созданный на 12 дек. 2012  ·  194Комментарии  ·  Источник: angular/angular.js

В нашем приложении есть парадигма, согласно которой пользователи создают новые вещи на той же странице, на которой они просматривают вещи. Итак, наш маршрут похож на /thing/:id. При создании новой вещи они идут в /thing/new. Как только вещь будет успешно сохранена, мы хотим изменить маршрут на /thing/1234 (или на любой другой его новый идентификатор). Теперь частичный не нужно перезагружать, потому что все данные одинаковы. Мы просто хотим, чтобы путь был обновлен, чтобы пользователь теперь мог добавить в закладки правильную ссылку и т. д.

Наличие опции в $location (не в определении маршрута) для включения/отключения загрузки маршрута будет работать, но я уверен, что есть другие способы реализовать эту функцию.

Lots of comments $location ngRoute high confusing feature

Самый полезный комментарий

Решение состоит в том, чтобы добавить дополнительный параметр в $location.path().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

А затем просто используйте вот так

$location.path('/path/that/you/need', false);

Все 194 Комментарий

+1, такая же проблема.

@cgross Поскольку служба $location использует цепочку методов в стиле JQuery, и все команды в любом случае откладываются до $digest, я предлагаю добавить новый метод цепочки, например, reload(boolean):

$location.path("/thing/"+thing.id).replace().reload(false)

PS Это тот самый Крис Гросс, который начал проект Eclipse Nebula?

Ага :)

+1 :+1:

+1

Прямо сейчас мы используем грязный хак, чтобы обойти эту проблему.

    $scope.$on('$locationChangeSuccess', function(event) {

        // Want to prevent re-loading when going from /dataEntry/1 to some other dataEntry path
        if ($route && $route.current && $route.current.$route.templateUrl.indexOf('dataEntry') > 0) {
            $route.current = lastRoute; //Does the actual prevention of routing
        }
});

+1

+1

+1

+1

+1

Вот моя попытка исправить это, поддержав preventDefault() за $routeChangeStart . Что ты об этом думаешь?
Фиксация: https://github.com/redhotsly/angular.js/commit/f8ac46e6ac9d76cf855077d21b68b4c2c8043db1

Я работал над подходом notify который позволяет один раз пропустить уведомление об изменении местоположения. Это должно избежать любого изменения маршрута или перезагрузки.

Это не затрагивает систему маршрутизации, поэтому вам не нужно менять определения маршрута. Он по-прежнему будет работать, даже если вы используете ui-router вместо стандартной системы маршрутизации AngularJS.

Пример: $location.path('/client/2').replace().notify(false);

Я также работал над некоторыми тестами и документацией.

+1

еще +1
Уже используется @lrlopez . Работает как шарм.

+1

+1

К сожалению, PR https://github.com/angular/angular.js/pull/2398 был отклонен командой Angular, поскольку пропуск уведомлений может привести к несоответствию между фактическим URL-адресом и текущим маршрутом. В конце обсуждения есть длинное объяснение.

Мне не нужна эта функция ни в одном из моих проектов, но я сохраню ее в своем репозитории, чтобы при желании ее можно было объединить. Просто дайте мне знать, если он не синхронизируется с основной веткой, чтобы я мог перебазировать изменения. Спасибо!

+1

+1, нужно это :(

+1

Изменить: здесь лучше подойти: https://github.com/angular/angular.js/issues/1699#issuecomment -22511464

Я создал многоразовую фабрику, чтобы обойти это (на основе идеи @shahmirn):

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

Как пользоваться:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

Источник: http://stackoverflow.com/a/16496112/990356

Если вы установите для «reloadOnSearch» значение false на маршруте, то, похоже, это исправит изменения хэша, перезагружая всю страницу.

+1

Это было бы здорово для мобильных приложений, где жизненный цикл создания/удаления ng-view не подходит (убивает производительность и удобство использования).

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

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', '$rootScope', function($route, $rootScope) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $rootScope.$broadcast('locationChangedWithoutReload', $route.current);
        $route.current = lastRoute;        
      }
    });
  };
}]);

Потом:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);

  $rootScope.$on('locationChangedWithoutReload', function(event, route) {
    // set location specific breadcrumbs
    $scope.breadcrumbs = assembleBreadCrumbs();

    // do something for specific route
    if (route.$$route.action == 'item') {
      $scope.item = $scope.items[params.itemId];
      $rootScope.title = $scope.item.name;
    }
  });
}]);

У меня та же проблема, что и у автора темы, и я создал модифицированную версию решения, описанного выше.

app.factory('skipReload', [
    '$route',
    '$rootScope',
    function ($route, $rootScope) {
        return function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        };
    }
]);

Использование:

app.controller('ThingCtrl', ['$scope', '$http', '$location', 'skipReload', function ($scope, $http, $location, skipReload) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            skipReload();
            $location.path('/thing/' + id).replace();
        });
    };
}]);

Использование постоянного прослушивателя событий, который проверяет templateUrl, чтобы решить, разрешить ли перезагрузку маршрута, может нарушить маршрутизацию к другим представлениям, использующим тот же templateUrl. Например, я перешел к /thing/new, и в представлении отображается ссылка на последнюю добавленную вещь /thing/5, я не смогу перейти к вещи 5 и к любой другой вещи (даже явно изменив URL-адрес в браузере). Другая проблема с тестированием templateUrl заключается в том, что шаблоны могут быть указаны с использованием свойства шаблона (в виде строки или функции).

Этот хак позволяет пропустить ровно одну перезагрузку маршрута и не ломает маршрутизацию.

Также я пробовал менее подробное (для использования в контроллере) решение:

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        $location.skipReload = function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
            return $location;
        };
        return $location;
    }
]);
app.controller('ThingCtrl', ['$scope', '$http', 'location', function ($scope, $http, location) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            location.skipReload().path('/thing/' + id).replace();
        });
    };
}]);

Кажется, работает нормально.

+1

@sergey-buturlakin твой $location.skipReload просто замечательный! Я использую его вместо DoNotReloadCurrentTemplate

@sergey-buturlakin sergey-buturlakin Похоже, ваше решение работает для меня. Спасибо!

после запуска skipReload() с https://github.com/angular/angular.js/issues/1699#issuecomment -22511464 перезагрузка остается отключенной
похоже, un() следует вызывать позже

Он предотвращает любую следующую перезагрузку и должен вызываться непосредственно перед изменением пути.
Когда изменение пути закончится - будет вызвано "un".

да, но я хотел пропустить перезагрузку только один раз, однако предложенный подход предотвращает дальнейшую перезагрузку

+1

+1

+1

PR https://github.com/angular/angular.js/pull/2398 пропускает перезагрузку только один раз. Я не знаю, есть ли у него конфликты слияния с текущим мастером. Если это так, просто напишите мне, и я все исправлю.

Я использую решение сергея, при этом отбрасывая бесполезную (по моему мнению) переменную "un" и вызывая.

Это ведет к:

  angular.module('services.locationChanger', [])
    .service('locationChanger', ['$location', '$route', '$rootScope', function ($location, $route, $rootScope) {

        this.skipReload = function () {
            var lastRoute = $route.current;
            $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
            });
            return this;
        };

        this.withoutRefresh = function (url, doesReplace) {
            if(doesReplace){
                $location.path(url).replace();
            }
            else {
                $location.path(url || '/');
            }
        };
    }]);

и любой вызывающий абонент:

 locationChanger.skipReload().withoutRefresh("/", true);

+1

+1

+1

+1

@ mica16 Разве переменная un используется для отмены привязки события?

Я создал два plunkrs для тестирования, с и без на un переменной развязывать событий и, кажется , что без него событие продолжает стрелять снова и снова

Да, действительно, переменная un звучит странно.
По словам первоначального автора, это будет считаться полезным ... но я не уверен, что это идеальное решение.

Я предпочел переписать его, но выбросил эту функцию, так как больше ее не жду.

Так что продолжайте, если ваш модульный тест может убедиться, что он работает так, как вы ожидаете :)

Вы правы ребята
Мое решение было

app.factory 'location', ['$location', '$route', '$rootScope', '$timeout', ($location, $route, $rootScope, $timeout) ->
  $location.skipReload = ->
    lastRoute = $route.current
    un = $rootScope.$on('$locationChangeSuccess', ->
      $route.current = lastRoute
    )
    $timeout((->
      un()
    ), 1000)
    $location

  $location
]

затем в контроллере

location.skipReload().path('/new/path').replace()

+1

+1

+1

+1

другой способ сделать это - вывести его из текущего "шага" javascript, чтобы маршрут не знал об изменении, внесенном в window.location:

setTimeout(function() {
    window.location.hash = "";
    if (typeof (history.pushState) !== "undefined") {
        history.pushState("", document.title, location.pathname);
    }
}, 0);

+1

+1

+1

+1

Как насчет использования $anchorScroll? Я порылся в сети и обнаружил, что для этого есть несколько инструментов. По крайней мере, у меня это работает.

Вы должны проверить это: http://docs.angularjs.org/api/ng. $anchorScroll

+1

Это плохая идея? Признаюсь, я не тестировал его широко. Мне не понравилась идея вызова отдельного метода перед установкой пути. По крайней мере, с точки зрения использования это кажется чище.

Я расширил встроенную функцию $location.path, используя технику Сергея.

App.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
        var original = $location.path;
        $location.path = function (path, reload) {
            if (reload === false) {
                var lastRoute = $route.current;
                var un = $rootScope.$on('$locationChangeSuccess', function () {
                    $route.current = lastRoute;
                    un();
                });
            }

            return original.apply($location, [path]);
        };
    }])

Использование:

$location.path('/my/path', false);

+1

Решения @EvanWinstanley работают для меня, спасибо!

+1

Исправление @EvanWinstanley/sergey выглядит красиво и не перезагружает контроллер, но при изменении маршрута снова запрашиваются разрешения.

Моим основным вариантом использования для этого было бы ручное изменение $location.path без перезагрузки разрешений/контроллера, чтобы я мог выполнять кэширование.

Например: у меня есть выбор, который позволяет пользователю выбирать между несколькими приложениями, что также изменяет $location.path для более удобных URL-адресов. Когда пользователь выбирает приложение, результаты вызова API кэшируются. Даже с исправлением он всегда снова выполняет разрешение, что не нужно, поскольку результаты уже кэшированы.

+1

Решение @sergey-buturlakin отлично работает для меня, оно не перезагружает контроллер, но все еще перезагружает функцию reslove и отправляет запрос на сервер.

+1

Решение @EvanWinstanley у меня не работает. С другой стороны, решение @mica16 работало слишком хорошо... (кнопка «Назад» не обновляла страницу). Я безуспешно пытался написать свою версию :(.

+1

+1

+1

+1
Пожалуйста, поддержите: $location.reload();

@jvmvik ты пробовал $route.current.reload() ?

7.1.1

нет новостей об этом спустя более года?

+1

+1

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

// Original: https://github.com/angular/angular.js/issues/1699#issuecomment-22511464
//
// Usage:
//
// (interception is needed for Back/Forward buttons to work)
//
// location.intercept($scope._url_pattern, function(matched) {
//   // can return false to abort interception
//   var type = matched[1]
//   if (!type) {
//     return;
//   }
//   $scope.safeApply(function() {
//     $scope.data_type = type; 
//     $scope.params.page = 1; 
//     $scope.get_data(); 
//   });
// });
// 
// anywhere in your controller: 
// location.skipReload().path(url);
//
// to replace in history stack:
// location.skipReload().path(url).replace();

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern)
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

Другим обходным путем для предотвращения перезагрузки маршрута при изменении пути $location является установка текущего маршрута pathParams в соответствии с новым путем к местоположению.

В моей конфигурации $routeProvider у меня есть маршрут к /tasks следующим образом

when('/tasks/:section/:taskId?', {
      templateUrl: 'partials/tasks.html',
      controller: 'TaskCtrl',
      reloadOnSearch: false
      })

Приведенный выше маршрут имеет два раздела pathParams и идентификатор задачи (taskId является необязательным), а для параметра reloadOnSearch установлено значение false.

Чтобы перейти от, скажем, /tasks/inbox к /tasks/inbox/122 без перезагрузки маршрута, установите pathId pathParam перед изменением пути $location следующим образом.

$scope.showTask = function(task) {
    // set taskId to prevent route reloading
    $route.current.pathParams ['taskId'] = task.id;
    $location.path('/tasks/'+$route.current.pathParams ['section']+'/'+task.id);
};

Обратите внимание, что раздел pathParam не нужно задавать, так как он уже задан маршрутом.

Когда маршрут обнаруживает, что pathParams, извлеченный из нового пути $location, соответствует текущему pathParams маршрута (и для reloadOnSearch установлено значение false), он не будет перезагружать маршрут, вместо этого будет запущено событие $routeUpdate.

Чтобы вернуться из /tasks/inbox/122 в /tasks/inbox без перезагрузки маршрута, удалите pathId pathParam перед изменением пути $location следующим образом.

$scope.back = function () {
    // delete taskId to prevent route reloading
    delete $route.current.pathParams ['taskId'];
    $location.path('/tasks/'+$route.current.pathParams ['section']);
};

+1

+:100:

Вы читали статью Дерика Бейли о маршрутизаторе Backbone http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ ? Backbone предоставляет эту опцию, и, по словам Дерика, вы всегда должны избегать запуска перезагрузки (это кажется противоположным тому, что здесь делает маршрутизатор angular).
К этому посту 48 "+1"... Не думаю, что все эти люди делают что-то не так. Необходимость хранить var lastRoute = $route.current; и восстанавливать его позже на $locationChangeSuccess с помощью $route.current = lastRoute; кажется довольно хакерской.
@IgorMinar, у тебя есть какие-нибудь мысли?

Хорошо, я владею этой темой на следующей неделе, так что мы можем, наконец, закрыть ее.

Прежде всего, я не думаю, что маршрут должен перезагружаться при изменении значения #hash.

website.com/#/my-route#top

website.com/#/my-route#bottom

Поэтому, когда верхняя часть изменяется на нижнюю, она не должна перезагружаться, но если значение my-route изменяется, она должна перезагружаться. Мы все можем согласиться с этим, верно?

Во-вторых, что более важно, нам нужен простой способ указать, что путь маршрута не будет перезагружаться при изменении, но это становится проблематичным, если изменяется более ранний фрагмент маршрута. Так почему бы нам не использовать другое обозначение для параметра маршрута, используя два двоеточия :: чтобы указать, что он действует как подстановочное значение.

// this will reload the page when the id changes like normal
$routeProvider.register('/users/:id')

// this will reload the page when the id changes, but not the section (since two colons are used)
$routeProvider.register('/users/:id/::section')

Что вы думаете, ребята?

Это не очень помогает в ситуации, когда вы хотите перейти от:

/users/new

к

/users/123

как только новый пользователь будет создан и ему будет присвоен идентификатор, без перезагрузки пользователя, поскольку он уже существует.

Потому что каким-то образом нужно сопоставить новый пользовательский объект с текущим пользовательским объектом с идентификатором 123. Это скорее зависит от приложения, нет?

Как бы то ни было, Ember Router также не поддерживает изменение URL без перехода:

http://stackoverflow.com/questions/19076834/change-url-без-перехода

@matsko Можем ли мы просто уполномочить инженера и позволить ему или ей решить, должен ли маршрут вызвать изменение или нет?
Я в порядке с хорошими значениями по умолчанию.
Создайте способ, чтобы инженер мог изменить URL-адрес, не запуская всю цепочку событий перезагрузки. Лично я согласен с добавлением необязательных дополнительных параметров с параметрами (например: {reload: false}) в «.path()», «.search()» и т. д.

@ChrisCinelli да, но что происходит, когда в определении маршрута есть два параметра? Как вы можете предотвратить перезагрузку одного, позволяя другому? Флаг был бы функцией «все или ничего».

@matsko Итак, что

@cgross — выберите «да» (используя двойное двоеточие). Пока это только идея, так что ничего конкретного. Если это соответствует планам для angular 2.0, тогда мы сможем использовать его или попытаться использовать что-то, что планируется иметь в 2.0, или что-то, что уже есть в Dart (чтобы сохранить согласованность API).

Я думаю, что это было бы идеально и хорошо смотрелось бы (по крайней мере, ИМО) как часть маршрута.

$routeProvider,when("/:categoryName/:id[reload=no]", { ... })

А потом в контроллере

$scope.$on("$routeChangeSuccess", function () {
  if ($routeParams.id !== $scope.activeId) {
    // React to ID update without reload
  }
});

Да, что-то в этом роде, но вместо :id[reload=no] будет использоваться двойное двоеточие '::id'.

Имейте в виду, что это всего лишь идея, но многим здесь нужна эта функция.

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

Мацко +1 за двойное двоеточие, но я думаю, что было бы также полезно иметь возможность передавать параметры в path() также для некоторых крайних случаев, упомянутых выше.

+1 за двойное двоеточие

@matsko попросил меня оценить, как мы справляемся с этим в маршрутизаторе Angular 2.0. Я знаю, что это не поможет тем из вас, кому это нужно для ng1.x, но я подумал, что это может быть вам интересно.

Новый маршрутизатор имеет метод navigate которому вы можете передать _url_ и необязательный объект _options_. Это основано на возможностях истории маршрутизатора Backbone, поэтому он поддерживает два варианта: replace и trigger Если вы хотите перейти от /users/new к /users/123 без вызывая экранную навигацию, вы просто указываете trigger:false . Если вы также хотите, чтобы он заменил запись истории /users/new новым URL-адресом на основе идентификатора, вы также должны указать replace:true .

Обновление маршрутов с _new_ маршрута на _id-based_ маршрут без преобразования DOM и т. д. является ключевым вариантом использования этого API. Другой распространенный пример — разбиение на страницы. Например, если у вас есть сетка и вы хотите, чтобы пользователь пролистывал данные, вы можете использовать ее для обновления фрагмента информацией о текущей странице, не вызывая навигации. Я уверен, что есть и другие применения, но это те, которые я неоднократно видел в своих консультациях на протяжении многих лет.

Надеюсь, эта информация поможет. Возможно, маршрутизатор 1.x сможет использовать эту технику?

@EisenbergEffect, чтобы подтвердить. Для 1.x способ сделать это без изменения пути будет выглядеть так:

$location.path(value, { trigger : false })

И это эффективно заменит путь, не заставив новый маршрут + контроллер узнать. На данный момент это может сработать, но как вы относитесь к идее с двойным двоеточием :: ?

Что касается всех остальных здесь, вы бы предпочли, чтобы изменение URL-адреса (без перезагрузки маршрута) облегчалось через маршрут или разработчиком при изменении URL-адреса?

Должен быть центральный источник достоверной информации о местоположении, например служба, которая
маршруты и браузеры могут ссылаться и влиять на них, но остаются единственным
правда
1 июля 2014 г., 23:28, «Матиас Ниемела» [email protected] написал:

@EisenbergEffect https://github.com/EisenbergEffect, чтобы подтвердить. За
1.x способ сделать это без изменения пути будет выглядеть так:

$location.path(значение, {триггер: ложь})

И это эффективно заменит путь, не вызывая нового маршрута.

  • контроллер, чтобы быть в курсе. Это может сработать на данный момент, но как вы себя чувствуете
    об идее двойного :: двоеточия?

Для всех остальных здесь вы бы предпочли, чтобы URL-адрес изменился (без
перезагрузка маршрута) может быть облегчена через маршрут или разработчиком
когда URL-адрес изменен?


Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/angular/angular.js/issues/1699#issuecomment-47741350 .

Вот еще одна идея... Разрешить два новых метода для каждого определения маршрута, скажем, entering(transitionInfo) и leaving(transitionInfo) или что-то в этом роде. Если они существуют, то метод leaving будет вызываться на старом маршруте перед уходом, а затем метод entering() будет вызываться на новом маршруте. transitionInfo будет объектом, который выглядит следующим образом:

{
  newUrl: ...
  newRoute: ...
  oldUrl: ...
  oldRoute: ...
  cancelRouteChange: false,
  ignoreRouteChange: false
}

В этом методе мы можем проверить старые и новые маршруты / URL-адреса, а затем, если вы хотите, чтобы URL-адрес изменился, но не запускал повторно переход маршрута, вы устанавливаете ignoreRouteChange на true . Если вы хотите отменить изменение маршрута и вернуться к предыдущему маршруту, установите для параметра cancelRouteChange значение true.

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

Я считаю, что в маршрутизаторе AngularV2 есть что-то подобное под названием activate() . @EisenbergEffect можете подтвердить?

Что вы думаете?

@matsko Да, этот API может выглядеть так. Вы также можете рассмотреть возможность реализации опции replace , так как она тоже часто требуется. В этом конкретном случае от /users/new до /users/123 я бы подумал, что вы должны использовать оба варианта, поскольку на самом деле вы не хотите, чтобы /users/new оставался в заднем стеке после того, как новый пользователь созданный.

Что касается :: , я не уверен, что это полностью полезно. Причина в том, что в приведенном выше примере пользователей важен не шаблон URL, а конкретный контекст приложения. Например, если вы переходите с customers на users/123 вы хотите уведомить. Кроме того, если вы откажетесь от users/new и перейдете к users/456 вы также захотите уведомить об этом. Только когда вы находитесь в users/new и завершили создание нового пользователя, вы хотите просто обновить фрагмент, чтобы представить идентификатор вновь созданного пользователя, не вызывая никакого перехода. Это сильно зависит от того, что пользователь на самом деле делает в контексте этого пользовательского экрана в данный момент времени.

@petebacondarwin Одна из проблем с этой идеей заключается в том, что информация о разрешении уведомления о маршруте не является чем-то глобально доступным вместе с определениями маршрута. Скорее, он встроен в конкретный пользовательский контроллер, поскольку этот контроллер представляет собой фрагмент кода, который знает, действительно ли в данный момент создается новый пользователь, а не просто переходит от экрана нового пользователя к уже существующему пользователю.

Для меня путь - дать контроль вызывающему абоненту, который хочет изменить маршрут. Так что я однозначно за два варианта replace и trigger .

@EisenbergEffect - вы правы. Как правило, эта информация недоступна сразу, хотя я бы представил это следующим образом:

У вас есть служба с именем currentUser , которая содержит пользователя, с которым мы сейчас работаем. Обычно мне нравится делать это, потому что мне неудобно перегружать контроллеры кодом. Если это кажется слишком специфичным, вы можете иметь currentEntity , что является текущей «вещью», которую вы редактируете/просматриваете.

Затем метод маршрута enter() сможет внедрить это и решить, действительно ли переход к этому маршруту изменит текущий объект. Если нет, то мы не будем запускать переход, а просто обновим URL.

Я понимаю, что это может стать немного громоздким в большом приложении, но я решил добавить его туда как вариант.

Каковы аргументы против предоставления вызывающей стороне (то есть тому, кто запрашивает изменение маршрута/URL) контроля над тем, действительно ли происходит переход маршрута? Я помню, что @IgorMinar был обеспокоен тем, что это нарушило ограничение, согласно которому одно состояние приложения напрямую сопоставлялось с одним URL-адресом, и что изменение URL-адреса без повторного запуска маршрута потенциально может привести к тому, что приложение будет находиться в другом состоянии в зависимости от путь, по которому вы прибыли туда.

@petebacondarwin Это может сработать для этого конкретного сценария, но становится немного сложнее обработать пример разбиения на страницы, который я упомянул выше, который хотел бы не запускать и не заменять, в то время как новый пользовательский сценарий хочет не запускать, а заменять.

Для решения вопроса о состоянии немного сложнее. Причина в том, что у нас есть два URL-адреса, которые представляют одно и то же состояние контроллера... но разница в модели. Итак, на самом деле у нас есть два разных состояния в реальности... но с точки зрения UX мы не хотим, чтобы пользователь встряхивался при переходе. С точки зрения производительности мы не хотим без нужды выбрасывать DOM и создавать все заново. Таким образом, в случае нового пользователя => сохраненный переход пользователя мы действительно хотим избежать срабатывания, а также заменить фрагмент, чтобы кнопка «Назад» _не_ возвращала вас на экран нового пользователя. Внутри маршрутизатору или механизму определения местоположения нужно будет просто отслеживать текущий фрагмент. (Я делаю небольшое предположение, так как не знаю, как работает ng1.x в этом отношении. В 2.x мы тщательно отслеживаем текущий/предыдущий фрагмент независимо от того, как он был изменен.) сценарий разбивки на страницы, мы не хотим запускать его, потому что нам нужен приятный UX для просмотра данных и мы не хотим ненужного удаления DOM. Но мы ДЕЙСТВИТЕЛЬНО хотим сохранить предыдущий фрагмент, чтобы пользователь мог использовать кнопку «Назад» для перемещения назад по страницам данных.

Обратите внимание, что в обоих этих случаях речь идет не только об URL-адресе и состоянии приложения, но и о том, как мы переходим из текущего состояния в это новое состояние. Ничего из этого не имеет значения, если вы напрямую ссылаетесь на новое представление. Это имеет значение, когда вы переходите от одного к другому в контексте этих очень конкретных случаев.

+1

+1

+1

+1
А также возможность изменить URL без изменения маршрута

+1 - приятно видеть, что я не один этого хочу!

+1, еще не добавил(

+1

+1

+1

+1

+1

+1

+1

:+1:

Ты тоже @basarat? :улыбка:

У @johnnyreilly просто есть служба, в которую вы вводите $route и $location и у вас может быть эта функция:

/** navigate */
    public navigate(url: string, reload = true) {
        this.$location.url(url);

        // Don't do a controller reload on this navigation. Note: *you* need to be careful not to actually *need* a controller reload
        if (!reload) {
            var lastRoute = this.$route.current;
            var unsub = this.$rootScope.$on('$locationChangeSuccess', () => {
                this.$route.current = lastRoute; // fake this to make the route logic think nothing changed
                unsub();
            });
        }
    }

Хороший @basarat!

+1

+1

Одна вещь, которую я замечаю с текущими решениями, заключается в том, что мой блок разрешения маршрутизатора все еще выполняется. Кто-нибудь еще это замечает? Было достаточно легко передать что-то в объекте $route.current, но это кажется хакерским. Кроме того, похоже, что эта проблема никогда не будет решена? :плакать:

+1

+1

Для тех из вас, кто не хочет прокручивать назад... насколько я могу судить, это то, что у нас есть на данный момент (извините, coffeescript). К сожалению, это не работает на 100% для меня. Я все еще выполняю свой resolve и кнопки назад/вперед теперь кажутся сломанными.

#
# Special $location.path function that allows you to change the url, but not
# send things through the router.
# <strong i="7">@see</strong> https://github.com/angular/angular.js/issues/1699
#
@app.run(['$rootScope', '$location', '$route', '$timeout', ($rootScope, $location, $route, $timeout) ->

    originalPath = $location.path

    $location.path = (url, reload=true) ->
        if ! reload
            lastRoute = $route.current
            if lastRoute
                unsub = $rootScope.$on('$locationChangeSuccess', ->
                    $route.current = lastRoute # fake this to make the route logic think nothing changed
                    $route.current.ignore = true
                    unsub()
                )
                # make sure to clean up and unregister the unsub function
                $timeout( ->
                    unsub()
                , 500)

        originalPath.apply($location, [url])

])

@lookfirst вы звоните unsub дважды. Предпочтение: https://github.com/angular/angular.js/issues/1699#issuecomment -54931571

@basarat пожимает плечами ... Я добавил это, потому что обнаружил, что unsub() не всегда

unsubbed = false
unsub = ... ->
    if ! unsubbed
        ...
        unsubbed = true
        unsub()

$timeout(unsub, 500)

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

потому что я обнаружил, что unsub() не всегда вызывается в вашем предпочтительном примере

@lookfirst вы используете location.path Я использую location.url . Это может повлиять на это.

Просто придирки (мне нравится быть полезным): вместо originalPath.apply($location, [url]) вы можете сделать originalPath.call($location, url) (вы, возможно, уже знаете это)

Интересно... location.url работает намного лучше (кнопка «Назад» снова работает!), но, похоже, вызывает еще одну проблему с моей стороны. Мне придется покопаться в этом подробнее, когда я не буду кодировать 10 часов подряд. Спасибо за подсказки и обсуждение, правда!

+1

+1

+1

@kuchumovn Ваше решение отличное. Одна проблема, с которой я столкнулся, заключается в том, что $routeParams не меняется после изменения пути. Я решил это, заменив значение $routeParams внутри прослушивателей событий. Я не уверен, что это правильный путь, но он работает для меня.

app.factory('Location', [
    '$location',
    '$route',
    '$rootScope',
    '$routeParams',
    function ($location, $route, $rootScope, $routeParams) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern);
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

Вчера я лично разговаривал с

почему вы хотите просто слить ui-router в angular?
Маршрутизатор uui, кажется, решает все, что может понадобиться.

согласен с @elennaro. хотя ui-router требует некоторого творчества и изучения, чтобы заставить его работать, он помогает в каждом сценарии, с которым я сталкивался. любопытно, каковы недостатки использования ui-router и использования ресурсов Google?

Похоже, что ни одно решение не будет выполнять решение, есть идеи?

+1

+1

+1 за $location.path(value, { trigger : false })
Есть ли прогресс в этом?

+1

+1

+1 как насчет совершенно отдельного метода? оставить path() как есть и просто добавить метод $location.hash, который просто изменяет URL-адрес браузера без перезагрузки контроллера. это позволяет нам контролировать обратную навигацию браузера и является допустимым сценарием использования в многофункциональных приложениях.

+1

В настоящее время используется фрагмент здесь, на этой странице: http://joelsaupe.com/programming/angularjs-change-path-without-reloading/

+1

+1

+1

Обходной путь, который я нашел, заключался в том, чтобы добавить дочернее состояние с абсолютным URL-адресом.

$stateProvider.state('state.child', {
    url: '^/some-other-url'
});

Затем просто выполните $state.go('.child'), и это должно изменить URL-адрес на тот, который вы хотите, без перезагрузки и сделать всех счастливыми. Обратите внимание на каретку, которая указывает, что URL-адрес не будет наследоваться от родителя... мне потребовалось некоторое время, чтобы понять это.

+1

+1

+1

+1

+1

+1

+1

@btford, как это решается (если вообще) в componentRouter ?

Спасибо @balsarori за простое рабочее решение .

Доработано поверх него, чтобы позволить обновлять параметры пути $route без перезагрузки контроллера.

Он использует console.log вместо $log поскольку мы можем просто удалить все журналы для производственных сборок с помощью uglifyjs.

Изменить: исправить при использовании в проекте:

/**
 * parameter handling
 */

'use strict';

angular.module('route-params', [
  'ng',
  'ngRoute',
])
.service('routeParams', function(
  $route,
  $location
) {
  var R = {};

  /**
   * <strong i="15">@ngdoc</strong> method
   * <strong i="16">@name</strong> routeParams#replace
   *
   * <strong i="17">@description</strong>
   * Replace route params, including path params, without reloading controller.
   *
   * Causes `$route` service to update the current URL, replacing
   * current route parameters with those specified in `params`.
   *
   * Provided property names that match the route's path segment
   * definitions will be interpolated into the location's path, while
   * remaining properties will be treated as query params.
   *
   * If `options.reload` is truthy, the current controller is reloaded.
   *
   * If `options.history` is truthy, the browser history is updated.
   * If `options.history` is undefined, the browser history is only updated
   * when changing value of any existing `$route` parameter,
   * ie. not when adding a previously undefined `$route` parameter.
   *
   * <strong i="18">@param</strong> {!Object<string, string>} params mapping of URL parameter names to values
   * <strong i="19">@param</strong> {!Object<string, boolean>} options `history` and `reload` options.
   *
   * <strong i="20">@returns</strong> {Object} self
   */
  R.replace = function(params, options) {
    var key, value, updateRequired;
    options = options || {};

    // Convert params to strings, and check if they differ from current route params.
    // If `options.history` is undefined, and passed params update any current route params, then set it to true.
    var currentParams = $route.current.params;

    console.log('route: params: replace', {last: angular.copy(currentParams), next: params,
      route: $route.current, options: options});

    if (!$route.current.$$route) {
      // Can't change location.
      return;
    }
    var currentPath = $route.current.$$route.originalPath;
    var history = options.history;

    for (key in params) {
      // jshint eqnull: true
      value = params[key] = (params[key] == null ? undefined : params[key] + '');

      if (value !== currentParams[key]) {
        updateRequired = true;

        console.log('route: params: replace: ' + (currentPath.search(':\\b' + key + '\\b') ? 'path.' : 'search.') +
          key + ' = ' + (key in $route.current.params ? $route.current.params[key] + ' -> ' : '') + params[key]);

        if (history === undefined && key in currentParams) {
          console.log('route: params: replace: update history: ' + key + ' = ' + currentParams[key] + ' -> ' + value);
          history = true;
        }
      }
    }

    if (updateRequired) {
      // If `options.reload` is falsey, then set current route `reloadOnSearch` to false,
      // and make passed path params equal with current route path params,
      // so that `$route` treats the change as update only, and does not broadcast `$routeChangeStart` event.
      //
      // See https://github.com/angular/angular.js/issues/1699#issuecomment-45048054
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L484
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L539
      if (!options.reload) {
        // Set current route `reloadOnSearch` to false.
        $route.current.$$route.reloadOnSearch = false;

        // Add any passed path params to current route path params, and convert them to strings.
        // Path params are detected by searching for respective name group `:key[*?]` in current route path.
        for (key in params) {
          if (currentPath.search(':\\b' + key + '\\b') !== -1) {
            $route.current.pathParams[key] = params[key];
          }
        }

        // Add any current route path params to passed params, if not set there already.
        for (key in $route.current.pathParams) {
          if (!(key in params)) {
            params[key] = $route.current.pathParams[key];
          }
        }

        // TODO: push state if `options.history` is truthy.
      }
      // If `options.history` is falsey, and controller is reloaded,
      // then make `$location` replace current history state, instead of pushing a new one.
      else if (!history) {
        $location.replace();
      }

      $route.updateParams(params);

      // Update current route params, so the change is reflected immediatelly,
      // and nearby replace() call work correctly.
      for (key in params) {
        $route.current.params[key] = params[key];
      }
    }

    return R;
  };

  return R;
});

Каков текущий статус этого вопроса? Это будет решено в новом роутере? Вовремя для выпуска 1.4? Скоро?

У меня такая же проблема, но с параметрами строки запроса. Я не хочу перезагрузки при их изменении. Но я не могу отключить reloadOnSearch потому что мне нужно знать, меняет ли пользователь строку запроса вручную! :)

Спасибо!

@marcalj между прочим, мы взломали это решение с помощью службы, которая синхронизировала URL-адрес с адресной строкой, чтобы мы могли отключить reloadOnSearch и получить параметры запроса. кроме того, пользователи могут перемещаться по URL-адресу напрямую через закладку или ссылку.

С ng-маршрутизатором или ui-маршрутизатором? Можете ли вы предоставить мне ссылку с кодом или что-то в этом роде?

Возможно, это можно решить с помощью нового маршрутизатора, я могу подождать, так как мой проект еще не завершен.

Спасибо!

@marcalj наш пример немного запутан и, возможно, устарел, но посмотрите на https://github.com/irthos/mngr/blob/master/app.js и проверьте строку 10. он использует наш метод api.loadState, который идентифицирует параметры в URL для построения правильного состояния.

NgSilent был решением для меня.
Посмотрите https://github.com/garakh/ngSilent
Протестировано с ngRoute 1.3.15

Я использовал решения @sergey-buturlakin и @kuchumovn и постоянно сталкиваюсь с одной и той же проблемой.

На рассматриваемой странице (pagex) я использую location.skipReload, и он отлично работает — я могу обновлять путь снова и снова без изменения маршрута. Если я затем использую фактический $location.path для изменения маршрута от pagex к новому контроллеру, это сработает, но если я нажму кнопку «Назад», чтобы вернуться к pagex, а затем попытаюсь перейти на другую страницу, я больше не могу изменить мой маршрут больше, не обновляя браузер. Это можно обойти?

+1

+1. Мне нравится подход с двойным двоеточием, который предложил Матиас

+1

Обычно одностраничное приложение не перезагружает страницу при изменении пути,
очень странно, почему он еще не встроен в angular.
сделал плагин на основе решения
https://github.com/anglibs/angular-location-update

Вернемся к этому в 1.5. Есть соответствующий PR в #5860

Мне нужна та же функция для обновления строки запроса без запуска контроллера маршрута! :)

Что-то вроде: $location.searchSilent('sf', SearchFilter.getForRouteParams());

$location.path(href);
область. $ применить();
это решение!!
нам нужно сообщить об изменении с помощью $apply()

Этот поток не был открыт в течение 2 лет, потому что angular не был уведомлен — мы хотим изменить часть маршрута, не вызывая все.

@petebacondarwin было бы хорошо иметь не только обновление пути местоположения без перезагрузки, но и обновление параметров пути без перезагрузки ... как реализовано выше https://github.com/angular/angular.js/issues/1699#issuecomment -96126887

Решение @jicasada отлично сработало для меня! Спасибо!

@jicasada работает ли это с ui-router? Я обнаружил ошибку с ui-router, из-за которой reloa dOnSearch:false не работает, если родительское и дочернее состояния имеют один и тот же URL-адрес. Если я перейду в дочернее состояние с обновленным параметром запроса, он дважды вызовет родительский контроллер.

+1

+1

Я подтверждаю, что ngSilent отлично справляется со своей задачей. Даже в этом случае официальное решение будет высоко оценено.

Решение состоит в том, чтобы добавить дополнительный параметр в $location.path().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

А затем просто используйте вот так

$location.path('/path/that/you/need', false);

Я должен просто сделать $location.replace() точно так же, как функция window.location.replace. Это просто глупо, потому что каждая операция Create in CRUD всегда будет иметь эту проблему.

(и) +1

+1

Я советую вам использовать «параметр поиска» следующим образом:
/thing?id=1234
Не настраивайте параметр в провайдере маршрутизатора, он не перезагрузит представление и контроллер.
Если вы хотите получить идентификатор
$location.search().id //1234
@cgross

Демо http://codepen.io/dolymood/details/jrEQBx/
Используйте decorator для декоратора ngViewDirective

+1

+1

+1

+1

+1

+1

+!

Хватит уже +1 :-)
Мы получаем сообщение. Мы посмотрим на это перед Рождеством!

Закрыто №15002.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги