Привет, Андерс, отличный вопрос!
У меня почти такой же вариант использования, как и у вас, и я хотел сделать то же самое! Поиск пользователя> получить результаты> Пользователь переходит к результату> Пользователь переходит назад> БУМ молниеносно быстро возвращается к результатам , но вы не хотите сохранять конкретный результат, к которому перешел пользователь.
tl; dr
Вам нужен класс, который реализует RouteReuseStrategy
и предоставляет вашу стратегию в ngModule
. Если вы хотите изменить время сохранения маршрута, измените shouldDetach
функцию. Когда он возвращается true
, Angular сохраняет маршрут. Если вы хотите изменить, когда маршрут прикреплен, измените shouldAttach
функцию. Когда shouldAttach
возвращается true, Angular будет использовать сохраненный маршрут вместо запрошенного маршрута. Вот вам Plunker , с которым можно поиграть.
О RouteReuseStrategy
Задав этот вопрос, вы уже понимаете, что RouteReuseStrategy позволяет вам указать Angular не уничтожать компонент, а фактически сохранить его для повторного рендеринга в будущем. Это круто, потому что позволяет:
- Снижение обращений к серверу
- Повышенная скорость
- И компонент по умолчанию отображает в том же состоянии, в котором он был
Последнее важно, если вы хотите, скажем, временно покинуть страницу, даже если пользователь ввел на нее много текста. Корпоративным приложениям понравится эта функция из-за чрезмерного количества форм!
Это то, что я придумал для решения проблемы. Как вы сказали, вам нужно использовать RouteReuseStrategy
@ angular / router в версиях 3.4.1 и выше.
ДЕЛАТЬ
Сначала убедитесь, что в вашем проекте используется @ angular / router версии 3.4.1 или выше.
Затем создайте файл, в котором будет размещен ваш класс, реализующий RouteReuseStrategy
. Я позвонил в свою reuse-strategy.ts
и положил в /app
папку на хранение. На данный момент этот класс должен выглядеть так:
import { RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
}
(не беспокойтесь о своих ошибках TypeScript, мы все решим)
Закончите основу , предоставив урок вашему app.module
. Учтите, что вы еще не написали CustomReuseStrategy
, но следует продолжить, и import
это reuse-strategy.ts
все равно. Такжеimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
Последняя часть - это написание класса, который будет контролировать, будут ли маршруты отсоединяться, сохраняться, извлекаться и повторно присоединяться. Прежде чем мы перейдем к старому копированию / вставке , я сделаю здесь краткое объяснение механики, насколько я понимаю. Ссылайтесь на приведенный ниже код для описываемых мной методов, и, конечно же, в коде есть много документации .
- При навигации
shouldReuseRoute
горит. Для меня это немного странно, но если он вернетсяtrue
, то он фактически повторно использует маршрут, на котором вы сейчас находитесь, и ни один из других методов не запускается. Я просто возвращаю false, если пользователь уходит.
- Если
shouldReuseRoute
возвращается false
, shouldDetach
срабатывает. shouldDetach
определяет, хотите ли вы сохранить маршрут, и возвращает это значение boolean
. Здесь вы должны решить хранить / не хранить пути , что я бы сделал, проверив массив путей, которые вы хотите сохранить route.routeConfig.path
, и вернув false, если path
в массиве не существует.
- Если
shouldDetach
возвращается true
, store
запускается, что дает вам возможность сохранить любую информацию о маршруте, которую вы хотите. Что бы вы ни делали, вам нужно будет сохранить, DetachedRouteHandle
потому что это то, что Angular использует для идентификации вашего сохраненного компонента позже. Ниже я сохраняю и DetachedRouteHandle
и ActivatedRouteSnapshot
в переменной, локальной для моего класса.
Итак, мы увидели логику хранилища, но как насчет перехода к компоненту? Как Angular решает перехватить вашу навигацию и поместить сохраненную на место?
- Опять же , после того, как
shouldReuseRoute
вернулся false
, shouldAttach
прогоны, что это ваш шанс , чтобы выяснить , хотите ли вы , чтобы восстановить или использовать компонент в памяти. Если вы хотите повторно использовать сохраненный компонент, возвращайтесь, true
и все в порядке!
- Теперь Angular спросит вас: «Какой компонент вы хотите, чтобы мы использовали?», Который вы укажете, вернув этот компонент
DetachedRouteHandle
из retrieve
.
Это почти вся необходимая логика! В приведенном ниже коде reuse-strategy.ts
я также оставил вам отличную функцию, которая сравнивает два объекта. Я использую его для сравнения будущего маршрута route.params
и route.queryParams
сохраненного. Если все они совпадают, я хочу использовать сохраненный компонент вместо создания нового. Но как вы это сделаете, решать только вам!
повторное использование-strategy.ts
/**
* reuse-strategy.ts
* by corbfon 1/6/17
*/
import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router';
/** Interface for object which can store both:
* An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
* A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
*/
interface RouteStorageObject {
snapshot: ActivatedRouteSnapshot;
handle: DetachedRouteHandle;
}
export class CustomReuseStrategy implements RouteReuseStrategy {
/**
* Object which will store RouteStorageObjects indexed by keys
* The keys will all be a path (as in route.routeConfig.path)
* This allows us to see if we've got a route stored for the requested path
*/
storedRoutes: { [key: string]: RouteStorageObject } = {};
/**
* Decides when the route should be stored
* If the route should be stored, I believe the boolean is indicating to a controller whether or not to fire this.store
* _When_ it is called though does not particularly matter, just know that this determines whether or not we store the route
* An idea of what to do here: check the route.routeConfig.path to see if it is a path you would like to store
* @param route This is, at least as I understand it, the route that the user is currently on, and we would like to know if we want to store it
* @returns boolean indicating that we want to (true) or do not want to (false) store that route
*/
shouldDetach(route: ActivatedRouteSnapshot): boolean {
let detach: boolean = true;
console.log("detaching", route, "return: ", detach);
return detach;
}
/**
* Constructs object of type `RouteStorageObject` to store, and then stores it for later attachment
* @param route This is stored for later comparison to requested routes, see `this.shouldAttach`
* @param handle Later to be retrieved by this.retrieve, and offered up to whatever controller is using this class
*/
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
let storedRoute: RouteStorageObject = {
snapshot: route,
handle: handle
};
console.log( "store:", storedRoute, "into: ", this.storedRoutes );
// routes are stored by path - the key is the path name, and the handle is stored under it so that you can only ever have one object stored for a single path
this.storedRoutes[route.routeConfig.path] = storedRoute;
}
/**
* Determines whether or not there is a stored route and, if there is, whether or not it should be rendered in place of requested route
* @param route The route the user requested
* @returns boolean indicating whether or not to render the stored route
*/
shouldAttach(route: ActivatedRouteSnapshot): boolean {
// this will be true if the route has been stored before
let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[route.routeConfig.path];
// this decides whether the route already stored should be rendered in place of the requested route, and is the return value
// at this point we already know that the paths match because the storedResults key is the route.routeConfig.path
// so, if the route.params and route.queryParams also match, then we should reuse the component
if (canAttach) {
let willAttach: boolean = true;
console.log("param comparison:");
console.log(this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params));
console.log("query param comparison");
console.log(this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams));
let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[route.routeConfig.path].snapshot.params);
let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[route.routeConfig.path].snapshot.queryParams);
console.log("deciding to attach...", route, "does it match?", this.storedRoutes[route.routeConfig.path].snapshot, "return: ", paramsMatch && queryParamsMatch);
return paramsMatch && queryParamsMatch;
} else {
return false;
}
}
/**
* Finds the locally stored instance of the requested route, if it exists, and returns it
* @param route New route the user has requested
* @returns DetachedRouteHandle object which can be used to render the component
*/
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
// return null if the path does not have a routerConfig OR if there is no stored route for that routerConfig
if (!route.routeConfig || !this.storedRoutes[route.routeConfig.path]) return null;
console.log("retrieving", "return: ", this.storedRoutes[route.routeConfig.path]);
/** returns handle when the route.routeConfig.path is already stored */
return this.storedRoutes[route.routeConfig.path].handle;
}
/**
* Determines whether or not the current route should be reused
* @param future The route the user is going to, as triggered by the router
* @param curr The route the user is currently on
* @returns boolean basically indicating true if the user intends to leave the current route
*/
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
return future.routeConfig === curr.routeConfig;
}
/**
* This nasty bugger finds out whether the objects are _traditionally_ equal to each other, like you might assume someone else would have put this function in vanilla JS already
* One thing to note is that it uses coercive comparison (==) on properties which both objects have, not strict comparison (===)
* Another important note is that the method only tells you if `compare` has all equal parameters to `base`, not the other way around
* @param base The base object which you would like to compare another object to
* @param compare The object to compare to base
* @returns boolean indicating whether or not the objects have all the same properties and those properties are ==
*/
private compareObjects(base: any, compare: any): boolean {
// loop through all properties in base object
for (let baseProperty in base) {
// determine if comparrison object has that property, if not: return false
if (compare.hasOwnProperty(baseProperty)) {
switch(typeof base[baseProperty]) {
// if one is object and other is not: return false
// if they are both objects, recursively call this comparison function
case 'object':
if ( typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty]) ) { return false; } break;
// if one is function and other is not: return false
// if both are functions, compare function.toString() results
case 'function':
if ( typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString() ) { return false; } break;
// otherwise, see if they are equal using coercive comparison
default:
if ( base[baseProperty] != compare[baseProperty] ) { return false; }
}
} else {
return false;
}
}
// returns true only after false HAS NOT BEEN returned through all loops
return true;
}
}
Поведение
Эта реализация хранит каждый уникальный маршрут, который пользователь посещает на маршрутизаторе ровно один раз. Это будет продолжать добавляться к компонентам, хранящимся в памяти, на протяжении всего сеанса пользователя на сайте. Если вы хотите ограничить маршруты, которые вы храните, место для этого - shouldDetach
метод. Он контролирует, какие маршруты вы сохраняете.
пример
Предположим, ваш пользователь что-то ищет на домашней странице, которая направляет их по пути search/:term
, который может выглядеть как www.yourwebsite.com/search/thingsearchedfor
. Страница поиска содержит несколько результатов поиска. Вы хотите сохранить этот маршрут на случай, если они захотят по нему вернуться! Теперь они нажимают на результат поиска и переходят к нему view/:resultId
, который вы не хотите сохранять, так как они, вероятно, будут там только один раз. Имея вышеуказанную реализацию, я бы просто изменил shouldDetach
метод! Вот как это может выглядеть:
Прежде всего, давайте создадим массив путей, которые мы хотим сохранить.
private acceptedRoutes: string[] = ["search/:term"];
теперь shouldDetach
мы можем проверить route.routeConfig.path
наш массив.
shouldDetach(route: ActivatedRouteSnapshot): boolean {
// check to see if the route's path is in our acceptedRoutes array
if (this.acceptedRoutes.indexOf(route.routeConfig.path) > -1) {
console.log("detaching", route);
return true;
} else {
return false; // will be "view/:resultId" when user navigates to result
}
}
Поскольку Angular будет хранить только один экземпляр маршрута, это хранилище будет облегченным, и мы будем хранить только компонент, расположенный в, search/:term
а не все остальные!
Дополнительные ссылки
Хотя документации пока не так много, вот пара ссылок на то, что действительно существует:
Документы по Angular: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Вступительная статья: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Реализация RouteReuseStrategy по умолчанию в nativescript-angular : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts
Observable
. Компонент должен подписаться на ловушкуObservable
во времяngOnInit
жизненного цикла. Тогда вы сможете определить компонент по томуReuseRouteStrategy
, что он только что был прикреплен, и компонент может изменить свое состояние по мере необходимости.Не пугайтесь принятого ответа, это довольно просто. Вот быстрый ответ, что вам нужно. Я бы рекомендовал хотя бы прочитать принятый ответ, поскольку он полон мельчайших подробностей.
Это решение не выполняет никакого сравнения параметров, как принятый ответ, но оно отлично подойдет для хранения набора маршрутов.
app.module.ts импортирует:
общий / routing.ts:
источник
В дополнение к принятому ответу (Корбфон) и более короткому и прямому объяснению Криса Фремгена я хочу добавить более гибкий способ обработки маршрутов, который должен использовать стратегию повторного использования.
Оба ответа сохраняют маршруты, которые мы хотим кэшировать, в массиве, а затем проверяем, находится ли текущий путь маршрута в массиве или нет. Эта проверка выполняется в
shouldDetach
методе.Я считаю этот подход негибким, потому что, если мы хотим изменить имя маршрута, нам нужно не забыть также изменить имя маршрута в нашем
CustomReuseStrategy
классе. Мы можем либо забыть его изменить, либо другой разработчик в нашей команде может решить изменить имя маршрута, даже не зная о существованииRouteReuseStrategy
.Вместо того, чтобы хранить маршруты, которые мы хотим кэшировать в массиве, мы можем пометить их непосредственно с
RouterModule
помощьюdata
объекта. Таким образом, даже если мы изменим имя маршрута, стратегия повторного использования все равно будет применяться.И затем
shouldDetach
мы используем это в методе.источник
Чтобы использовать стратегию Криса Фремгена с отложенной загрузкой модулей, измените класс CustomReuseStrategy на следующее:
наконец, в файлах маршрутизации ваших функциональных модулей определите свои ключи:
Больше информации здесь .
источник
route.data["key"]
для сборки без ошибок. Но проблема в том, что у меня есть компонент route +, который используется в двух разных местах.1. sample/list/item
и2. product/id/sample/list/item
когда я впервые загружаю один из путей, он загружается нормально, но другой выдает повторно прикрепленную ошибку, потому что я сохраняю на основеlist/item
Итак, моя работа заключается в том, что я дублировал маршрут и внес некоторые изменения в путь URL, но отображал тот же компонент. Не уверен, есть ли для этого другой способ.Еще одна реализация, более достоверная, полная и многоразовая. Он поддерживает модули с отложенной загрузкой, такие как @ Uğur Dinç, и интегрирует флаг данных маршрута @Davor. Лучшее улучшение - автоматическое создание (почти) уникального идентификатора на основе абсолютного пути к странице. Таким образом, вам не нужно определять его самостоятельно на каждой странице.
Отметьте любую страницу, которую вы хотите кэшировать
reuseRoute: true
. Он будет использован вshouldDetach
методе.Это самая простая реализация стратегии без сравнения параметров запроса.
Здесь также сравниваются параметры запроса.
compareObjects
имеет небольшое улучшение по сравнению с версией @Corbfon: перебирает свойства как базовых, так и сравниваемых объектов. Помните, что вы можете использовать внешнюю и более надежную реализацию, напримерisEqual
метод lodash .Если у вас есть лучший способ сгенерировать уникальные ключи, прокомментируйте мой ответ, я обновлю код.
Спасибо всем ребятам, которые поделились своим решением.
источник
Все упомянутые решения в нашем случае оказались недостаточными. У нас есть приложение для малого бизнеса с:
Наши требования:
Упрощенный пример наших маршрутов:
Стратегия повторного использования:
источник
Следующее - работа! ссылка: https://www.cnblogs.com/lovesangel/p/7853364.html
источник
_routerState
._routerState
причиняет вред?deleteRouteSnapshot
?Я столкнулся с этими проблемами при реализации стратегии повторного использования пользовательского маршрута:
Поэтому я написал библиотеку, решающую эти проблемы. Библиотека предоставляет службу и декораторы для присоединения / отсоединения перехватчиков и использует компоненты маршрута для хранения отсоединенных маршрутов, а не путей маршрута.
Пример:
Библиотека: https://www.npmjs.com/package/ng-cache-route-reuse
источник