Ei Anders, ótima pergunta!
Tenho quase o mesmo caso de uso que você e queria fazer a mesma coisa! Pesquisa do usuário> obter resultados> O usuário navega até o resultado> O usuário navega de volta> BOOM retorno rápido aos resultados , mas você não deseja armazenar o resultado específico para o qual o usuário navegou.
tl; dr
Você precisa ter uma classe que implemente RouteReuseStrategy
e forneça sua estratégia no ngModule
. Se você deseja modificar quando a rota é armazenada, modifique a shouldDetach
função. Quando retorna true
, o Angular armazena a rota. Se você deseja modificar quando a rota é anexada, modifique a shouldAttach
função. Quando shouldAttach
retorna verdadeiro, o Angular usará a rota armazenada no lugar da rota solicitada. Aqui está um Plunker para você brincar.
Sobre RouteReuseStrategy
Ao fazer essa pergunta, você já entende que RouteReuseStrategy permite que você diga ao Angular para não destruir um componente, mas na verdade salvá-lo para uma nova renderização em uma data posterior. Isso é legal porque permite:
- Chamadas de servidor diminuídas
- Velocidade aumentada
- E o componente renderiza, por padrão, no mesmo estado em que foi deixado
Este último é importante se você quiser, digamos, sair de uma página temporariamente, mesmo que o usuário tenha inserido muito texto nela. Os aplicativos corporativos vão adorar esse recurso por causa do excesso quantidade de formulários!
Foi isso que eu inventei para resolver o problema. Como você disse, você precisa fazer uso do RouteReuseStrategy
oferecido pelo @ angular / router nas versões 3.4.1 e superiores.
FAÇAM
Primeiro, certifique-se de que seu projeto tenha @ angular / router versão 3.4.1 ou superior.
A seguir , crie um arquivo que hospedará sua classe que implementa RouteReuseStrategy
. Liguei para o meu reuse-strategy.ts
e coloquei-o na /app
pasta para guarda. Por enquanto, esta classe deve ser semelhante a:
import { RouteReuseStrategy } from '@angular/router';
export class CustomReuseStrategy implements RouteReuseStrategy {
}
(não se preocupe com seus erros de TypeScript, estamos prestes a resolver tudo)
Termine o trabalho de base fornecendo a aula para você app.module
. Observe que você ainda não escreveu CustomReuseStrategy
, mas deve ir em frente e import
desde reuse-strategy.ts
sempre. Além dissoimport { RouteReuseStrategy } from '@angular/router';
@NgModule({
[...],
providers: [
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
]
)}
export class AppModule {
}
A parte final é escrever a classe que controlará se as rotas serão ou não desanexadas, armazenadas, recuperadas e reanexadas. Antes de passarmos ao antigo copiar / colar , farei uma breve explicação da mecânica aqui, como eu a entendo. Consulte o código abaixo para ver os métodos que estou descrevendo e, claro, há muita documentação no código .
- Quando você navega,
shouldReuseRoute
dispara. Este é um pouco estranho para mim, mas se voltartrue
, na verdade, ele reutiliza a rota em que você está atualmente e nenhum dos outros métodos é acionado. Acabei de retornar falso se o usuário estiver navegando para longe.
- Se
shouldReuseRoute
retornar false
, shouldDetach
dispara. shouldDetach
determina se você deseja ou não armazenar a rota e retorna um boolean
indicando isso. É aqui que você deve decidir armazenar / não armazenar caminhos , o que eu faria verificando uma matriz de caminhos que deseja armazenar route.routeConfig.path
e retornando false se path
não existir na matriz.
- Se
shouldDetach
retornar true
, store
é disparado, o que é uma oportunidade para você armazenar todas as informações que desejar sobre a rota. Faça o que fizer, você precisará armazenar o DetachedRouteHandle
porque é isso que o Angular usa para identificar seu componente armazenado mais tarde. Abaixo, eu armazeno o DetachedRouteHandle
e o ActivatedRouteSnapshot
em uma variável local para minha classe.
Então, vimos a lógica de armazenamento, mas e quanto a navegar até um componente? Como o Angular decide interceptar sua navegação e colocar aquela armazenada em seu lugar?
- Mais uma vez, depois de
shouldReuseRoute
retornar false
, shouldAttach
é executado, que é sua chance de descobrir se deseja regenerar ou usar o componente na memória. Se você deseja reutilizar um componente armazenado, volte true
e você está no caminho certo!
- Agora o Angular perguntará a você "qual componente você deseja que usemos?", Que você indicará retornando o componente
DetachedRouteHandle
de retrieve
.
Essa é praticamente toda a lógica de que você precisa! No código para reuse-strategy.ts
, abaixo, também deixei uma função bacana que irá comparar dois objetos. Eu o utilizo para comparar as rotas futuras route.params
e route.queryParams
as armazenadas. Se todos eles corresponderem, quero usar o componente armazenado em vez de gerar um novo. Mas como você faz isso é com você!
reuse-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;
}
}
Comportamento
Essa implementação armazena todas as rotas exclusivas que o usuário visita no roteador exatamente uma vez. Isso continuará a ser adicionado aos componentes armazenados na memória durante a sessão do usuário no site. Se você quiser limitar as rotas que armazena, o lugar para fazer isso é o shouldDetach
método. Ele controla quais rotas você salva.
Exemplo
Digamos que seu usuário procure algo na página inicial, o que o leva ao caminho search/:term
, que pode aparecer como www.yourwebsite.com/search/thingsearchedfor
. A página de pesquisa contém vários resultados de pesquisa. Você gostaria de armazenar esta rota, caso eles queiram voltar a ela! Agora, eles clicam em um resultado de pesquisa e são direcionados para o view/:resultId
que você não deseja armazenar, visto que provavelmente estarão lá apenas uma vez. Com a implementação acima implementada, eu simplesmente mudaria o shouldDetach
método! Pode ser assim:
Primeiro, vamos criar uma série de caminhos que queremos armazenar.
private acceptedRoutes: string[] = ["search/:term"];
agora, shouldDetach
podemos verificar o em route.routeConfig.path
relação ao nosso array.
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
}
}
Como o Angular armazenará apenas uma instância de uma rota, esse armazenamento será leve e armazenaremos apenas o componente localizado emsearch/:term
e não todos os outros!
Links Adicionais
Embora ainda não haja muita documentação, aqui estão alguns links para o que existe:
Documentos angulares: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
Artigo de introdução: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
Implementação padrão de RouteReuseStrategy do nativescript-angular : https://github.com/NativeScript/nativescript-angular/blob/cb4fd3a/nativescript-angular/router/ns-route-reuse-strategy.ts
Observable
. O componente deve assinar oObservable
durante ongOnInit
gancho do ciclo de vida. Então você será capaz de dizer ao componente, a partir doReuseRouteStrategy
, que ele acabou de ser anexado e o componente pode modificar seu estado conforme o ajuste.Não se intimide com a resposta aceita, isso é bastante simples. Aqui está uma resposta rápida do que você precisa. Eu recomendaria pelo menos ler a resposta aceita, pois ela é cheia de detalhes.
Esta solução não faz nenhuma comparação de parâmetro como a resposta aceita, mas funcionará bem para armazenar um conjunto de rotas.
importações de app.module.ts:
shared / routing.ts:
fonte
Além da resposta aceita (por Corbfon) e da explicação mais curta e direta de Chris Fremgen, desejo adicionar uma maneira mais flexível de lidar com as rotas que devem usar a estratégia de reutilização.
Ambas as respostas armazenam as rotas que queremos armazenar em cache em uma matriz e, em seguida, verificam se o caminho da rota atual está na matriz ou não. Esta verificação é feita no
shouldDetach
método.Acho essa abordagem inflexível porque, se quisermos alterar o nome da rota, precisaremos lembrar de também alterar o nome da rota em nossa
CustomReuseStrategy
classe. Podemos esquecer de alterá-lo ou algum outro desenvolvedor de nossa equipe pode decidir alterar o nome da rota sem saber da existência deRouteReuseStrategy
.Em vez de armazenar as rotas que queremos armazenar em cache em um array, podemos marcá-las diretamente em
RouterModule
usingdata
object. Dessa forma, mesmo se alterarmos o nome da rota, a estratégia de reutilização ainda será aplicada.E então, no
shouldDetach
método, fazemos uso disso.fonte
Para usar a estratégia de Chris Fremgen com módulos carregados lentamente, modifique a classe CustomReuseStrategy para o seguinte:
finalmente, nos arquivos de roteamento de seus módulos de recursos, defina suas chaves:
Mais informações aqui .
fonte
route.data["key"]
para construir sem erros. Mas o problema que estou tendo é que tenho um componente de rota + que é usado em dois lugares diferentes.1. sample/list/item
e2. product/id/sample/list/item
quando carrego pela primeira vez qualquer um dos caminhos, ele carrega bem, mas o outro gera o erro reanexado porque estou armazenando com base emlist/item
Então, minha solução é duplicar a rota e fazer algumas alterações no caminho do url, mas exibindo o mesmo componente. Não tenho certeza se há outra solução alternativa para isso.Outra implementação mais válida, completa e reutilizável. Este suporta módulos carregados lentamente como @ Uğur Dinç e integra o sinalizador de dados de rota @Davor. A melhor melhoria é a geração automática de um identificador (quase) exclusivo com base no caminho absoluto da página. Dessa forma, você não precisa defini-lo sozinho em todas as páginas.
Marque qualquer página que você deseja configurar em cache
reuseRoute: true
. Será usado noshouldDetach
método.Esta é a implementação de estratégia mais simples, sem comparar parâmetros de consulta.
Este também compara os parâmetros da consulta.
compareObjects
tem uma pequena melhoria em relação à versão @Corbfon: faz um loop pelas propriedades de ambos os objetos de base e de comparação. Lembre-se de que você pode usar uma implementação externa e mais confiável, como oisEqual
método lodash .Se você tiver a melhor maneira de gerar chaves exclusivas, comente minha resposta, atualizarei o código.
Obrigado a todos os caras que compartilharam sua solução.
fonte
Todas as soluções mencionadas foram de alguma forma insuficientes em nosso caso. Temos um aplicativo de negócios menor com:
Nossos requisitos:
Exemplo simplificado de nossas rotas:
Estratégia de reutilização:
fonte
o seguinte é trabalho! referência: https://www.cnblogs.com/lovesangel/p/7853364.html
fonte
_routerState
._routerState
causa algum dano ?deleteRouteSnapshot
?Eu enfrentei esses problemas ao implementar uma estratégia de reutilização de rota personalizada:
Então, escrevi uma biblioteca resolvendo esses problemas. A biblioteca fornece um serviço e decoradores para anexar / desconectar ganchos e usa os componentes de uma rota para armazenar rotas desconectadas, não os caminhos de uma rota.
Exemplo:
A biblioteca: https://www.npmjs.com/package/ng-cache-route-reuse
fonte