最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why is ngOnInit() being called before canActivate()? - Stack Overflow

programmeradmin5浏览0评论

I'm using route guards, specifically the canActivate() method, but Angular is calling ngOnInit() of my root AppComponent before canActivate is called.

I have to wait on some data in canActivate before the AppComponent can render it in the template.

How can I do this?

I'm using route guards, specifically the canActivate() method, but Angular is calling ngOnInit() of my root AppComponent before canActivate is called.

I have to wait on some data in canActivate before the AppComponent can render it in the template.

How can I do this?

Share Improve this question edited Jun 7, 2018 at 18:23 jonrsharpe 122k30 gold badges268 silver badges476 bronze badges asked Jun 7, 2018 at 18:22 KingamereKingamere 10.2k24 gold badges78 silver badges115 bronze badges 4
  • 5 canActivate gets called to find out if the relevant route ponent loads into the outlet, not the root app ponent. If you need something to happen before the app starts, you'll need to use APP_INITIALIZER. – jonrsharpe Commented Jun 7, 2018 at 18:25
  • 6 AppComponent is the default bootstrapped ponent. It does not wait for any routes to activate. – Amit Chigadani Commented Jun 7, 2018 at 18:27
  • You want to make some asynchronous calls inside canActivate and activate/don't activate specific ponent based on this async response? – P.S. Commented Jun 7, 2018 at 19:13
  • @CommercialSuicide Yes that's right. – Kingamere Commented Jun 7, 2018 at 19:19
Add a ment  | 

2 Answers 2

Reset to default 3

I was dealing with such cases, and here is what I usually do:

1. I create a Resolver service (which implements Resolve interface). It allows you to get all necessary data before activating the route:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { DataService } from 'path/to/data.service';

@Injectable()
export class ExampleResolverService implements Resolve<any> {
  constructor(private _dataService: DataService) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<any> {
    return this._dataService.anyAsyncCall()
      .then(response => {
        /* Let's imagine, that this method returns response with field "result", which can be equal to "true" or "false" */
        /* "setResult" just stores passed argument to "DataService" class property */
        this._dataService.setResult(response.result);
      })
      .catch(err => this._dataService.setResult(false););
  }
}

2. Here is how we can deal with AuthGuard, which implements CanActivate interface:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { DataService } from 'path/to/data.service';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private _dataService: DataService) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    /* "getResult" method operates with the same class property as setResult, it just returns the value of it */
    return this._dataService.getResult(); // will return "true" or "false"
  }
}

3. Then you can include the Resolver and the AuthGuard to your routes config, here is just a part (the structure of routes can differ, here is an example with activating the parent ponent):

const routes: Routes = [
  {
    path: 'app',
    ponent: AppComponent,
    resolve: {
      result: ExampleResolverService // your resolver
    },
    canActivate: [AuthGuard], // your AuthGuard with "canActivate" method
    children: [...] // child routes goes inside the array
  }
];

How it works

When you're navigating to /app, the ExampleResolverService starts, makes API call and stores the necessary part of response to class property in DataService via setResult method (it's the usual setter). Then, when the resolver finished the work, it's time for our AuthGuard. It gets stored result from DataService via getResult method (it's the usual getter), and returns this boolean result (our AuthGuard expects boolean to be returned, and the route will be activated if it returns true, and will not be activated if it returns false);

It's the simplest example without any additional operations with data, the logic is more plex usually, but this skeleton should be enough for basic understanding.

For me, I listened for ROUTE_NAVIGATED events in my app ponent like below

I am using ngrx/router-store to be able to listen to these router actions.

  // app.ponent.ts
  public ngOnInit(): void {
    // grab the action stream
    this.actions$.pipe(
      // Only pay attention to pleted router 
      ofType(ROUTER_NAVIGATED),
      // Now I can guarantee that my resolve has pleted, as the router has finsihed
      // Switch 
      switchMap(() => {
        // Now switch to get the stuff I was waiting for 
        return this.someService.getStuff();
      })
    // Need to subscribe to actions as we are in the ponent, not in an effect
    // I suppose we should unsubscribe, but the app ponent will never destroy as far as I am aware so will always be listening
    ).subscribe();
发布评论

评论列表(0)

  1. 暂无评论