#Signals and Guards

33 messages · Page 1 of 1 (latest)

jade abyss
#

Hello,

I am currently authenticating and storing the user inside a signal. I am also storing guards associated to this user inside another signal in my auth.service.ts.

My code is working when I navigate from / to /anything-else because my app.component is calling authenticate()

But if I refresh the page with /anything-else it redirects me because user and guards are not defined yet.

This is my Auth Service:

public _userGuards = signal<Guard | null>(null);
  
public authenticate() : void {    
    this.http
      .post<UserAccount>(authenticateUrl, null, { observe: 'response' })
      .subscribe({
        next: (response) => {
          this._userAccount.set(response.body as UserAccount);
        },
        error: () => {
          this._isAuthenticating.set(false);
              // Error handling
        },
        complete: () => {
          this._userGuards.set(this.getGuards()); 
          // getGuards example: {canView: true, canEdit: false}             
        },
      });
  }```

And this is my guard:

```export class CanViewGuard implements CanActivate {

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


  canActivate(): boolean {
    this.authService.authenticate();
    if (this.authService._userGuards().canView)
        return true;

    this.router.navigate(['unauthorized']);
  }
}```

If I put ```this.authService.authenticate();``` inside an if block it is also not working.

Also, I tried to call authenticate with APP_INITIALIZER but it is still not working.

I think that I am missing something with Signals...
fiery wind
#

Not sure if Guards support signals already.

#

But there are some async issues with your code.

stuck quartz
#

You can use functional guards instead of class.

What is more important - it is not said that subscription will be completed before you call if (this.authService...)

fiery wind
#

What would a functional guard solve ?

#

(I know the OP isnt returning signals, but yeah they want to return something async I think).

stuck quartz
#

About functional guards - it is not about solving, it is about new habits.

And I added what is really important.

#

So, if I were you - I would think about moving authenticate logic into guard and return boolean there.

fiery wind
#

Personally, I would look along the lines of

public authenticate() : void {    
    return this.http
      .post<UserAccount>(authenticateUrl, null, { observe: 'response' })
      .pipe(
         tap((response) => {
          this._userAccount.set(response.body as UserAccount);
         }),
       );
  }

public guards() : void {    
    this.authenticate()
      .pipe(
         map(() => this.getGuards())
       )
  }

and in the guard

export class CanViewGuard implements CanActivate {

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


  canActivate(): boolean {
    return this.authService.guards().pipe(
      map(guard => guard.canView),
      tap(canView => {
        if (!canView) {
          this.router.navigate(['unauthorized']);
        }
      })
    );
  }
}
#

Still not idea but solves the async issue.

#

I wouldnt bother moving to functional guards at this point, as that doesnt solve anything. But yes, it's worth considering going forward.

#

Also in the above snippet, authenticate() has changed. So wherever u call authenticate(), you will need to subscribe at some point.

#

Also, I would avoid the tap() in the authenticate(), but I left it for now.

stuck quartz
fiery wind
jade abyss
fiery wind
#

I think the authentication logic should stay in a service, as that is what services are for. Your main issue is with asynchronous code.

jade abyss
stuck quartz
#

how does it look?

#

your guard now

fiery wind
jade abyss
#

I have updated with the code you sent

#

both files

jade abyss
fiery wind
#

Ah

#
public authenticate() : Observable<unknown> {    
    return this.http
      .post<UserAccount>(authenticateUrl, null, { observe: 'response' })
      .pipe(
         tap((response) => {
          this._userAccount.set(response.body as UserAccount);
         }),
       );
  }

public guards() : Observable<Guard> {    
    this.authenticate()
      .pipe(
         map(() => this.getGuards())
       )
  }
jade abyss
#

    let authenticateUrl = `${this.authenticateApiUrl}`;

    return this.http
      .post<UserAccount>(authenticateUrl, null, { observe: 'response' })
      .pipe(
        tap((response: HttpResponse<UserAccount>) => {
          this._userAccount.set(response.body as UserAccount);
        })
      );
  }


  public guards(): Observable<Guard> {
    this.authenticate().pipe(
      map(()=>this.getGuards())
    );
    return;
  }


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


  canActivate() {
    return this.authService.guards().pipe(
        map((guard: Guard) => guard.canView),
        tap(canView =>{
            if(!canView){
                this.router.navigate(['unauthorized']);
            }
        })
    );
  }
}

I did this but now I can't even get my userAccount signal now the post request seems not working

fiery wind
#

Did u also ensure that everywhere u call authenticate(), you ensure you subscribe one way or the other?

jade abyss
#

I did not understand, how should I do that for example?

fiery wind
jade abyss
#
  public userAccount: Signal<UserAccount | null>;
  public userGuards: Signal<Guard | null>;
  constructor(
    private readonly authService: AuthService,
  ) {
  }


  public ngOnInit(): void {
    this.userAccount = this.authService._userAccount;
    this.userGuards = this.authService._userGuards;
   


    this.authService.authenticate();
  }




// Change identity
  public leaveIdentity(): void {
    this.authService.authenticate(10100101);
  }
}
```  This is an example in my main.component, I also pass some signals as props to other components
#
  <app-navbar [userAccount]="userAccount()" [userGuards]="userGuards()" (profileClick)="leaveIdentity()" />
}
``` like this