简体   繁体   中英

CanActivate Observable returning false when delay in service check

Context: I have created a route guard on my Angular application. When a guard is active on that route it activates the guard where it runs a check. In the check it calls a service to to get a value. With the value it then maps true/false. On true it navigates straight to the route and on false it shows a modal. Neither outcomes are relevant to the issue I'm facing.

Issue: In my test environment with mock data present, all works as expected. The check runs, gets the service and value. It then returns true/false based on that value and calls the relevant logic. In the development environment using the real service the value always returns undefined, therefore always triggers false. This only happens on the first instance. I suspected it was a delay in the service and the true/false logic ran before the service could return. Therefore I added a delay to my mock service and it replicated the issue.

Tried Fixes: I've tried adding .pipe(delay) to the method before it runs the true/false logic. I've also tried calling the service from the constructor to call it early.

routeGuard.guard.ts

import { catchError, map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { idService } from '../../idService.service';

@Injectable({
    providedIn: 'root'
})
export class UnauthorisedReportGuard implements CanActivate {
    constructor(private idService: idService) {}
isIdValid;
id;

validateId(queryParamsId): Observable<boolean> {
    this.idService.getId(queryParamsId).subscribe(selectedId => {
        this.id = selectedId;
        this.isIdValid = this.id.toLowerCase();
    });
    return of(this.validateIdBool());
}

validateIdBool(): boolean {
    if (this.isIdValid === 'validId') {
        return true;
    }
    return false;
}

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const queryParamsId = route.queryParams.id;

    return this.validateId(queryParamsId).pipe( //tried adding a delay here
        map(e => {
            // e returns false first time - this.isIdValid is undefined due to delay in getting service
            if (e) {
                return true;
            } else {
                //logic here if false
            }
        }),
        catchError(() => {
            return of(false);
        })
    );
}

}

Code explanation: canActivate is implemented and uses ActivatedRouteSnapshot to get an id from a queryParam . This is then passed to validateId(queryParamsId) method to call the service. Once the value has been found it then runs another method validateIdBool(): boolean to return true/false based on the value returned from the service call. True or flase is then returned to the canActivate where it returns this.validateId(queryParamsId).pipe(map(e =>... and mapped to e .

You have to use switchMap in this case, so you wait for the request to finish before returning the statement. Try something like this on your routeGuard.guard.ts:

@Injectable({
  providedIn: 'root',
})
export class UnauthorisedReportGuard implements CanActivate {
  constructor(private idService: idService) {}
  isIdValid;
  id;

  validateId(queryParamsId): Observable<boolean> {
    return this.idService.getId(queryParamsId).pipe(
      switchMap((request) => {
        this.id = request.id;
        this.isIdValid = this.id.toLowerCase();
        return of(this.validateIdBool(request.id.toLowerCase())); // parameter to avoid mutable variables.
      }),
      catchError(() => of(false))
    );
  }

  validateIdBool(id): boolean {
    return id === 'validId';
  }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const queryParamsId = route.queryParams.id;
    return this.validateId(queryParamsId);
  }
}

I have modified your code.

This code will also cache the result of already validated ids. The key was in the validateId function. When you have an observable you have to transform it, rather than creating a new one. If you would place breakpoints, you would see the order of and thus why it breaks.

This code will wait for the service to process the ID, take the result, see if it is valid if it throws, it returns false . After knowing the result, it will save it for the next use.

import { catchError, map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { idService } from '../../idService.service';

@Injectable({
    providedIn: 'root'
})
export class UnauthorisedReportGuard implements CanActivate {
    idValidity: {
        [key: string]: Observable<boolean>;
    } = {};

    constructor(private idService: idService) {}

validateId(queryParamsId): Observable<boolean> {
    if (!this.idValidity[queryParamsId]) {
        this.idValidity[queryParamsId] = this.idService.getId(queryParamsId).pipe(
            map(selectedId => this.validateIdBool(selectedId)), // Transform service result
            catchError(() => of(false)), // In case of error, return false
        ); // Save it for next use
    }

    return this.idValidity[queryParamsId];
}

// If this function is this simple, you can inline it into the `map` function
validateIdBool(selectedId: string): boolean {
    if (selectedId === 'validId') {
        return true;
    }
    return false;
}

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    return this.validateId(route.queryParams.id);
}

you can borrow a leaf from this snippet

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';


@Injectable({
  providedIn: 'root'
})
export class AdminAuthGuardGuard implements CanActivate {
  
  constructor( public _auth:AuthService, public _router:Router ) {

  }
  canActivate():boolean {
    if(this._auth.loggedIn()){
      return true;
    }
    else{
      this._router.navigate(['/login']);
      return false;
    }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM