3

How can I make use of the latest CanActivateFn in Angular 16 with DI?

The recent Angular 16 uses a function, not a class, for the canactivate functionality. This is my code below. How can I add my DI's that would normally be in the constructor in the function?

CanActivateFn function code:

export const authGuard: CanActivateFn = (
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  return inject(TokenService).authenticated.pipe(
    take(1),
    map((status) => {
      console.log('auth status: ', status);
      if (!status) {
        return inject(Router).createUrlTree(['/login']);
      }
      return true;
    })
  );
};

The route settings:

const routes: Routes = [
  {
    path: '',
    component: DefaultComponent,
    canActivate: [authGuard],
    children: [
      {
        path: '',
        component: DashboardComponent,
        data: { title: 'My Dashboard' },
      },
]
Kms
  • 1,082
  • 2
  • 11
  • 27
joseph chikeme
  • 184
  • 1
  • 12

6 Answers6

5

I figured it out. I tried this. I updated my service BehavoirSubject variable to use signal().

export class TokenService {
  authenticated = signal(false);
}

Then I updated my authGuard function using the inject() function.

export const authGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  // console.log('guard status: ', inject(TokenService).authenticated());

  return inject(TokenService).authenticated()
    ? true
    : inject(Router).createUrlTree(['/auth/login']);
};

This approach worked for me.

joseph chikeme
  • 184
  • 1
  • 12
1

To add DI's to the authGuard function in Angular 16, you can create a factory function that returns the authGuard function with the dependencies injected. Here is an example:

import { Injectable } from '@angular/core';
import {
  CanActivateFn,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router,
} from '@angular/router';
import { TokenService } from './token.service';
import { take, map } from 'rxjs/operators';

function authGuardFactory(tokenService: TokenService, router: Router): CanActivateFn {
  return (
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) => {
    return tokenService.authenticated.pipe(
      take(1),
      map((status) => {
        console.log('auth status: ', status);
        if (!status) {
          return router.createUrlTree(['/login']);
        }
        return true;
      })
    );
  };
}

@Injectable({
  providedIn: 'root',
})
export class AuthGuard {
  constructor(private tokenService: TokenService, private router: Router) {}

  canActivate: [authGuardFactory(this.tokenService, this.router)];
}

In this example, we define a authGuardFactory function that takes in the dependencies TokenService and Router and returns the authGuard function with the dependencies injected using inject(). Then, we use the authGuardFactory function as the canActivate property in our AuthGuard class. This way, we can still use dependency injection in the authGuard function even though it is a function and not a class.

1

I had a similar issue; luckily, your code matches mine.

import { inject } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivateFn,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { map } from 'rxjs';
import { AuthService } from 'src/app/services/auth.service';

export const AuthGuard: CanActivateFn = (
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const authService: AuthService = inject(AuthService);
  const router: Router = inject(Router);

  return authService.isAuthenticated().pipe(
    map((status) => {
      console.log(status);
      if (status) {
        return true;
      }

      return router.createUrlTree(['/auth/login']);
    })
  );
};

NOTE: If your routes are lazy loaded, then add this auth guard in your app-routing.module.ts file as its recommended

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from './guards/auth-guard/auth.guard';

const routes: Routes = [
  {
    path: '',
    pathMatch: 'full',
    redirectTo: 'auth/login',
  },
  {
    path: 'auth',
    loadChildren: () =>
      import('./screens/auth/auth.module').then((m) => m.AuthModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./screens/dashboard/dashboard.module').then(
        (m) => m.DashboardModule
      ),
    canActivate: [AuthGuard],
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Hope This Works!

0
import { Injectable, inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';


@Injectable()
 export class PermissionsService {

 constructor(
 private cookieService: CookieService,
 public router: Router,
 ) { }

 canActivate(): boolean {
 if (this.cookieService.get('teacher-plus-user')) {
   return true
 } else {
   this.router.navigate(['/login']);
   return false
   }
 }

 }

export const authGuardGuard: CanActivateFn = (route, state) => {
  return inject(PermissionsService).canActivate();
};
  • Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. **Would you kindly [edit] your answer to include additional details for the benefit of the community?** – Jeremy Caney Jul 14 '23 at 16:47
0

See here is the solution which worked out for me ...(auth.guard.ts) File

import { CanActivateFn } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { ɵɵinject } from '@angular/core';

 // Define the authGuard function, which implements CanActivateFn interface
export const authGuard: CanActivateFn = (route, state) => {
  // Use dependency injection to get an instance of the AuthService
  const authService =  ɵɵinject(AuthService);

  // Check if the user is logged in using the AuthService
  if (authService.loggedIn()) {
    return true; // If logged in, allow access to the route
  } else {
    return false; // If not logged in, deny access to the route
  }
};

this is what my service file looks like (AuthService)

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
@Injectable({
  providedIn: 'root'
})
export class AuthService {
  router : any;
  constructor(private route: Router) {
      this.router = route
   }
  loggedIn() {
    return !!localStorage.getItem('login_token');
  }
}

was able to inject authService having method which returns true if authenticated

0

check if this code is working for you, it work for me


import {CanActivateFn, Router} from '@angular/router';
import {AuthenticationService} from "../services/authentication.service";
import {User} from "../models/user.model";
import {inject} from "@angular/core";

export const authGuard: CanActivateFn = (route,
                                         state) => {
  let currentUser = new User();
  inject(AuthenticationService).currentUser.subscribe( data => {
    currentUser = data
  });
  if (currentUser) {
    // @ts-ignore
    if (route.data.roles?.indexOf(currentUser.role) === -1){
      inject(Router).createUrlTree(['/401']);
      return inject(Router).createUrlTree(['/401']);
    }
    return true
  }
    return inject(Router).createUrlTree(['/login']);

};


youssef
  • 91
  • 1
  • 7