Services & RoutingPro· 40 min read

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.

A functional canActivate guard
// 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:

Protect the dashboard with the guard
// 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.

A lazy-loaded route with loadComponent
// 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).

A resolver that pre-fetches tasks
// 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
};
The route waits for taskResolver before showing
// 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.

ToolQuestion it answersRuns
Guard (canActivate)May this user enter?Before the route
Lazy loadingWhen 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?

Answer: A guard returning false blocks navigation to the route. Guards run before the route activates to decide whether the user may proceed.

✍️ Practice

  1. Write an authGuard that allows a route only when a boolean loggedIn is true.
  2. Convert one route to loadComponent lazy loading and confirm it still works.

🏠 Homework

  1. Build a two-route app where /admin is protected by a guard and lazy-loaded, redirecting to /login when not allowed.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →