Angular 9. Reiniciando os guardas da página atual. Acionar os protetores de rota atuais

Diante da necessidade de reiniciar os guardas para a página atual, independente de qual página esteja aberta.



Não encontrei uma solução padrão e as oferecidas na Internet limitam-se a uma página. Portanto, escrevi o meu próprio e decidi compartilhá-lo.



Descrição do caso



As páginas do aplicativo são divididas em 3 grupos:



  • Apenas para usuários autorizados

  • Apenas para usuários não autorizados

  • Para qualquer usuário



Você pode fazer login ou logout em qualquer página.



Se você entrar / sair de uma página com acesso limitado, precisará ir para a página permitida.



Se a página não tiver restrições, você precisará permanecer na página atual.



Para um melhor entendimento, é aconselhável conhecer:



  • Guarda - CanActivate

  • Rota

  • Roteador

  • ActivatedRoute

  • Tomada de roteador



Decisão



A diferenciação dos direitos de acesso às páginas é realizada através do guard - CanActivate. A verificação é realizada quando você vai para a página, mas não reage às mudanças nos direitos quando a transição já foi feita. Para evitar a duplicação da lógica dos direitos de acesso, reiniciamos os guardas à força.



O reinício dos guardas para a página atual é feito navegando no url atual. E mudanças nas estratégias Router.onSameUrlNavigation e Route.runGuardsAndResolvers.



Aqui está uma solução pronta. Mais detalhes na próxima seção.
  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];
            }
          });
      }
    }
  }
  




Descrição adicional da solução



A primeira coisa que você deseja tentar reiniciar os guardas é usar a navegação para a url atual.



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


Mas, por padrão, o evento de transição de url atual é ignorado e nada acontece. Para que isso funcione, você precisa configurar o roteamento.



Configurando o roteamento



1. Mude a estratégia do roteador. onSameUrlNavigation



onSameUrlNavigation pode assumir os seguintes valores:



onSameUrlNavigation: 'reload' | 'ignore';


Para sensibilidade à transição para o url atual, você precisa definir 'recarregar'.



A mudança de política não recarrega, mas gera um evento de navegação adicional. Pode ser obtido por meio de uma assinatura:



this.router.events.subscribe();


2. Altere a estratégia de rota. runGuardsAndResolvers



runGuardsAndResolvers pode assumir os seguintes valores:



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


Para sensibilidade à transição para o url atual, você precisa definir 'sempre'.



Configurando o roteamento durante a configuração do aplicativo



onSameUrlNavigation:



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


runGuardsAndResolvers:



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


Configurando o roteamento em tempo de execução



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


Reiniciando guardas



Para reiniciar os guardas de uma página específica, basta configurar o roteamento durante a configuração.



Mas para recarregar os guardas de qualquer página, alterar runGuardsAndResolvers em cada rota levará a verificações desnecessárias. E a necessidade de sempre lembrar este parâmetro - para erros.



Como nosso caso pressupõe a reinicialização de qualquer página sem restrições na configuração do aplicativo, você precisa de:



1. Substitua onSameUrlNavigation e mantenha o valor atual



//   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. Obtenha ActivatedRoute para o url atual



Como o ActivatedRoute injetado é executado no serviço, o ActivatedRoute resultante não está associado ao url atual.



ActivatedRoute para o url atual está na última saída primária e você precisa encontrá-lo:



//   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. Substitua runGuardsAndResolvers por todos os ActivatedRoute e seus ancestrais, mantendo os valores atuais



Um guarda que restringe o acesso pode ser localizado em qualquer um dos ancestrais do ActivatedRoute atual. Todos os ancestrais estão localizados em 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. Vá para o url atual



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


5. Retorne runGuardsAndResolvers e onSameUrlNavigation ao seu estado original



restoreRunGuards();
restoreSameUrl();


6. Combine estágios em uma função



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 artigo tenha sido útil para você. Se houver outras soluções, ficarei feliz em vê-las nos comentários.



All Articles