Routing & Navigation in Angular

Angular Routing & Navigation

Table of contants

  1. Router Basics
  2. Route Parameters
  3. Query Parameters
  4. Child Routes
  5. Lazy Loading
  6. Route Guards
  7. Preloading Strategies
  8. Interview Questions

Router Basics

What is Angular Router?

Angular Router is a powerful navigation library that enables navigation from one view to another based on URL changes. It interprets browser URLs as instructions to navigate to a client-generated view.

Setting up Router

app.config.ts (standalone)

import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
    providers: [
        provideRouter(routes)
    ]
};

app.routes.ts

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

export const routes: Routes = [
    {path: '', redirectTo: '/home', pathMatch: 'full'},
    {path: 'home', component: HomeComponent},
    {path: 'about', component: AboutComponent},
    {path: '**', component: PageNotFoundComponent} // wildcard route
];

app.component.html

<nav>
    <a routerLink="/home" routerLinkActive="active">Home</a>
    <a routerLink="/about" routerLinkActive="active">About</a>
</nav>

<router-outlet></router-outlet>

Router Method

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

export class AppComponent{
    constructor(private router: Router){}

    navigateTAbout(){
        // Programmatic navigation
        this.router.navigate(['/about']);

        // Navigate with extras
         this.router.navigate(['/about'], {
            queryParams: {id: 123},
            fragment: 'section1'
         });
    }

    navigateRelative(){
        //Relative navigation
        this.router.navigation(['../sibling'], { relativeTo: this.route });
    }
}

Route Parameters

Required route paramenters

Route paramenters are part of the URL path and are mandatory for the route to match.

Defining route with parameters

export const routes: Routes =[
    {path: 'user/:id', component:UserDetailsComponent},
    {path: 'product/:category/:id', component: ProductComponent}
];

Accessing Route paramemters - snapshot

import { Component, OnInit} from '@angular\core';
import { ActivatedRoute } from '@angular\router';

@component({
    selector: 'app-user-detail',
    template:`<h2>User ID: {{userId}} </h2>`
})
export class UserDetailsComponents implements OnInit {
    userId: string = '';
    constructor(privete route:ActivatedRoute){}
    ngOnInit(){
        //using snapshot (for one time read)
        this.userId = this.route.snapshot.paramMap.get('id') ||'';
    }
}

Accessing Route Parameters -Observable

import { Component, OnInit} from '@angular\core';
import { ActivatedRoute } from '@angular\router';

@component({
    selector: 'app-user-detail',
    template:`<h2>User ID: {{userId}} </h2>`
})
export class UserDetailsComponents implements OnInit {
    userId: string = '';
    constructor(privete route:ActivatedRoute){}
    ngOnInit(){
        //using Observable (for reactive updates)
        his.route.paramMap.subscribe(params =>{
            this.userId = params.get('id')||'';
            // Load user data based on new Id 
        });
    }
}

Navigating with Parameters

// Template
<a [routerLink]="['/user', userId]">View User </a>

//Component
this.router.navigate(['/user', userId]);
this.router.navigate(['/product', category, productId]);


Query Paramenters

Query parameters are optional parameters that apear after the ? in the URL.

Using Query Parameter

Defining Query Parameters

// Template
<a [routerLink]="['/products']"
    [queryParams]="{category: 'electronics', sort: 'price' }">
    Products
</a>

// Component
this.router.navigate(['/products'], {
    queryParams: {
        category: 'electronics',
        sort: 'price',
        page: 1
    }
});

Accessing Query Parameters

import {Component, OnInit} from '@angualr/core';
import {ActivatedRoute} from '@angular/router';

@Component({
    selector: 'app-products',
    templateUrl: './products.component.html'
})
export class ProductComponent implements OnIntit {
    category: string = '';
    sortBy: string = '';
    page: number =1;

    constructor(private router: ActivatedRoute){}

    ngOnInit(){
        //Using Snapshot
        this.category = this.router.snapshot.queryParamMap.get('category') || '';
        // Using observable
        this.route.queryParamMap.subscribe(params =>{
            this.category = params.get('cotegory') || '';
            this.sortBy = params.get('sort') || 'name';
            this.page = Number(params.get('page')) ||1;
            this.loadProducts();
        })    
    }
    loadProducts(){
        // load Product based on query params
    }
}

Query Params Handling Opetions

//Merge with existing query params 
this.router.navigate(['/products'], {
    queryParams: {page: 2},
    queryParamsHandeling: 'Image' // Keeps exiting params
});

//Preserve all existing query params
this.router.nevigate(['/other-page'], {
    queryParamsHandling: 'preserve'
});

Child Routes

Child routes allow you to create nested routing structures for component hirerachies.

Defining Child Routes

app.route.ts

import { Routes } from '@angualr/router';

export const routes: Routes = [
    {
        path: 'admin', 
        component: AdminComponent,
        children: [
            {path: '', redirectTo: 'dashboard', pathMatch: 'full'},
            {path: 'dashboard', component: DashboardComponent},
            {path: 'User', component: UserComponent},
            {path: 'setting', component: SettnigComponent},
            {
                path: 'reports',
                component: ReportComponent,
                children: [
                    {path: 'sales', component: SalesComponent},
                    {path: 'inventory', component: InvonteryComponent}
                ]
            }
        ]
    }
]

admin.component.html (Parent Component)

<div class="admin-container">
    <nav class="sidebar">
        <a routerLink="dashboard" routerLinkActivated="Active"> Dashboard </a>
        <a routerLink="users" routerLinkActivated="Active"> Users </a>
        <a routerLink="settings" routerLinkActivated="Active"> Settings</a>
        <a routerLink="reports" routerLinkActivated="Active"> Reports </a>
    </nav>

    <div class="content">
        <!-- Child component render here -->
         <router-outlet></router-outlet>
    </div>
</div>

Accessing Parant Route Data

import {Component, OnInit} from '@angualr/core';
import {ActivatedRoute} from '@angular/router';

@Component({
    selector: 'app-dashboard',
    templateUrl: './dashboard.component.html'
})

export class DashboardComponent implements OnInit{
    constructor(Private route: ActivatedRoute){}

    ngOnInit(){
        // Access parent route data
        const parentData = this.parent?.snapshot.data;

        // Acces parent route params

        const parentData = this.parent?.snapshot.paramMap;
    }
}

Lazy Loading

Lazy loading helps reduce initial bundle size by loading feature modules on demand.

Implementing Lazy Loading

app.routes.ts

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

export class routes: Routes = [
    {path: '', redirectTo: '/home', pathMatch: 'full'},
    {path: 'home', component: HomeComponent},

    // Lazy load modules
    {
        path: 'products',
        loadChildren: ()=> import('./products/products.routes')
            .then(m => m.PRODUCTS_ROUTES)
    },
    {
        path: 'admin',
        loadChildren: ()=> import('./admin/admin.routes')
            .then(m => m.ADMIN_ROUTES),
        canActivate: [AuthGuard] // Guard applied to lazy loded module
    }
];

products/products.routes.ts

import {Routes } from '@angular/router';
import { ProductListComponent} from './product-list/product-list.component';
import { ProductDetailComponent} from './product-Detail/product-Detail.component';

export const PRODUCTS_ROUTES: Routes = [
    {path: '', component: ProductListComponent},
    {path: ':id', component: ProductDetailComponent}
];

Lazy Loading a component (Angular17+)

export const routes: Routes =[
    {
        path: 'about',
        loadComponent: () => import('about/about.component')
            .then(m => m.AboutComponent)
    }
];

Benifits of Lazy Loading

  • Reduced initial bundle size
  • Faster initial load time
  • Load features only when needed
  • Batter performance for larze application

Route Guards

Route guards control navigation to and from routes. They are implemented as functions (functional guard) or classes (Angular < v15>).

Type of Route Guards

  1. CanActivate - Controls route activation
  2. CanActivateChild - Cntrols child route activation
  3. CanDeactivate - Controls route deactivation
  4. CanLoad - Controls Lazy loading of modules (deprecated in favor of canMatch)
  5. CanMatch - Controls if a route can be matched
  6. Resolve - pre-fatches data before route activation

AuthGuard (CanActivate) - Functional Approch

auth.guard.ts

import { inject } from '@angular\core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivationFn = (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    if(AuthService.isAuthenticated()){
        return true;
    }

    // Store the attempted URL for redirecting after login
    authService.redirectUrl = state.url;

    // Navigate to login page
    router.navigate(['/login']);
    return false
};

Using AuthGuard

export const routes: Routes =[
    {path: 'login', component: LoginComponent},
    {
        path: 'dashboard',
        component: DashboardComponent,
        canActivate: [authGuard]
    },
    {
        path: 'admin',
        component: AdminComponent,
        canActivate: [authGuard, roleGuard], //Multiple Guards
        data: {roles: ['admin']}
    }
];

Role-Based Guard

role.guard.ts

import { inject } from '@angular\core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const roleGuard: CanActivateFn = (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    const requiredRoles = route.data['roles'] as string[];
    const userRole = authService.getUserrole();

    if(requiredRoles.include(userRole)){
        return true;
    }

    // Redirect to unathorized page
    router.navigate(['/unauthorized']);
    return false;
};

CanActivateChild Guard

admin.guard.ts

import { inject } from '@angular\core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const adminGuard: CanActivateChildFn = (route, state) => {
    const authService = inject(AuthService);

    // Check if user has admin privileges 
    return authService.hasAdminAccess();
};

Using CanactivateChild

export const routes: Routes = [
    {
        path: 'admin',
        component: AdminComponent,
        canActivateChild: [adminGuard],
        children: [
            {path: 'users', component: UsersComponent},
            {path: 'settings', component: SettingsComponent}
        ]
    }
];

CanDeactivate Guard

unsaved-changes.guard.ts

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

export interface CanComponentDeactivate {
    canDeactivate: () => boolean || Promise<boolean>;
}

export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> =
(comonent) => {
    if(component.canDeactivate){
        return component.canDeactivate();
    }
    return true;
};

form.component.ts

import { Component } from '@angular/core';
import {CanComponentDeactivate } from './unsaved-changes.guard';

@Component({
    selector: 'app-form',
    templateUrl: './form.component.html'
})
export class FormComponent implements CanComposeDeactivate {
    formChanged: boolean = false;

    canDeactivate(): boolean {
        if(this.formChanged){
            return confirm('You have unsave changes. Do you realy want to leave?');
        }
        return true;
    }
    OnFormChange(){
        this.formChanged = true;
    }
}

Using CanDeactivate

export const routes: Routes = [
    {
        path: 'edit/:id',
        component: FromComponent,
        canDeactivate: [unsavedChangesGuard]
    }
];

Async Guard with Observable

auth.guard.ts (Observable-based)

import { inject } from '@angular\core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';
import { map } from 'rxjs/operators';

export const authGuard: Canactivate = (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    return authService.isLoggedIn$.pipe(
        map(isLoggedIn => {
            if(isLoggedIn) => {
                return true;
            }
            router.navigate(['/login']);
            return false;
            
        })
    );
};

Class-Based Guards ( Legacy - Angular < v15)

auth.guard.ts(class-based)

import { injectable } from '@angular/core';
import {
    ActivatedRouteSnapshot,
    RouterStateSnapshot,
    CanActivate,
    Router
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
    provideIn: 'root'
})
export class AuthGuard implements CanActivate {
    constructor(
        private authService: AuthService,
        private router: Router
    ){}

    canActivate(
        route: ActivateRouteSnapshot, 
        state: RouterStateSnapshot
    ): boolean {
        if(this.authService.isAuthenticated()){
            return true;
        }

        this.router.navigate(['/login'],{
            queryParams: {returnUrl: state.url}
        });
        return false;
    }
}

Preloading Strategies

Preloading strategies determaine when and how lazy-loaded modules are loaded in the background.

Built-in Preloading Strategies

1. NoPreloading (Default)

import { ApplicationConfig } from '@angualr/core';
import { provideRouter, NoPreloading } from '@angualr/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
    Providers: [
        provideRouter(
            routes, 
            withPreloading(NoPreloading) // No preloading
        )
    ]
};

2. PreloadAllModules

import { ApplicationConfig } from '@angualr/core';
import { provideRouter, PreloadAllModules } from '@angualr/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
    Providers: [
        provideRouter(
            routes, 
            withPreloading(PreloadAllModules) // Preload all lazy modules
        )
    ]
};

Custom Preloading Strategy

selective-preloading-strategy.ts

import { Injectable } from '@angualr/core';
import { Route, PreloadingStrategy } from '@angualr/router';
import { of, Observable } from 'rxjs';

@Injectable({
    provideIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy{
    preloadedModules: string[] = [];

    preload( route: Route, load: () => Observable<any>): Observable<any>{
        if(route.data && route.data['preload']){
            // add the route path to the preloaded module array
            this.preloadedModules.push(route.path || '');

            console.log('Preload: ' + route.path);
            return load();
        }else{
            return of(null);
        }
    }
}

Using Custom Preloading Strategy

import { ApplicationConfig } from '@angualr/core';
import { provideRouter } from '@angualr/router';
import { routes } from './app.routes';
import { SelectivePreloadingStrategy } from './Selective-Preloading-Strategy';

export const appConfig: ApplicationConfig = {
    Providers: [
        provideRouter(
            routes, 
            withPreloading(SelectivePreloadingStrategy) 
        )
    ]
};

app.route.ts with preload data

export const routes: Routes = [
    {
        path: 'products',
        loadChildren: () => import('./products/product.routes')
            .then(m => m.PRODUCT_ROUTES),
        Data: { preload: true }// This module will be preloaded 
    },
    {
        path: 'admin',
        loadChildren: () => import('./admin/admin.routes')
            .then(m => m.ADMIN_ROUTES),
        Data: { preload: false }// This module will NOT be preloaded 
    }
];

Network-Aware Preload Strategy

network-aware-preload-strategy.ts

import { Injectable } from '@angualr/core';
import { Route, PreloadingStrategy } from '@angualr/router';
import { of, Observable } from 'rxjs';

@Injectable({
    provideIn: 'root'
})
export class NetworkAwarePreloadStrategy implements PreloadingStrategy{
    preload(route: Route, load: ()=> Observable<any>): Observable<any>{
        // Check if user is on a slow connection
        const connection = (navigator as any).connection;

        if(connection){
            // Don't preload on slow connections or save-data mode

            if(connection.saveData || connection.effectiveType ==='slow-2g' ||
                connection.effectiveType === '2g'
            ){
                return of(null);
            }
        }

        // Preload if data flag is set
        if(route.data && route.data['preload']){
            return laod();
        }

        return of(null);
    }
}

InterView Questions

Basic Lavel (1-2 years exp)

Q1. What is Angular routing and why do we need it?

Answer: Angular Router is a navigation library that enables navigation between views/components bassed on URL changes. We needit because:

  • Enables Single-page application (SPA) navigation
  • Maps URLs to components
  • Maintains browser history
  • Supports deep linking
  • Provides programitic navigation
  • Hadles route parameter and query string

Answer:

  • routerLink: Angular directive that prevents full page reload, updates URL via History API, maintains application state, and is SPA-frindely -href: Standard HTML attribute that causes full page reload, loses application state, and makes server request
<!-- Good for SPA -->
 <a routerLink="/about">About</a>
 <!-- Causes page reload -->
   <a href="/about">About</a>

Q3. What is <router-outlet> and what it its purpose?

Angular: <router-outlet> is a directive that acts as a placholder where the Angular Router inserts the component for the current route. It makes the spot in the tamplate where the routed component should be displayed.

<nav>...</nav>
<router-outlet></router-outlet> <!--Component load here-->
<footer>...</footer>

Q4. How do you navigate programmatically in Angualr?

Answer:

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

constructor(private router: Router){}

// Simple navigation
this.router.navigate(['/products']);

// with paramenter
this.router.navigate(['/products', productId]);

// with query params
this.router.navigate(['/products'], {
    queryParams: {category: 'electronics'}
});

// Using navigationByUrl
this,router.navigateByUrl('/about');

Q5: What is difference between navigate() and navigateByUrl()?

Answer:

  • navigate() - Takes an array of URL segments, supports relative navigation, and can use route parameters
  • navigateByUrl() - Takes a complate URL string, always absolute navigation, trates Url as a single string
// navigate - array for segments
this.router.navigate(['/user', userId, 'profile']);

// navigationByUrl - a complete URL string
this,router.navigateByUrl('/user/123/profile');

Q6: What are route paramenters and how do you access them?

Answer: Route paramerer are dynamic sengment in URL path

// Define route
{path: 'user\:id', component: UserComponent}

// Access in component
export class UserComponent implements Oninit {
    constructor(private router: ActivatedRoute){}

    ngOninit(){
        //snapshot (one-time read)
        const id = this.route.snapshot.paramMap.get('id');

        // Observable (reactive)
        this.router.paramMap.subscribe(params => {
            const id = params.get('id');
        });
    }
}

Q7. What is differece between route parameters query paramaters?

Answer: -Route Paramenters: Part of the route path, required for route matching, used for essential data - Example: /user/123 where 123 is a route parameter -Query Parameter: Optional, appear after ?, used for filters/sorting/pagination - Example: /products?category=electronics&short=price

Q8. What is routerLinkActive directive?

Answer: routerLinkActive adds CSS classes to an element when its associated routerLink is active.

<a routerLink="/home" routerLinkActive="active">Home </a>
<a routerLink="/about" routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}">About </a>
.active{
    font-weight: bold;
    color: blue;
}

Intermediate Level (2-3 Years Expericnce)

Q9: Explain child route in Angula. How do you implement them?

Anwer: Child routes create nested routing structures for component hierarchies.

const routes: Routes = [
    {
        path: 'admin',
        component: AdminComponent,
        children: [
            {path: 'dashboard', component: DashboardComponent },
            {path: 'users', component: UsersComponent }
        ]
    }
];

Parent component needs a <router-outlet> for child components. URLs become /admin/dashboard and /admin/users.

Q10: What is lazy loading in Angular routing? What are its benefits?

Angular: Lazy loading loads features modules on-demand rather than at application startup.

Benefits:

  • Reduced initial bundle size
  • Faster initial load time
  • Better performance
  • Load features only when needed
{
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes')
        .then(m => m.ADMIN_ROUTES)
}

Q11: What are the Route Guard? List all types of route guards.

Answer:

  1. CanActivate - Can route be activated?
  2. CanActivateChild - Can child routes be activated?
  3. CanDeactivate -- Can user leave the route?
  4. CanMatch - Can route be matched?
  5. Resolve - Pre-fatch data before activation

Q12: Explain CanActivate guard with an example.

Answer: CanActivate determines if a route can be activated.

import { inject } from '@angular\core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivationFn = (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    if(AuthService.isAuthenticated()){
        return true;
    }
    // Navigate to login page
    router.navigate(['/login']);
    return false
};

//Usage 
{
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [authGuard]
}

Q13. what is CanDeactivate guard and when would you use it?

Answer: CanDeactivate prevents navigation away from a route, typically used to warn users about unsaved changes.

export const unsavedChangesGuard: CanDeactivateFn<FromComonent> =
(component) => {
    if(component.hasUnsavedChanges()){
        return confirm('You have unsaved changes. Leave anyway');
    }
    return true;
};

Use cases:

  • Form with unsaved changes
  • Ongoing file upload
  • Incomplate wizard steps

Q14: What is difference between CanActivate and CanActivateChild?

Answer:

  • CanActivate: Guard the route itself
  • CanActivateChild: Guard all child routes of a parent route
{
    path: 'admin',
    component: AdminComponent,
    canActivate: [authGuard], //Guards /admin
    canActivateChild: [adminGuard], //Guards /admin/users, /admin/settings
    children: [
        {path: 'users', component: UsersComponent },
        {path: 'settings', component: settingsComponent }
    ]
}

Q15. How do you pass data to a route?

Answer: Multiple ways:

1. Route Parameters:

this.router.navigate(['/user', 123]);

2. Query Paramenters:

this.router.navigate(['/products', {
    queryParams: {category: 'electronics'}
}]);

3. static data:

{
    path: 'about',
    component: AboutComponent,
    data: {title: 'About Us', breadcrumb: 'About'}
}

// Access
this.router.data.subscribe(data=> {
    console.log(data['title']);
});

4. State:

this.router.nivigate(['/result'], {
    state: {searchResults: results}
});

// Access
const state = history.state;
console.log(state.searchResults);

Q16. What is preloading strategies? Name the built-in strategies.

Answer: Preloading strategies determine when lazy-loaded modules are loaded.

Built-in strategies:

  1. NoPreloading (default) - don't preload any modules
  2. PreloadAllMudeles - Preload all lazy modules after initial load
proviedRouter(
    routes,
    withPreloading(PreloadAllMudeles)
)

Q17: How do you create a custom preloading strategy?

Answer:

import { Injectavble } from '@angular/core';
import { PreloadingStrategy, Route} from '@angular/router';
import {Observable, of} from 'rxjs';

@Injectable({ providedIn: 'root'})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
    preload(route: Route, load: ()=> Observable<any>): Observable<any> {
        if(route.data && route.data['preload']) {
            console.log('Preloading: ' + route.path);
            return load();
        }
        return of(null);
    }
}

// route configuration
{
    path: 'products',
    loadChilderen: () => import('./products/products.routes'),
    data: {preload: true}
}

// App config
provideRouter(routes, withPreloading(SelectivePreloadingStrategy))

Q18. What is the wildcard route and where should it be placed?

Answer: Wildcard route (**) matches any URL not matched by previous routes. Must be placed last in route configuration.

const routes: Routes =[
    {path: '', component: HomeComponent},
    {path: 'about', component: aboutComponent},
    {path: '**', component: PageNotFoundComponent} // Must be in last
];

Q19. How do you handle query parameter in Angular?

Answer:

//set query paramenter
this.router.navigate(['/products'], {
    queryParams: {category: 'electronics', sort: 'price'},
    queryParamsHandeling: 'merge' // merge, preserve, or empty
});

//Read query params
this.route.queryParaMap.subscribe(params => {
    const category = params.get('category');
    const sort = params.get('sort');
})

// Snapshot (one-time read)
const category = this.route.snapshot.queryParamsMap.get('category');

Q20. What is difference between paramMap and params in ActivatedRoute?

Answer:

  • paramMap: Return ParamMap object with helper methods (get()getAll()has(),keys)
  • params: Return plain JavaScript object
//paramMap (recommended)
this.route.paramMap.subscribe(params => {
    const id = params.get('id'); // type-safe
    const hasId = params.has('id');
});

//params (lagacy)
this.route.params.subscribe(params =>{
    const id = params['id']; // direct access
});

Advance Level (3+ year Exp)

Q21: How would you implement role-based authentication with route guards?

Answer:

export const roleGuard: canActivateFn = (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);

    const requiredRoles = route.data['roles'] as string[];
    const userRole = authService.getUserRole();

    if(!authService.isAuthentcated()){
        router.navigate(['/login']);
        return false;
    }

    if(requiredRole.includes(userRole)){
        return true;
    }

    router.navigate(['/unauthorized']);
    return false;
};

// Route configuration
{
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard, roleGuard],
    data: {roles: ['admin', 'superAdmin']}
}

Q22: How do you handle authentication tokens and refresh tokens with route guards?

Answer:

export const authGuard: CanActivatedFn = async (route, state) => {
    const authService = inject(AuthService);
    const router = inject(Router);
    
    if(!authService.hasValidToken()) {
        try{
            // Try to refresh token
            await authService.refreshToken().toPromise();
            return true;
        } catch (error) {
            // refresh failled. redirect to login
            authService.logOut();
            router.navigate(['/login'], {
                queryParams: {returnUrl: state.url }
            });
            return false;
        }
    }
};

Q23: What is resolve guard and how is it different from other guards?

Answer: Reslove guard pre-fetches data bedore route activation, ensuring data is available when component initializes.

import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { USerService } from './user.service';

export const userResolver: ResolveFn<User> = (route, sate) => {
    const userService = inject(USerService);
    const userId = route.paramMap.get('id');
    return userService.getUser(userId);
};

// Route configuration
{
    path: 'user/:id',
    component: UserComponent,
    resolve: {user: userResolver }
}

//Component
ngOninit(){
    this.route.data.subscribe(data => {
        this.user = data['user']; // Data already loaded
    });
}

Difference from other guards:

  • Other guards return boolean (allow/ deny)
  • Resolve returns data (Observable/Promise)
  • Delays navigation until data is not loaded

Q24. How do you implements multiple guards on a single route? What is a execution order?

Answer:

{
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [authGuard, roleGuard, subscriptionGuard]
}

Execution order

  1. Guards execute in the order they're defined (left to right)
  2. If any guard return false, subsequect guard don't execute
  3. All guard must reutrn true for navigation to proceed

Q25. How would network -aware preloading strategy?

Answer:

export class NetworkAwarePreloadStrategy implements PreloadingStrategy {
    preload(route: Route, load() => Observable<any>): Observable<any> {
        const connection = (navigator as any).connection;

        if(connection){
            //Don't preload on slow connection
            if(connection.saveData || connection.effectiveType==='slow-2g' ||connection.effectiveType==='2g' ) {
                return of(null);
            }
        }

        // Check battery status
        if((navigator as any).getBattery) {
            return from((navigator as any).getBattery()).pipe(
                margeMap((battery: any) =>{
                    // Don't preload if battery is low or not charging
                    if(battery.level<0.2 && !battery.charging){
                        return of(null);
                    }
                    return route.data?.['preload']? load() : of(null);
                })
            );
        }
        return route.data?.['preload'] ? load(): of(null);
    }
}

Q26: How do you test route guards?

Answer:

import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { authGuard } from './auth.guard';
impoort { AuthService } from './auth.service';

describe('authGuard', () =>{
    let authService: jasmine.SpyObj<AuthService>;
    let router: jasmine.SpyObj<Router>;

    beforeEach(() => {
        const authSpy  = jasmine.createSpyObj('AuthService', ['isAuthenticated']);
        const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

        TestBed.configureTestingModule({
            providers: [
                { provide: AuthService, useValue: authSpy },
                {provide: Router, useValue: routerSpy}
            ]
        });
        authService = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
        router = TestBed.inject(router) as jasmine.SpyObj<Router>;
    });
    it('sould allow activation when Authenticated', () => {
        authServise.isAuthenticated.and.returnValue(true);

        const result = authGuard(
            {} as any,
            {url: '/dashboard'} as any
        );

        expect(result).toBe(true);
    })
    it('should redirect to login when not authenticated', () => {
        authServise.isAuthenticated.and.returnValue(false);

         const result = authGuard(
            {} as any,
            {url: '/dashboard'} as any
        );

        expect(result).toBe(false);
        expect(router.navigatge).toHaveBeenCalledWith(['/login']);
    });
});

Q27: What is difference between snapshot and Observable approch for route parameters?

Answer:

Snapshot:

  • One-time read
  • Use when component is destroyed and recreated on nevigation
  • Simpler, Synchronous access
const id = this.route.snapshot.paramMap.get('id');

Observalbe:

  • Reactive updates
  • Use when navigation to same component with different parameters
  • Required when route reuse stratgy keeps component alive
this.route.paramMap.subscribe(params => {
    const id = params.get('id');
    this.loadData(id);
});

Example scenario:

//Navigation from /user/1 to /user/2
//Component is reused, snapshot won't update
// Observable will emit new value

Q28: How do you implement auxiliary routes (named outlets)?

Answer: Auxiliary routes allow multiple independent router outlets in the same component.

<!-- app.component.html -->
 <router-outlet></router-outlet>  <!-- Primary outlet -->
 <router-outlet name="sidebar"></router-outlet>   <!-- Named outlet -->
 <router-outlet name="popup"></router-outlet>
// Route configuration
{
    path: 'home',
    component: HomeComponent
},
{
    path: 'chat',
    component: ChatComponent,
    outlet: 'sidebar'
},
{
    path: 'alert'
    component: alertComponent,
    outlet: 'popup'
}

// Navigation
this.router.navigate([
    {Outlets: {primary: 'home', sidebar: 'chat', popup: 'alert '} }
]);

// URL: /home (sidebar: chat//popup:alert)

// Close named outlet
this.router.navigate([{ outlets: {popup: null}}]);

Q29: How would you implement a breadcrumb navigation system?

Answer:

// Route configuration with breadcrumb data

{
    path: 'products',
    component: ProductComponent,
    data: {breadcrumb: 'Products'},
    children: [
        {
            path: ':id',
            component: ProductDetailComponent,
            data: {breadcrumb: 'Product Detail'}
        }
    ]
}

// Breadcrumb component
@Component({
    selector: 'app-breadcrumb',
    template: `
    <nav>
        <a *ngFor="let breadcrumb of breadcrumbs"
        [routerLink]="breadcrumb.url">
        {{breadcrumb.label}}
        </a>
    </nav>
    `
})
export class BreadcrumbComponent implements onInit {
    breadcrumbs: Breadcrumb[] = [];

    constructor(
        private router: Router,
        private activatedRouter: ActivatedRoute
    ){}

    ngOnInit(){
        this.router.events.pipe(
            filter(event => event instanceof NavigationEnd)
        ).subscribe(() => {
            this.breadcrumbs = this.createBreadcrumbs(this.activatedRoute.root);
        });
    }

    private createBreadcrumbs(
        route: activatedRoute,
        url: string = '',
        breadcrumbs: Breadcrumb[] = []
    ): Breadcrumb[] {
        const children: activatedRoute[] = route.children;

        if(children.length===0){
            return breadcrumbs;
        }

        for(const child of children){
            const routeURL: string = child.snapshot.url
                .map(segment => segment.path)
                .join('/');
            
            if(routeURL !==''){
                url += `/${routeURL}`;
            }

            const label = child.snapshot.data['breadcrumb'];
            if(label){
                breadcrumbs.push({label, url});
            }

            return this.createBreadcrumbs(child, url, breadcrumbs);
        }
        return breadcrumbs;
    }
}

Q30. How do you handle route events and what are the different type?

Angular: Angular Router emits various events during navigation lifecycle.

import {
    Router,
    NavigationStart,
    NavigationEnd,
    NavigationCancel,
    NavigationError
} from '@angular/router';

export class AppComponent {
    loading = false;

    constructor (private router: Router){
        this.router.events.subscribe(event => {
            if(event instanceof NavigatinStart) {
                this.loading = true;
                console.log('Navigation statted:', event.url);
            }
            if(event instanceof NavigationEnd){
                this.loading = false;
                console.log('Navigation Ended: ', event.url);
                // Google Analytics tracking
                this.trackPageView(event.url);
            }

            if(event instanceof NavigationCancel){
                this.loading = false;
                console.log('Navigation cancelled: ', event.url);
            }
            if(event instanceof NavigationError){
                this.loading = false;
                console.log('Navigation Error: ', event.error);
            }
        });
    }

         trackPageView(url: string){
            // Analytics tracking logic
         };
}

Router Events:

  1. NavigationStart - Navigation Begins
  2. RoutesRecognized - Routes parsed
  3. RouteConfigLoadStart - Before Lazy loading
  4. RouteConfigLoadEnd - After lazy loading
  5. NavigationEnd - Navigation successful
  6. NavigationCancel - Navigation Cancel (gurad returns false)
  7. NavigationError - Navigation Error
  8. Scroll - Scroll position change

Additional Interview Tips

Q31: What are route reuse strategies?

Answer: RouterReuseStrategy cntrols how Angular reuse route components.

import { RouterReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';

export class CustomReuseStrategy implements RouterReuseStrategy {
    private handlers: { [key: string]: DetachedRouteHandle } = {};

    shouldDetach( route: ActivatedRouteSnapshot): boolen {
        return route.data['shouldReuse'] || false;
    }

    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        if(route.data['shouldReuse']) {
            this.handlers[route.routeConfig!.path!];
        }
    }
    shouldAttach(route: ActivatedRouteSnapshot): boolean {
        return !!thi.handlers[route.routeConfig!.path!];
    }
    retrieve(route: ActivatedRouteSnapshot):DetachedRouteHandle {
        return this.handlers[route.routerConfig!.path!];
    }
    shouldReuseRoute(future:ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot) :boolean {
        return future.routeConfi === curr.routeConfig;
    }
}

// Provide in app.config.ts
providers: [
    {provide:RouterReuseStrategy, useClass: CustomReuseStrategy}
]

Q32: How do you implement location strategy vs path location strategy?

Answer:

import { ApplicationConfig } from '@angular/core';
import { provideRouter, withHashLocation } from '@angular/core';

// Hash Location Strategy (URLs with #)
// Example: http://example.com/#/products
export const appConfig: ApplicationConfig ={ 
    providers: [
        provideRouter(routes, withHasLocation())
    ]
};

// path loacation Strategy (default - HTML5 pushState)
// Example: http://example.com/products

export const appConfig: ApplicationConfig ={ 
    providers: [
        provideRouter(routes) // Default is PAthLocationStrategy
    ]
};

When to use Hash Location

  • Server does not support URL rewriting
  • Static file hosting (Guithub Pages)
  • Legacy browser support

When to use Path Location

  • Modern application
  • SEO importent
  • Server support URL rewriting

Q33: What is the CanMatch Guard and How it is different from CanActivate?

Answer: CanMatch (Angualr 14+) determines if a route can be matched before attempting to load it.

export const featureGuard: CanMatchFn = (route, segments) => {
    const featureService = inject(FeatureService);
    return featureService.isFeatureEnabled('product');
}

//Route configuration
{
    path: 'products',
    loadChildren: () => import('./products/product.routes'),
    canMatch: [featureGuard]
}

Differences:

  • CanMatch : Evaluated before route matching, prevents lazy loading, can have fallback routes
  • CanActivate: Evaluated after route matching, module already loaded, can't have fallback

Use case: Feature flags, A/B testing, conditional routing


Best Practices

  1. Use functional guards (Angular 15+) instead of class-based guards
  2. Prefer lazy loading for feature modules to improve performance
  3. Use router resolvers to pre-fatch data and avoid loading spinners in components
  4. Implement custom preloading strategies based on user behaviour or network conditions
  5. Use route data for configuration instead of heardcoding in components
  6. Always handle errors in guard and resolver
  7. Implement peoper 404 handling with wildcard routes
  8. Use relative navigation where appropriate for maintainability
  9. Use routerLinkActive for navigation highlight instead of manual traking
  10. Subscribe to route event for loading indicators and analytics

Common Pitfalls

  1. Not unsubscribing from route observables (though ActivatedRoute observables are automatically cleaned up)
  2. Placing wildcard route before any other route
  3. Not handeling async guard properly (returning Observable/Promise)
  4. Lazy loading without guard (security risk)
  5. Not considering route reuse strategy for complex applications
  6. Hardcoding URLs instead of using router methods
  7. Not testing route guards throughly
  8. Forgetting to provide router in testes
  9. Using snapshot when observable is needed (when component is reused)
  10. Not handeling query parameter changes reactively

Performance Tips

  1. USe lazy loading for large frature modules
  2. Implement custom preloading strategies
  3. Use OnPush change dection in routed components
  4. Minimize guard logic complexity
  5. Cache resolved data when appropriate
  6. Use trackBy in lists within routed components
  7. Implement route reuse strategy for expensive componets
  8. Avoid unnecessary redirects
  9. Use auxiliary routes for independent sections
  10. Monitor bundle sizes after lazy loading configuration

Comments

Popular posts from this blog