Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Angular Router Basics

Episode 113 written by Jeff Delaney
full courses and content on fireship.io

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v6.0.4

Find an issue? Let's fix it

Source code for Angular Router Basics on Github

Routing is a critical UX element for most Angular apps. Your route configuration has a major impact on pageload performance, search engine optimization, security, and the user experience. In this lesson, you will learn all of the core techniques needed to use the Angular effectively.

Let’s start this lesson by generating a brand new Angular app with the CLI:

ng new awesome-app --routing

Using the Angular router with firestore dynamic child routes

Angular Router Fundamentals

In this first section, I will cover the absolute basics of routing and navigation. You can think of the router as a global service that holds information about the state of the URL.

Creating Routes and Navigating

The router will render a component in the <router-outlet></router-outlet> of it’s parent component, which is the app.component by default. Here is the most basic use case.

import { HomeComponent } from './home/home.component';

const routes: Routes = [
{ path: 'home', component: HomeComponent }
];

Now setup a link in the HTML that points to this path.

<a routerLink="/home">Home Page!</a>

Alternatively, you might want to navigate from the a component’s TypeScript. This is useful when you want to change routes after the user signs in without them needing to tap another button.

import { Router } from '@angular/router';

@Component({})
export class HomeComponent {
constructor(private router: Router) {}

goHome() {
this.router.navigate(['home']);
}
}

Child Routes with Parameters

Let’s imagine we have a list of Animals and want to render some details about a specific animal on navigation to /animals/elephant. But our database has thousands of animals, so we need to generate this route dynamically. Adding : to a path segment makes it a dynamic value.

const routes: Routes = [
{
path: 'animals',
component: ParentComponent,
children: [{ path: ':name', component: ChildComponent }]
}
];

In this case, we can extract the animal’s name or ID from /animals/elephant from the URL segment name.

In the parent component, we would loop over our animal objects from the database and create a router link based on the animal name:

<li *ngFor="let animal of animals$ | async">

<a [routerLink]="['/animals', animal.name]">{{ animal.name }}</a>

</li>

<router-outlet></router-outlet>

The child component will be rendered inside the outlet of the parent.

Wildcard Route 404 Page

You can use the ** path as a catchall route to handle non-existent data gracefully. Make sure this is the very last route in the routes array. And definitely use a funny gif on your error page.

const routes: Routes = [{ path: '**', component: ErrorComponent }];

A router 404 page demo in Angular 6

Redirect Routes

You don’t have to navigate to a component. Instead, you might want to redirect to an existing route.

const routes: Routes = [
{ path: 'animales', redirectTo: 'animals', pathMatch: 'full' }
];

Change CSS on the Active Route

Angular makes it easy to change the CSS styling to an actived route. The router has a special property binding routerLinkActive that will apply the specified CSS classes provided on the right side.

<a routerLinkActive="highlight">Some Link</a>

Then give it a CSS class to make it stand out:

.highlight {
font-weight: bold;
font-size: 1.5em;
color: #23d160;
}

Guards

A guard is just an injectable Angular service used to control the behavior of certain routes. The three most common user interfaces include:

  1. canActvate - block routes based on a condition, commonly a user’s auth state.
  2. canDeactivate - forces user to stay on the route, commonly used prevent loss of unsaved changes on a form.
  3. resolve - preload additional data that can be accessed from the route.

CanActivate Example

In this example, we simulate an asynchronous call with a timer of 1s to verify a user is an admin. The canActivate guard will automatically subscribe and use the emitted boolean to block or allow access.

@Injectable({
providedIn: 'root'
})
export class AdminGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return timer(1000).pipe(
map(v => false),
tap(v => alert('Only ADMINS allowed here!'))
);
}
}

Then apply the guard to the protected routes.

const routes: Routes = [
{ path: 'secret', component: SecretComponent, canActivate: [AdminGuard] }
];

Preload Data with the Router Example

One of the most powerful mechanisms for keeping your code DRY is to preload data with resolve. If you need to query the same data on multiple routes, a resolver can dramatically simplify your code. In the code below, we extract a segment from the route, then return an Observable that fetches the data asynchronously.

@Injectable({
providedIn: 'root'
})
export class Preloader implements Resolve<any> {
constructor(private afs: AngularFirestore, private router: Router) {}

resolve(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any> {
const name = next.paramMap.get('id');
return http.get(id).pipe(
tap(data => {
if (!data) {
alert('Data not found!');
this.router.navigate(['home']);
}
})
);
}
}

All routes with the specified resolver will have their data accessible when the component is initialized.

const routes: Routes = [
{
path: 'settings/:id',
component: SettingsComponent,
resolve: [PreloadData]
},
{
path: 'playground/:id',
component: PlaygroundComponent,
resolve: [PreloadData]
}
];

You can then access the data as an Observable from the component. Much cleaner than repeating the same data-fetching code in multiple components.

import { ActivatedRoute } from '@angular/router';
@Component(...)
export class SettingsComponent implements OnInit {
constructor(private route: ActivatedRoute) {}

ngOnInit() {
this.data$ = this.route.data;
}
}

Lazy Loaded Routes

Lazy loading is a critical design pattern for progressive web apps because it allows you break your app’s JS bundles into chunks with code splitting, thus increasing initial page load performance.

If you have a poor app performance due to a massive main.js bundle, you will very likely need to use lazy loading, but don’t worry, it’s easy and I covered it in detail back in episode 23, which has been fully updated to work with Angular 6.