Angular 9. Reiniciando los guardias de la página actual. Activar guardias de ruta actual

Se enfrentó a la necesidad de reiniciar los guardias para la página actual, independientemente de la página que esté abierta.



No encontré una solución estándar y las que se ofrecen en Internet están limitadas a una página. Por lo tanto, escribí el mío y decidí compartirlo.



Descripción del caso



Las páginas de la aplicación se dividen en 3 grupos:



  • Solo para usuarios autorizados

  • Solo para usuarios no autorizados

  • Para cualquier usuario



Puede iniciar o cerrar sesión en cualquier página.



Si ingresa / sale en una página con acceso limitado, entonces debe ir a la página permitida.



Si la página no tiene restricciones, debe permanecer en la página actual.



Para una mejor comprensión, es recomendable conocer:





Decisión



La diferenciación de derechos de acceso a las páginas se realiza a través de guard - CanActivate. La verificación se realiza al cambiar a la página, pero no reacciona a los cambios de derechos cuando la transición ya se ha realizado. Para evitar la duplicación de la lógica para los derechos de acceso, reiniciamos los guardias a la fuerza.



El reinicio de los guardias de la página actual se realiza navegando por la URL actual. Y cambios en las estrategias Router.onSameUrlNavigation y Route.runGuardsAndResolvers.



Aquí tienes una solución lista para usar. Más detalles en la siguiente sección.
  import { Injectable } from '@angular/core';
  import { ActivatedRoute, PRIMARY_OUTLET, Router, RunGuardsAndResolvers } from '@angular/router';

  @Injectable()
  export class GuardControlService {
    constructor(
      private route: ActivatedRoute,
      private router: Router,
    ) {}

    /**
    *   guard-  url
    */
    forceRunCurrentGuards(): void {
      //   Router.onSameUrlNavigation       url
      const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

      //   ActivatedRoute  primary outlet
      const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

      //   runGuardsAndResolvers  ActivatedRoute          url
      const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

      //   
      this.router.navigateByUrl(
        this.router.url
      ).then(() => {
        //  onSameUrlNavigation
        restoreSameUrl();
        //  runGuardsAndResolvers
        restoreRunGuards();
      });
    }

    /**
    *  onSameUrlNavigation    
    * @param router - Router,    
    * @param strategy -  
    * @return callback   
    */
    private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
      const onSameUrlNavigation = router.onSameUrlNavigation;
      router.onSameUrlNavigation = strategy;

      return () => {
        router.onSameUrlNavigation = onSameUrlNavigation;
      }
    }

    /**
    *   route  outlet-
    * @param route - Route    
    * @param outlet -  outlet-,    
    * @return  ActivatedRoute   outlet
    */
    private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
      if (route.children?.length) {
        return this.getLastRouteForOutlet(
          route.children.find(item => item.outlet === outlet),
          outlet
        );
      } else {
        return route;
      }
    }

    /**
    *  runGuardsAndResolvers  ActivatedRoute   ,    
    * @param route - ActivatedRoute    
    * @param strategy -  
    * @return callback   
    */
    private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
      const routeConfigs = route.pathFromRoot
        .map(item => {
          if (item.routeConfig) {
            const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
            item.routeConfig.runGuardsAndResolvers = strategy;
              return runGuardsAndResolvers;
              } else {
            return null;
          }
        });

      return () => {
        route.pathFromRoot
          .forEach((item, index) => {
            if (item.routeConfig) {
              item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
            }
          });
      }
    }
  }
  




Descripción adicional de la solución



Lo primero que debe intentar para reiniciar los guardias es usar la navegación a la URL actual.



this.router.navigateByUrl(this.router.url);


Pero, de forma predeterminada, el evento de transición de URL actual se ignora y no sucede nada. Para que esto funcione, debe configurar el enrutamiento.



Configurar el enrutamiento



1. Cambie la estrategia del enrutador. onSameUrlNavigation



onSameUrlNavigation puede tomar los siguientes valores:



onSameUrlNavigation: 'reload' | 'ignore';


Para la sensibilidad a la transición a la URL actual, debe configurar 'recargar'.



El cambio de política no se vuelve a cargar, pero genera un evento de navegación adicional. Se puede obtener mediante suscripción:



this.router.events.subscribe();


2. Cambie la estrategia de ruta. runGuardsAndResolvers



runGuardsAndResolvers puede tomar los siguientes valores:



type RunGuardsAndResolvers = 'pathParamsChange' | 'pathParamsOrQueryParamsChange' | 'paramsChange' | 'paramsOrQueryParamsChange' | 'always' | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean);


Para la sensibilidad a la transición a la URL actual, debe establecer "siempre".



Configurar el enrutamiento durante la configuración de la aplicación



onSameUrlNavigation:



const routes: : Route[] = [];
@NgModule({
  imports: [
    RouterModule.forRoot(
      routes,
      { onSameUrlNavigation: 'reload' }
    )
  ]
})


runGuardsAndResolvers:



const routes: Route[] = [
  {
    path: '',
    component: AppComponent,
    runGuardsAndResolvers: 'always',
  }
];


Configurar el enrutamiento en tiempo de ejecución



constructor(
  private router: Router,
  private route: ActivatedRoute
) {
  this.router.onSameUrlNavigation = 'reload';
  this.route.routeConfig.runGuardsAndResolvers = 'always';
}


Reinicio de guardias



Para reiniciar los guardias de una página específica, es suficiente configurar el enrutamiento durante la configuración.



Pero para recargar los guardias de cualquier página, cambiar runGuardsAndResolvers en cada ruta dará lugar a comprobaciones innecesarias. Y la necesidad de recordar siempre este parámetro, a los errores.



Dado que nuestro caso asume un reinicio para cualquier página sin restricciones en la configuración de la aplicación, necesita:



1. Reemplace onSameUrlNavigation y mantenga el valor actual



//   Router.onSameUrlNavigation       url
const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

...
/**
*  onSameUrlNavigation    
* @param router - Router,    
* @param strategy -  
* @return callback   
*/
private changeSameUrlStrategy(router: Router, strategy: 'reload' | 'ignore'): () => void {
  const onSameUrlNavigation = router.onSameUrlNavigation;
  router.onSameUrlNavigation = strategy;

  return () => {
    router.onSameUrlNavigation = onSameUrlNavigation;
  }
}


2. Obtenga ActivatedRoute para la URL actual



Dado que la inyección ActivatedRoute se lleva a cabo en el servicio, la ActivatedRoute resultante no está asociada con la URL actual.



ActivatedRoute para la URL actual se encuentra en la última salida principal y debe encontrarla:



//   ActivatedRoute  primary outlet
const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

...
/**
*   route  outlet-
* @param route - Route    
* @param outlet -  outlet-,    
* @return  ActivatedRoute   outlet
*/
private getLastRouteForOutlet(route: ActivatedRoute, outlet: string): ActivatedRoute {
  if (route.children?.length) {
    return this.getLastRouteForOutlet(
      route.children.find(item => item.outlet === outlet),
      outlet
   );
  } else {
    return route;
  }
}


3. Reemplace runGuardsAndResolvers por todo ActivatedRoute y sus ancestros, manteniendo los valores actuales



Un guardia que restringe el acceso puede ubicarse en cualquiera de los antepasados ​​de la ActivatedRoute actual. Todos los antepasados ​​se encuentran en pathFromRoot.



//   runGuardsAndResolvers  ActivatedRoute          url
const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

...
/**
*  runGuardsAndResolvers  ActivatedRoute   ,    
* @param route - ActivatedRoute    
* @param strategy -  
* @return callback   
*/
private changeRunGuardStrategies(route: ActivatedRoute, strategy: RunGuardsAndResolvers): () => void {
  const routeConfigs = route.pathFromRoot
    .map(item => {
      if (item.routeConfig) {
        const runGuardsAndResolvers = item.routeConfig.runGuardsAndResolvers;
        item.routeConfig.runGuardsAndResolvers = strategy;
        return runGuardsAndResolvers;
      } else {
        return null;
      }
    });

  return () => {
    route.pathFromRoot
      .forEach((item, index) => {
        if (item.routeConfig) {
          item.routeConfig.runGuardsAndResolvers = routeConfigs[index];
        }
      });
  }
}


4. Ir a la URL actual



this.router.navigateByUrl(this.router.url);


5. Devuelva runGuardsAndResolvers y onSameUrlNavigation a su estado original



restoreRunGuards();
restoreSameUrl();


6. Combinar etapas en una función



constructor(
  private route: ActivatedRoute,
  private router: Router,
) {}

/**
*   guard-  url
*/
forceRunCurrentGuards(): void {
  //   Router.onSameUrlNavigation       url
  const restoreSameUrl = this.changeSameUrlStrategy(this.router, 'reload');

  //   ActivatedRoute  primary outlet
  const primaryRoute: ActivatedRoute = this.getLastRouteForOutlet(this.route.root, PRIMARY_OUTLET);

  //   runGuardsAndResolvers  ActivatedRoute          url
  const restoreRunGuards = this.changeRunGuardStrategies(primaryRoute, 'always');

  //   
  this.router.navigateByUrl(
    this.router.url
  ).then(() => {
    //  onSameUrlNavigation
    restoreSameUrl();
    //  runGuardsAndResolvers
    restoreRunGuards();
  });
}





Espero que este artículo te haya sido útil. Si hay otras soluciones, estaré encantado de verlas en los comentarios.



All Articles