Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is ngOnInit() being called before canActivate()?

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?

like image 806
Kingamere Avatar asked Oct 17 '22 17:10

Kingamere


1 Answers

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 component):

const routes: Routes = [
  {
    path: 'app',
    component: 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 complex usually, but this skeleton should be enough for basic understanding.

like image 142
Commercial Suicide Avatar answered Oct 20 '22 01:10

Commercial Suicide