Cómo implementar RouteReuseStrategy shouldDetach para rutas específicas en Angular 2


Tengo un módulo Angular 2 en el que he implementado enrutamiento y me gustaría que los estados se almacenaran al navegar. El usuario debe ser capaz de: 1. búsqueda de documentos mediante una fórmula de búsqueda 2. vaya a uno de los resultados 3. vuelva a searchresult-sin comunicarse con el servidor

Esto es posible incluyendo RouteReuseStrategy. La pregunta es: ¿Cómo puedo implementar que el documento no se almacene?

Así que el estado de la ruta "documentos" debe ser almacenado y la ruta ruta "documentos/:id"' estado NO debe ser almacenado?

Author: Corbfon, 2016-12-22

4 answers

Hey Anders, gran pregunta!

Tengo casi el mismo caso de uso, y quería hacer lo mismo! Búsqueda de usuarios > obtener resultados > El usuario navega hacia el resultado > El usuario navega hacia atrás > BOOM retorno increíblemente rápido a los resultados, pero no desea almacenar el resultado específico al que navegó el usuario.

Tl; dr

Necesita tener una clase que implemente RouteReuseStrategy y proporcione su estrategia en ngModule. Si desea modificar cuándo la ruta se almacena, modifique la función shouldDetach. Cuando devuelve true, Angular almacena la ruta. Si desea modificar cuando se adjunta la ruta, modifique la función shouldAttach. Cuando shouldAttach devuelve true, Angular utilizará la ruta almacenada en lugar de la ruta solicitada. Aquí hay un Émbolo para que juegues.

Acerca de RouteReuseStrategy

Al hacer esta pregunta, ya entiendes que RouteReuseStrategy te permite decirle a Angular2 no para destruir un componente, sino para guardarlo para volver a renderizarlo en una fecha posterior. Eso es genial porque permite:

  • Disminuidas llamadas al servidor
  • Mayor velocidad
  • Y el componente renderiza, por defecto, en el mismo estado que se dejó

Este último es importante si desea, por ejemplo, dejar una página temporalmente aunque el usuario haya introducido un lote de texto en ella. Empresa a las aplicaciones les encantará esta característica debido a la excesiva cantidad de formularios!

Esto es lo que se me ocurrió para resolver el problema. Como ha dicho, debe hacer uso del RouteReuseStrategy ofrecido por @angular/router en las versiones 3.4.1 y posteriores.

TODO

Primero Asegúrese de que su proyecto tiene @angular/router versión 3.4.1 o superior.

A continuación, , cree un archivo que albergará su clase que implementa RouteReuseStrategy. Yo llamé a la mía. reuse-strategy.ts y lo colocó en la carpeta /app para su custodia. Por ahora, esta clase debería parecerse a:

import { RouteReuseStrategy } from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {
}

(no se preocupe por sus errores de TypeScript, estamos a punto de resolver todo)

Termina el trabajo de base proporcionando la clase a tu app.module. Tenga en cuenta que aún no ha escrito CustomReuseStrategy, pero debe seguir adelante y import desde reuse-strategy.ts de todos modos. También import { RouteReuseStrategy } from '@angular/router';

@NgModule({
    [...],
    providers: [
        {provide: RouteReuseStrategy, useClass: CustomReuseStrategy}
    ]
)}
export class AppModule {
}

La pieza final es escribir la clase que controlará si las rutas se separan, almacenan, recuperan y vuelven a conectar. Antes de llegar a la antigua copiar/pegar , voy a hacer una breve explicación de la mecánica aquí, como los entiendo. Consulte el siguiente código para los métodos que estoy describiendo, y por supuesto, hay mucha documentación en el código.

  1. Cuando navegas, shouldReuseRoute se dispara. Este es un poco extraño para mí, pero si devuelve true, entonces realmente reutiliza la ruta en la que está actualmente y ninguno de los otros métodos están despedidos. Solo devuelvo false si el usuario está navegando.
  2. Si shouldReuseRoute devuelve false, shouldDetach incendios. shouldDetach determina si desea o no almacenar la ruta, y devuelve un boolean indicando tanto. Aquí es donde debe decidir almacenar/no almacenar rutas, lo que haría comprobando una matriz de rutas que desea que se almacene contra route.routeConfig.path, y devolviendo false si el path no existe en la matriz.
  3. Si shouldDetach devuelve true, store es despedido, lo que es una oportunidad para que almacene cualquier información que desee sobre la ruta. Hagas lo que hagas, necesitarás almacenar DetachedRouteHandle porque eso es lo que Angular usa para identificar tu componente almacenado más adelante. A continuación, almaceno tanto el DetachedRouteHandle como el ActivatedRouteSnapshot en una variable local a mi clase.

Entonces, hemos visto la lógica para el almacenamiento, pero ¿qué hay de navegar a un componente? ¿Cómo Angular decide interceptar su navegación y poner el almacenado en su lugar?

  1. De nuevo, después de shouldReuseRoute ha regresado false, shouldAttach se ejecuta, que es su oportunidad de averiguar si desea regenerar o utilizar el componente en la memoria. Si desea reutilizar un componente almacenado, devuelva true y ya está en su camino!
  2. Ahora Angular te preguntará, " ¿qué componente quieres que usemos?", que indicarás devolviendo el DetachedRouteHandle de ese componente desde retrieve.

¡Eso es prácticamente toda la lógica que necesitas! En el código para reuse-strategy.ts, a continuación, también te he dejado una función ingeniosa que comparará dos objetos. Lo uso para comparar las rutas futuras route.params y route.queryParams con las almacenadas. Si todas coinciden, quiero usar el componente almacenado en lugar de generar uno nuevo. ¡Pero cómo lo haces depende de ti!

Reutilización-estrategia.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;
    }
}

Comportamiento

Esta implementación almacena cada ruta única que el usuario visita en el router exactamente una vez. Esta voluntad continúe agregando a los componentes almacenados en la memoria a lo largo de la sesión del usuario en el sitio. Si desea limitar las rutas que almacena, el lugar para hacerlo es el método shouldDetach. Controla qué rutas guardas.

Ejemplo

Digamos que tu usuario busca algo desde la página de inicio, que lo navega a la ruta search/:term, que podría aparecer como www.yourwebsite.com/search/thingsearchedfor. La página de búsqueda contiene un montón de resultados de búsqueda. Te gustaría almacenar esta ruta, en caso de que quieran volver a ella! Ahora hacen clic en un resultado de búsqueda y navegan a view/:resultId, que no desea almacenar, ya que probablemente estarán allí solo una vez. Con la implementación anterior en su lugar, simplemente cambiaría el método shouldDetach! Esto es lo que podría parecer:

En primer lugar vamos a hacer una matriz de rutas que queremos almacenar.

private acceptedRoutes: string[] = ["search/:term"];

Ahora, en shouldDetach podemos comprobar el route.routeConfig.path contra nuestro 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
    }
}

Porque Angular solo almacenará una instancia de una ruta, este almacenamiento será ligero, y solo almacenaremos el componente ubicado en search/:term y no todos los demás!

Enlaces Adicionales

Aunque todavía no hay mucha documentación, aquí hay un par de enlaces a lo que sí existe:

Angular Docs: https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html

Artículo de introducción: https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx

 124
Author: Corbfon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-23 01:26:21

No se deje intimidar por la respuesta aceptada, esto es bastante sencillo. Aquí tienes una respuesta rápida a lo que necesitas. Recomendaría al menos leer la respuesta aceptada, ya que está llena de gran detalle.

Esta solución no hace ninguna comparación de parámetros como la respuesta aceptada, pero funcionará bien para almacenar un conjunto de rutas.

App.módulo.importaciones ts:

import { RouteReuseStrategy } from '@angular/router';
import { CustomReuseStrategy, Routing } from './shared/routing';

@NgModule({
//...
providers: [
    { provide: RouteReuseStrategy, useClass: CustomReuseStrategy },
  ]})

Enrutamiento/compartido.ts:

export class CustomReuseStrategy implements RouteReuseStrategy {
 routesToCache: string[] = ["dashboard"];
 storedRouteHandles = new Map<string, DetachedRouteHandle>();

 // Decides if the route should be stored
 shouldDetach(route: ActivatedRouteSnapshot): boolean {
    return this.routesToCache.indexOf(route.routeConfig.path) > -1;
 }

 //Store the information for the route we're destructing
 store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
    this.storedRouteHandles.set(route.routeConfig.path, handle);
 }

//Return true if we have a stored route object for the next route
 shouldAttach(route: ActivatedRouteSnapshot): boolean {
    return this.storedRouteHandles.has(route.routeConfig.path);
 }

 //If we returned true in shouldAttach(), now return the actual route data for restoration
 retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
    return this.storedRouteHandles.get(route.routeConfig.path);
 }

 //Reuse the route if we're going to and from the same route
 shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    return future.routeConfig === curr.routeConfig;
 }
}
 18
Author: Chris Fremgen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-30 20:46:49

Para usar la estrategia de Chris Fremgen con módulos cargados perezosamente, modifique la clase CustomReuseStrategy a lo siguiente:

import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {
  routesToCache: string[] = ["company"];
  storedRouteHandles = new Map<string, DetachedRouteHandle>();

  // Decides if the route should be stored
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
     return this.routesToCache.indexOf(route.data["key"]) > -1;
  }

  //Store the information for the route we're destructing
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
     this.storedRouteHandles.set(route.data["key"], handle);
  }

  //Return true if we have a stored route object for the next route
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
     return this.storedRouteHandles.has(route.data["key"]);
  }

  //If we returned true in shouldAttach(), now return the actual route data for restoration
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
     return this.storedRouteHandles.get(route.data["key"]);
  }

  //Reuse the route if we're going to and from the same route
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
     return future.routeConfig === curr.routeConfig;
  }
}

Finalmente, en los archivos de enrutamiento de sus módulos de características, defina sus claves:

{ path: '', component: CompanyComponent, children: [
    {path: '', component: CompanyListComponent, data: {key: "company"}},
    {path: ':companyID', component: CompanyDetailComponent},
]}

Más información aquí .

 7
Author: dexter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-27 18:08:08

Lo siguiente es trabajo! referencia:https://www.cnblogs.com/lovesangel/p/7853364.html

import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';

export class CustomReuseStrategy implements RouteReuseStrategy {

    public static handlers: { [key: string]: DetachedRouteHandle } = {}

    private static waitDelete: string

    public static deleteRouteSnapshot(name: string): void {
        if (CustomReuseStrategy.handlers[name]) {
            delete CustomReuseStrategy.handlers[name];
        } else {
            CustomReuseStrategy.waitDelete = name;
        }
    }
   
    public shouldDetach(route: ActivatedRouteSnapshot): boolean {
        return true;
    }

   
    public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        if (CustomReuseStrategy.waitDelete && CustomReuseStrategy.waitDelete == this.getRouteUrl(route)) {
            // 如果待删除是当前路由则不存储快照
            CustomReuseStrategy.waitDelete = null
            return;
        }
        CustomReuseStrategy.handlers[this.getRouteUrl(route)] = handle
    }

    
    public shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

    /** 从缓存中获取快照,若无则返回nul */
    public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
        if (!route.routeConfig) {
            return null
        }

        return CustomReuseStrategy.handlers[this.getRouteUrl(route)]
    }

   
    public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        return future.routeConfig === curr.routeConfig &&
            JSON.stringify(future.params) === JSON.stringify(curr.params);
    }

    private getRouteUrl(route: ActivatedRouteSnapshot) {
        return route['_routerState'].url.replace(/\//g, '_')
    }
}
 2
Author: 红兵伍,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-09 15:37:57