All too often I become frustrated when building an authentication component. The asynchronous flow is hard to manage and it makes my template a mess of ngIfs and Elvis operators:
{{ (user | async)?.uid }}
The problem is always same. I’m mixing too many concerns in one place. Organizing the right code in the right place is what makes a happy codebase.
The solution: Store route specific code in Angular’s Route Guards.
Route Guards
1. Identify Route code in your component
The AngularFireAuth
service is a great match for routing code. It contains information about a current user, whether they are coming from a redirect, and all sorts of other things.
Take a simple signInWithRedirect
example:
import { Component, OnInit } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import { Router } from '@angular/router';
import * as firebase from 'firebase/app';
@Component({
selector: 'app-login',
template: `
<button class="btn" (click)="auth()">
Login
</button>
`,
styleUrls: ['./login.scss']
})
export class LoginComponent implements OnInit {
constructor(
private afAuth: AngularFireAuth,
private router: Router
) { }
ngOnInit() {
this.afAuth.getRedirectResult().then(result => {
if(result) {
this.router.navigateByUrl('profile');
}
});
}
auth() {
const provider = new firebase.auth.GoogleAuthProvider();
this.afAuth.auth.signInWithRedirect(provider);
}
}
Here’s what’s going in this example:
- The user is logged in with the
auth()
method. - The
signInWithRedirect()
method triggers a redirect to sign in with Google. - After the user selects their account, they are taken back to this component.
- The
getRedirectResult()
promise inngOnInit()
resolves the logged in user. - The router navigates the user to the profile page via the
navigateByUrl()
method.
This isn’t a problem with one component. However, imagine having to manage redirects in multiple components.
The code in ngOnInit
belongs in a Route Guard. This will clean up the component of routing logic and remove the Router
dependency entirely.
2. Create a Route Guard
A Route Guard is used for all sorts of useful things. In this case a guard can handle the redirect code.
@Injectable()
export class RedirectGuard implements CanActivate {
constructor(
private authService: AngularFireAuth,
private router: Router
) {}
canActivate() {
this.authService.auth.getRedirectResult().then(result => {
if(result.user != null) {
this.router.navigateByUrl('profile');
return false;
}
return true;
});
}
}
The canActivate
method determined if the user has access to the route. Here’s what happens:
- The user visits the route.
AngularFireAuth
checks if a redirect result exists.- If a user comes back from a redirect, route to the
'profile'
route. - Else, go to the current route.
3. Set up the route
This code is now reusable. The Angular Router lets you specify which routes you want to listen for a redirect.
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { RedirectGuard } from './redirect.guard';
const ROUTES: Routes = [
{
path: '',
component: LoginComponent,
canActivate: [RedirectGuard]
},
{
path: 'profile',
loadChildren: 'app/profile/profile.module#ProfileModule'
}
];
const AppRoutes = RouterModule.forRoot(ROUTES);
export { AppRoutes }
4. No more routing code in the component
Now the LoginComponent
is free of routing code:
@Component({
selector: 'app-login',
template: `
<button class="btn" (click)="auth()">
Login
</button>
`,
styleUrls: ['./login.scss']
})
export class LoginComponent {
constructor(private afAuth: AngularFireAuth) { }
auth() {
const provider = new firebase.auth.GoogleAuthProvider();
this.afAuth.auth.signInWithRedirect(provider);
}
}
No more async flow mess. The right code is organized in the right place. You can add the RedirectGuard
to more routes as the app grows, which keeps the complexity out of your components.