Route Guards, Lazy Loading & Resolvers
Guards decide who may visit a route, lazy loading delays heavy pages until needed, and resolvers fetch data before a page appears.
What you will learn
- Protect a route with a canActivate guard
- Lazy-load a component so it downloads only when visited
- Pre-fetch data with a resolver
Three ways to control navigation
Once an app has several pages, you need more control over them: stop some users reaching certain pages, avoid downloading every page up front, and make sure a page has its data before it shows. Angular’s router gives you three tools for exactly this.
- Route guards — a yes/no check that runs before a route loads (e.g. “is the user logged in?”).
- Lazy loading — only download a page’s code when it is first visited, so the app starts faster.
- Resolvers — fetch the page’s data before the page appears, so it never flashes empty.
A guard: who may enter?
A guard is a small function that returns true (allow) or false (block). Modern Angular uses functional guards — just a function. Here canActivate checks a service before letting the user in, and sends them to the login page if not.
// auth.guard.ts
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true; // allow the route
}
router.navigate(['/login']); // otherwise redirect
return false; // block the route
};Note: Output:
(No visible output by itself.)
The guard asks AuthService if the user is logged in. If yes it returns true and the page loads; if no it sends them to /login and returns false to block the page. inject(...) grabs a service inside a plain function, the same idea as constructor injection.
Attach the guard to a route with the canActivate array:
// app.routes.ts
import { authGuard } from './auth.guard';
import { DashboardComponent } from './dashboard.component';
export const routes = [
{ path: 'dashboard', component: DashboardComponent,
canActivate: [authGuard] } // protected route
];Note: Output:
Visiting /dashboard while logged out → redirected to /login.
Visiting it while logged in → the dashboard shows. The guard runs every time, before the component is created.
Lazy loading: download pages on demand
By default every page’s code is bundled together, so a big app is slow to start. Lazy loading splits a page into its own file that downloads only when the user first visits it. You write loadComponent with a dynamic import instead of component.
// app.routes.ts
export const routes = [
{ path: 'reports',
loadComponent: () =>
import('./reports.component').then(m => m.ReportsComponent) }
];Note: Output:
(The reports page’s code downloads only the first time you open /reports.)
Until someone visits Reports, none of its code is loaded — so the app starts faster and uses less data. The import(...) returns a promise, and .then picks out the component class.
Resolvers: data ready before the page
A resolver fetches data before the route activates, so the page opens already filled in — no empty flash while you wait. It is a function that returns the data (or an Observable of it).
// task.resolver.ts
import { inject } from '@angular/core';
import { TaskService } from './task.service';
export const taskResolver = () => {
const service = inject(TaskService);
return service.getTasks(); // router waits for this
};// app.routes.ts — attach the resolver
{ path: 'tasks', component: TasksComponent,
resolve: { tasks: taskResolver } }Note: Output:
(The tasks page appears already showing the list — never blank.)
The router runs taskResolver first, waits for the data, then shows TasksComponent with the result available under the key tasks (read via ActivatedRoute). The user never sees a half-loaded page.
| Tool | Question it answers | Runs |
|---|---|---|
Guard (canActivate) | May this user enter? | Before the route |
| Lazy loading | When do we download this page? | On first visit |
Resolver (resolve) | Is the data ready? | Before the route shows |
Watch out: A guard is a client-side convenience, not real security. Always check permissions on the server too — a determined user can bypass front-end code.
Tip: Lazy-load big feature areas (admin, reports, settings) that not everyone visits. Your app’s first load stays small and snappy, and rarely-used code downloads only when actually needed.
Q. What does a canActivate guard returning false do?
✍️ Practice
- Write an
authGuardthat allows a route only when a booleanloggedInis true. - Convert one route to
loadComponentlazy loading and confirm it still works.
🏠 Homework
- Build a two-route app where
/adminis protected by a guard and lazy-loaded, redirecting to/loginwhen not allowed.