42

Is there any way we can redirect to a different component from @CanActivate in Angular2 ?

Vinz and Tonz
  • 3,487
  • 5
  • 21
  • 32

6 Answers6

69

As of today, with latest @angular/router 3.0.0-rc.1, here are a couple of references on how to do that through CanActivate guards on routes:

  1. angular 2 reference
  2. Two answers to this SO question, by Nilz11 and by Jason

The main gist of logic looks like:

// ...
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  if (this.authService.isLoggedIn) {
    // all ok, proceed navigation to routed component
    return true;
  }
  else {
    // start a new navigation to redirect to login page
    this.router.navigate(['/login']);
    // abort current navigation
    return false;
  }
}
Community
  • 1
  • 1
superjos
  • 12,189
  • 6
  • 89
  • 134
  • 5
    This should be the new preferred answer with the new router. – Downhillski Sep 09 '16 at 20:34
  • would love to see how the redirect to the intended path is done after user logs using this auth guard. – Dawit Jul 14 '17 at 04:15
  • @Davvit I'm not sure what to do with proposed edit: by just adding a parameter to a call we're not providing much more information. Wouldn't be more helpful to me and others if you add a new answer, where you put all the additions and changes to satisfy your query? What do you think? – superjos Jul 14 '17 at 09:22
14

Your guard can easily just be an injectable which, as such, can include its own injectables. So we can simply inject the router, in order to redirect. Don't forget to add the service as a provider in your app module.

@Injectable()
export class AuthGuard implements CanActivate {

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

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
    if (!authService.isAuthenticated()) {
      this.router.navigate(['/login']);
      return false;
    }
    return true;
  }
}

export const ROUTES: Routes = [
  {path: 'login', component: LoginComponent},
  {path: 'protected', loadChildren: 'DashboardComponent', canActivate: [AuthGuard]}
];
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
12

Yes, you can! Doing this will prevent your component from instantiating for nothing.

First, make a new file src/app-injector.ts

let appInjectorRef;

export const appInjector:any = (injector = false) => {
    if (!injector) {
        return appInjectorRef;
    }

    appInjectorRef = injector;

    return appInjectorRef;
};

Then, get the reference from bootstrap

// ...
import {appInjector} from './app-injector';
// ...


bootstrap(App, [
  ROUTER_PROVIDERS
]).then((appRef) => appInjector(appRef.injector));

Finally in your function

// ...
import {appInjector} from './app-injector';
// ...

@CanActivate((next, prev) => {
  let injector = appInjector();
  let router = injector.get(Router);

  if (42 != 4 + 2) {
    router.navigate(['You', 'Shall', 'Not', 'Pass']);
    return false;
  }

  return true;
})

Et voilà !

It was discussed here https://github.com/angular/angular/issues/4112

You can find a complete example here http://plnkr.co/edit/siMNH53PCuvUBRLk6suc?p=preview by @brandonroberts

Jk Jensen
  • 339
  • 2
  • 4
  • 16
Druxtan
  • 1,311
  • 2
  • 15
  • 21
8

As of Angular 7.1, you can return UrlTree instead of boolean:

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

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

  canActivate(): boolean | UrlTree {
    return this.authService.isAuthenticated() || this.router.createUrlTree(['/login']);
  }
}
3

This might help someone who is trying to wait on something before continuing.

waitForBonusToLoad(): Observable<[boolean, string]> {

const userClassBonusLoaded$ = this.store.select(fromRoot.getUserClasssBonusLoaded);
const userClassSelected$ = this.store.select(fromRoot.getClassAttendancesSelectedId);

return userClassBonusLoaded$.withLatestFrom(userClassSelected$)
  .do(([val, val2]) => {
    if (val === null && val2 !== null) {
      this.store.dispatch(new userClassBonus.LoadAction(val2));
    }
  })
  .filter(([val, val2]) => {
    if(val === null && val2 === null) return true;
    else return val;
  })
  .map(val => {
    return val;
  })
  .take(1);
}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.waitForBonusToLoad().map(([val, val2]) =>
  {
    if(val === null && val2 === null){
      this.router.navigate(['/portal/user-bio']);
      return false;
    }
    else {
      return true;
    }
  }
)
}
Post Impatica
  • 14,999
  • 9
  • 67
  • 78
  • Your proposed solution contains side effects within Observable.map() Instead, you should move the navigation out of map() and into do(). Your map should simply resolve TRUE/FALSE. Observable$.map((x) => x === 1).do(result => { if (result) navigate(); }); – josh-sachs Apr 04 '17 at 20:39
2

If you are using an observable to determine, you can leverage tap operator from rxjs:

@Injectable({
  providedIn: SharedModule // or whatever is the equivalent in your app
})
export class AuthGuard implements CanActivate {

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

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.authService.isLoggedIn()
      .pipe(tap((isLoggedIn: boolean) => {
        if (!isLoggedIn) {
          this.router.navigate(['/']);
        }
      }));
  }

}
s.alem
  • 12,579
  • 9
  • 44
  • 72