0

I am creating a Route Guard in Angular; how it works is depending on if you are associated with a company; it will send you to a particular component. Unfourtnetly when running canLoad method, it does not save the value of the companieslist variable. Any help would be appreciated!

import { Injectable } from '@angular/core';
import { CanActivate, Router, CanLoad } from '@angular/router';
import { AuthService } from 'src/app/auth.service';
import { User } from 'src/models/user';
import {ListdataService} from 'src/app/listdata.service';

@Injectable({
  providedIn: 'root'
})
export class CompanyonlyService implements CanActivate, CanLoad {
  companieslist=[];

  constructor(private authService: AuthService, private router: Router,private dataService:ListdataService) { }



  run(){
    this.dataService.sendGetRequestcompanies().subscribe((data: any[])=>{
      let tmp = [];

      for (let key in data)
        if (data.hasOwnProperty(key))
          tmp.push(data[key])

          this.companieslist = tmp;
          console.log(this.companieslist)





    }   )}



  canActivate() {
    return this.canLoad()
  }


    //this method won't set the value of the variable why?
    canLoad(){

      this.run()
      console.log('after loop')
      console.log(this.companieslist)
    if (this.companieslist.length==0) {

      this.router.navigate(['/companysignup']);

    }
    else{
      this.router.navigate(['/dashboard']);
    }

    return this.authService.isLoggedIn();
  } 
  }


[enter image description here]this is what I am returning in the console

  • What I returned in the Console was after run() an array with one item. Then when I console.log(this.companieslist) I return an empty array –  Jun 04 '20 at 01:06
  • because console.log(this.companieslist) inside canLoad is evaluated before the observable emitted values and populated the array. js is asynchronous – ihor.eth Jun 04 '20 at 01:10

3 Answers3

1

The method run is async because of the request, you are not checking if the request already finished.

So, companiesList will always have the size equals to 0 if the request did not finish at the time it checks the condition.

Check this How to check all HTTP get request is completed (resolved) - Angular 2

Rafael Andrade
  • 495
  • 1
  • 5
  • 22
1

The issue is Async based. The run method runs a subscription that affects the campaniesList array. But when you call run and the subscription is fired, it doesn't stop executing your code. Thus, when you log right after run :

  this.run()
  console.log('after loop')
  console.log(this.companieslist)

It runs before what's nested in the subscribe block:

 this.dataService.sendGetRequestcompanies().subscribe((data: any[])=>{
  let tmp = [];

  for (let key in data)
    if (data.hasOwnProperty(key))
      tmp.push(data[key])

      this.companieslist = tmp;
      console.log(this.companieslist)
}

EDIT:

Example of a guard I have in place

 canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
   return this.userService.getPermissions(this.userService.userId).pipe(map((permissions: string[]) => {
       if(permissions.length === 0) {
            this.router.navigateByUrl('/dashboard');
       }
       return permissions.length > 0; // this is where you could check for some specific permission maybe?
   }),
   retry(3),
   catchError(() => {
    this.router.navigateByUrl('/dashboard');
    return of(false);
   })
iamaword
  • 1,327
  • 8
  • 17
  • I tried using settimeout method within canLoad() and it shows the correct value but it won't execute the next part of the script. Can you show me how to use async/await, please –  Jun 04 '20 at 01:55
  • The solution may involve addressing it in a guard class explicitly. So making a custom guard class that implements canActivate, associating that guard to a route that you want to restrict, and then using that class to do the async stuff. I know it was a pain when I did it so I'll post an example of a place where I implemented similar functionality (after lots of trial and error with the observable...) – iamaword Jun 04 '20 at 14:15
  • Hope that helps ya! – iamaword Jun 04 '20 at 14:18
0

The method type signature for CanLoad guard allows you to return an Observable<boolean>, a Promise<boolean>, or just a normal boolean

The CanActivate guard is similar except you can return a boolean or UrlTree but still allows it to be an Observable or a Promise. This means the function can wait for your call to finish.

For your purpose, CanActivate will be sufficient unless you want to prevent clients from loading different route modules entirely but thats beyond the scope of this answer.

Your dataService request returns an Observable, so all you would need to do is map the data returned from the request to the correct return type for CanActivate.

Here is one way you could do it:

import { Injectable } from '@angular/core';
import { CanActivate, Router, CanLoad } from '@angular/router';
import { map } from 'rxjs/operators';
import { AuthService } from 'src/app/auth.service';
import { User } from 'src/models/user';
import { ListdataService } from 'src/app/listdata.service';

@Injectable({
  providedIn: 'root'
})
export class CompanyonlyService implements CanActivate {

  constructor(private authService: AuthService, private router: Router,private dataService:ListdataService) { }

  canActivate() {
    return this.dataService.sendGetRequestcompanies().pipe(
      map((data: any[]) => {
        if(data && data.length > 0) return this.router.createUrlTree(['/dashboard']);
        else return this.router.createUrlTree(['/companysignup']);
      })
    );
  }

}

One of the main advantages to using Typescript is knowing what type your arguments are or what your return type is. It cannot save you from yourself if you do not declare them properly, and you miss out on seeing the different possible options you can return.

The fully typed method would be:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean|UrlTree> | Promise<boolean|UrlTree> | boolean|UrlTree {
    return this.dataService.sendGetRequestcompanies().pipe(
      map((data: any[]) => {
        if(data && data.length > 0) return this.router.createUrlTree(['/dashboard']);
        else return this.router.createUrlTree(['/companysignup']);
      })
    );
  }
Richard Schibly
  • 437
  • 2
  • 7