kdocs
GitHub
Lang - Web
Lang - Web
  • Base
    • Css
    • Javascript
    • Typescript
      • New Project
  • Frameworks
    • Angular
      • Directives
      • Components
      • Templates
        • Bindings
        • Control Flow
        • Variables
      • Signals
      • Pipes
      • Services
        • Dependency Injection
      • Forms
        • Reactive Form
        • Template-Driven Form
      • Router
      • HTTP Client
      • Observables RxJS
      • Testing
        • Components
        • Directives
        • Pipes
        • Services
      • Optimization & Performance
      • Security
Powered by GitBook
On this page
  • About
  • <router-outlet>
  • routerLink
  • routerLinkActive
  • Programatically navigate router.navigate()
  • Nested Routes
  • Wildcard Routes
  • Redirects
  • Setting the Page Title
  • Passing static and dynamic Data to a Route
  • Fetching Url data - with input()
  • Fetching Url data - with ActivatedRoute service
  • Route Guards
  • Generating Guard (CLI)
  • canMatch
  • canActivate
  • canActivateChild
  • canDeactivate
  1. Frameworks
  2. Angular

Router

PreviousTemplate-Driven FormNextHTTP Client

Last updated 5 months ago

About

Servers which will host the deploy will need to, on 404 error, redirect to index.php to garantee that Angular SPA will take over the routing.

Tipically the routes will be inside a file app.routes.ts.

Routes order is important since Router uses a first-match wins strategy.

  • More specific routes should be placed above less specific (more generic) routes.

  • Place wildcard ** routes last.

Url changes on a Component will re-use the Component instance.

This means that Signals or Observables are needed to catch changes on the URL like /user/1 -> /user/2.

app.routes.ts
const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'employees', component: EmployeesComponent },
    { path: 'employees/:id', component: EmployeeComponent },
    ...
];

<router-outlet>

The router Directive to indicate the place where the Angular router will load the Component of the currently selected route.

routerLink

Do not used the <a href="" /> to load routes, because these routes will be loaded but the page will also be reloaded, thus states will be lost.

<!-- Full Path -->
<a routerLink="/employee"></a>
<!-- Relative Path -->
<a routerLink="employee"></a>
<!-- Can be used with Arrays to declare multiple hierarchical paths like '/employee/new' -->
<a [routerLink]="['/employee']"></a>
<a [routerLink]="['/employee', 'new']"></a>

It can be used with Absolute paths or Relative paths.

  • Absolute paths always starts with / like /home.

  • Relative paths starts without the /, and always append the passed url to the current url.

    • You can use ../, like in directories, to remove go back to upper Url paths.

    • You can use ./, to stay in the current path.

Passing queryParams to the routerLink

<!-- employee?order=asc -->
<a routerLink="/employee" [queryParams]="{ order: 'asc', ... }"></a>

Passing fragments to the routerLink

<!-- employee#loading -->
<a routerLink="/employee" [fragment]="'loading'"></a>

routerLinkActive

Used to attach a css class to elements, for instance, when selecting the select tabs.

Without exact: true, it will check as contained substrings. So / will always be true.

<li routerLinkActive="<cssclass>" [routerLinkActiveOptions]="{exact: true}"></li>

Programatically navigate router.navigate()

  • navigate() method doesn't know your currently route. (So relative paths don't add up)

    • If the Url passed is static, it can be the literal Url string.

    • For dynamic path, must use an Array of path segments.

      • These segments are added to the current Url.

      • Or to the one provided in relativeTo config property.

  • Some additional configs that you can pass to navigate() include:

    • replaceUrl: To avoid that the browser can go back to that route if you hit the back button.

    • relativeTo: Used for dynamic paths, the specified relativeTo will be the main Url where the paths will be added to.

    • queryParamsHandling: How navigate handles queryParams and fragments values after a navigation.

@Component({ ... })
export class Component {
    constructor(private router: Router, private route: ActivatedRoute) {}

    goto() {
        // By default it is always relativeTo root '/'
        this.router.navigate(['/employee', ...]);

        // To tell navigate the current path
        this.router.navigate(['/employee', ...], { relativeTo: this.route });
    }
}

Passing queryParams

// employess/5/edit?allowEdit=1
this.router.navigate(["/employee", 5, "edit"], { queryParams: { allowEdit: "1" } });

Passing fragments

// employess/5/edit#loading
this.router.navigate(["/employee", 5, "edit"], { fragment: "loading" });

Preserving queryParams and fragments - after Navigate with queryParamsHandling

  • merge: Will merge new params with existing queryParams.

  • preserve: Will just preserve the existent queryParams.

this.router.navigate(["edit"], { relativeTo: this.route, queryParamsHandling: "merge" | "preserve" });

Nested Routes

Each child route will need a new <router-outlet> to render it's paths Components.

  • For instance, in the EmployeesComponent you will need to have a <router-outlet> which will be the place it's child routes will be loaded.

app.routes.ts
const appRoutes: Routes = [
    { path: "", component: HomeComponent },
    {
        path: "employees",
        component: EmployeesComponent,
        children: [
            // employees/:id
            { path: ":id", component: EmployeeComponent },
            // employees/:id/edit
            { path: ":id/edit", component: EditEmployeeComponent },
        ],
    },
];

Now Each parent component will need to have a <router-outlet>, which is the place it will load it's children Components.

Nested routes to it's own file

You can grab the children routes and put them in a different .routes.ts file for more leaner structure.

employee.routes.ts
export const employeesRoutes: Routes = [
    // employees/:id
    { path: ":id", component: EmployeeComponent },
    // employees/:id/edit
    { path: ":id/edit", component: EditEmployeeComponent },
];
app.routes.ts
const appRoutes: Routes = [
    { path: "", component: HomeComponent },
    {
        path: "employees",
        component: EmployeesComponent,
        children: employeesRoutes,
    },
];

Wildcard Routes

To handle routes that shouldn't exist.

  • ** is the wildcard and will catch everything that is not currently specified on the routes.

  • The order that you use it is VERY IMPORTANT, always use them as the last route.

const appRoutes: Routes = [
    { path: "not-found", component: NotFoundComponent },
    { path: "**", redirectTo: "/not-found" },
];

// Or

const appRoutes: Routes = [{ path: "**", component: NotFoundComponent }];

To be able to redirect with urls that may always trigger like path: '', you can use the option pathMatch: 'full'

{ path: '', redirectTo: '/somewhere-else', pathMatch: 'full' }

You can use more complex logic in redirect, returning a string or UrlTree.

{
    path: '',
    redirectTo: ({ queryParams }) => {
        const errorHandler = inject(ErrorHandler);
        const userIdParam = queryParams['userId'];
        if (userIdParam !== undefined) {
            return `/user/${userIdParam}`;
        } else {
            errorHandler.handleError(new Error('Attempted navigation to user page without user ID.'));
            return `/not-found`;
        }
    },
}

pathMatch option

  • prefix: Will look at the route's path and combine with parent paths if available, and then checks if the Url in the browser STARTS with this path.

  • full: Will look at the route's path and combine with parent paths if available, and then checks if the FULL Url in the browser is equal to this path.

Setting the Page Title

Each page in your application should have a unique title so that they can be identified in the browser history.

The Router sets the document's title using the title property.

You can also provide custom title strategies:

  • By providing ResolverFn functions.

  • Or by extending the TitleStrategy, check the docs.

static titles

const appRoutes: Routes = [
    { path: "", component: HomeComponent, title: "Home" },
    { path: "employees", component: EmployeesComponent, title: "Employees" },
];

dynamic title

You can provide dynamic title values by using resolver functions, just like in dynamic data.

// Each `resolveTitle` functions comes from it's Component file
const appRoutes: Routes = [
    { path: "", component: HomeComponent, title: resolveHomeTitle },
    { path: "employees", component: EmployeesComponent, title: resolveEmployeesTitle },
];

Passing static and dynamic Data to a Route

You can use both methods at the same time.

static data

You can pass static data to routes by providing them in the data property.

const appRoutes: Routes = [{ path: "", component: HomeComponent, data: { message: "Hello" } }];

Access with Signals

You can then access the data inside the Component with input(), by having a variable with the same name as the data property.

export class HomeComponent {
    message = input.required<string>();
}

Access with ActivatedRoute - with data (Observable)

data Observable will provide values from static and dynamic.

export class HomeComponent implements OnInit {
    message: string;
    private activatedRouteDataSubscription;
    private activatedRoute = inject(ActivatedRoute);

    ngOnInit() {
        this.activatedRouteDataSubscription = this.activatedRoute.data.subscribe((values) => {
            this.message = values.message;
        });
    }

    ngOnDestroy() {
        this.activatedRouteDataSubscription.unsubscribe();
    }
}

dynamic data - with ResolveFn functions

You can pass dynamic data to routes with resolve property of routes.

The resolve accepts an object just like the static one, but instead of static values you will provide valid Angular resolvers, which are functions.

The resolver will be called for every navigation action on the specific route.

  • BUT by default will NOT be re-executed when queryParams changes.

  • To allow re-execution set the runGuardsAndResolvers to always or paramsOrQueryParamsChange.

const appRoutes: Routes = [
    {
        path: "",
        component: HomeComponent,
        runGuardsAndResolvers: "always",
        resolve: { message: resolveMessage },
    },
];
export class HomeComponent {
    // Access the dynamic data with Signals
    message = input.required<string>();
    ...
}

// Can use the snapshot since it is executed every time the Route is active
export const resolveMessage: ResolveFn<string> = (activatedRoute: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) => {
    // You may `inject` services in here
    // ...
    return 'Hello: ' + data; // data is of type string
}

dynamic data - with Resolve Classes as Services (Avoid)

This is an older way, prefer the use with Functions. (Above)

You can pass dynamic data to routes with services implementing Resolve class.

@Injectable({
    providedIn: "root",
})
export class MessageResolver implements Resolve<string> {
    resolve(activatedRoute: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) {
        return "Hello: " + data; // data is of type string
    }
}
const appRoutes: Routes = [
    {
        path: "",
        component: HomeComponent,
        resolve: { message: MessageResolver },
    },
];
export class HomeComponent {
    message = input.required<string>();
    ...
}

Fetching Url data - with input()

Changes in the URL will use still use the same Component's instance.

This means that ngOnInit will NOT execute again, and changes in :params for instance will not be picked.

Retrieving data with Signals will garantee to get the new values.

When working Signals you must declare in the provideRouter the argument withComponentInputBinding.

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

Access Parent Route Data

With input() the Component can always access it's own url data, BUT the Component cannot access by default it's parent route url data.

Provide an additional configuration withRouterConfig(), with paramsInheritanceStrategy property.

Just consider still using UNIQUE names to avoid clashing of param names in the routes.

export const appConfig = {
    providers: [
        provideRouter(
            routes,
            withComponentInputBinding(),
            withRouterConfig({
                paramsInheritanceStrategy: "always",
            })
        ),
    ],
};

Retrieving param /:userId

You can retrieve url parameters by having a variable with the exact same name as declared in the Route.

The expected type will always be string since the url is a string.

@Component({ ... })
export class UserComponent {
    // user/:userId
    // You may use `required` since Url param will most likely be required in the URL
    userId = input.required<string>();
}

Retrieving multiple params /:userId/:name

@Component({ ... })
export class UserComponent {
    // user/:userId
    userId = input.required<string>();
    name = input.required<string>();
}

Retriving Url queryParams ?allowEdit=value

Just like a /:param, for queryParams just create a variable with the same name as the queryParam.

@Component({ ... })
export class UserComponent {
    // user?order=value
    // For queryParams don't use `required` since usually are opcional values
    order = input<'asc' | 'desc'>();
}

Retrieving Url fragments #loading

Not sure how to retrieve with Signals, but it could be just setting the variable input() with the same name as the #fragment name.

Fetching Url data - with ActivatedRoute service

With Observables a Component can by default access it's own route params AND parent route params.

Changes in the URL will use still use the same Component's instance.

This means that ngOnInit will NOT execute again, and changes in :params for instance will not be picked.

Retrieving data with Observables will garantee to get the new values.

Retriving data with snapshot will NOT get new values.

Fetching data the Reactive way - with (Observable)

Retrieving Url params /:userId - with paramMap

You can access url params by subscribing to paramMap property of ActivatedRoute.

@Component({ ... })
export class UserComponent implements OnInint, OnDestroy {
    userId: string;
    private activatedRouteParamsSubscription;

    constructor(private activatedRouteService: ActivatedRoute) {}
    // or
    readonly activatedRouteService = inject(ActivatedRoute);

    ngOnInit() {
        this.activatedRouteParamsSubscription = this.activatedRouteService.paramMap.subscribe({
            next: (paramMap) => {
                this.userId = paramMap.userId;
            }
        });
    }

    ngOnDestroy() {
        this.activatedRouteParamsSubscription.unsubscribe();
    }
}

Retrieving Url multiple params /:userId/:name - with paramMap

this.activatedRouteParamsSubscription = this.activatedRouteService.paramMap.subscribe({
    next: (paramMap) => {
        this.userId = paramMap.userId;
        this.name = paramMap.name;
    },
});

Retrieving Url queryParams ?allowEdit=value - with queryParams

You can access url queryParams by subscribing to queryParams property of ActivatedRoute.

this.activatedRouteQueryParamsSubscription = this.activatedRouteService.queryParams.subscribe({
    next: (params) => {
        this.order = params.order;
    },
});

Retrieving Url fragments #loading - with fragment

You can access url fragment by subscribing to fragment property of ActivatedRoute.

this.activatedRouteFragmentSubscription = this.activatedRouteService.fragment.subscribe({
    next: (fragment) => {
        this.loading = fragment;
    },
});

Fetching data NOT Reactive - with (.snapshot)

Values from the snapshot are not Observables, they are actual values to be accessed.

The snapshot values are NOT reactive to the Url changes. (The values displayed will be the values of the FIRST run on the Component)

Retrieving Url param /:userId - with snapshot.paramMap or snapshot.params

@Component({ ... })
export class UserComponent implements OnInint, OnDestroy {
    userId: string;

    constructor(private activatedRouteService: ActivatedRoute) {}
    // Or
    readonly activatedRouteService = inject(ActivatedRoute);

    ngOnInit() {
        this.userId =  this.activatedRouteService.snapshot.paramMap.userId;
    }
}

Retrieving Url queryParams ?allowEdit=value - with snapshot.queryParamMap or snapshot.queryParams

@Component({ ... })
export class UserComponent implements OnInint {
    allowEdit: string;

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {
        this.allowEdit = this.route.snapshot.queryParams['allowEdit'];
    }
}

Retrieving Url fragments #loading - with snapshot.frament

Only one fragment may be passed.

@Component({ ... })
export class UserComponent implements OnInint {
    loading: string;

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {
        this.loading = this.route.snapshot.loading;
    }
}

Route Guards

ng g guard [guard-name] [options]

It is code that can be executed before a route is loaded or when leaving a route, that checks whether a certain navigation action should be permitted or not.

Typically guards will have their own files <guard-name>.guard.ts.

Useful for preventing unauthorized access.

Add Guards to routes by using the routes properties that start with can.

By default a Guard will guard the Route where it was declared, and it's child routes.

Guards can be defined by:

  • Guard functions.

  • Guard Services that implement certain interfaces (This is the old way)

const appRoutes: Routes = [
    {
        path: "",
        component: HomeComponent,
        // They receive an array of Guards (Functions or Classes)
        canMatch: [],
        canActivate: [],
        canActivateChild: [],
        canDeactivate: [],
    },
];

canMatch

Allows you to control whether the entire route should be matched by a certain navigation action or not.

For instance, if some path that has been entered into the Url should match this route or not.

with Functions

/**
 * route: Current route that is trying to be matched.
 * segments: An array of Url path segments.
 */
export const authGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => {
    // Return `true` to grant access to the route, `false` to deny it
    return true;

    // Or you may return a UrlTree to redirect the navigation
    return router.parseUrl("/home");

    // Or you can redirect like this
    return new RedirectCommand(router.parseUrl("/home"));
};

with Classes

export class AuthGuard implements CanMatch {
    canMatch(route: Route, segments: UrlSegment[]) {
        return true;
    }
}

canActivate

Comes one step after canMatch but before the component has been loaded.

It will be checked by Angular once a route has been identified as matching for the currently active path.

export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
    // Return `true` to grant access to the route, `false` to deny it
    return true;

    // Or you may return a UrlTree to redirect the navigation
    return router.parseUrl("/home");

    // Or you can redirect like this
    return new RedirectCommand(router.parseUrl("/home"));
};

canActivateChild

  • Can be used if you want to activate the route, the Component, but not necessarily the child components of that route.

export const authGuard: CanActivateChildFn = (childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
    // Return `true` to grant access to the route, `false` to deny it
    return true;

    // Or you may return a UrlTree to redirect the navigation
    return router.parseUrl("/home");

    // Or you can redirect like this
    return new RedirectCommand(router.parseUrl("/home"));
};

canDeactivate

To control whether a user is allowed to leave a page or not.

export class HomeComponent {
    unsavedData = signal<boolean>(false);
}
// Specify `HomeComponent` as the implicit type of `CanDeactivateFn` so that you receive it in the `component` parameter
export const canLeavePage: CanDeactivateFn<HomeComponent> = (component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState: RouterStateSnapshot) => {
    // Return `true` to let navigate out of the component, or `false` to deny it
    if (component.unsavedData() === false) return false;
    return true;

    // Making a confirmation dialog
    if (component.unsavedData() === false) {
        return window.confirm("Do you really want to leave?");
    }
    return true;

    // You may also return a UrlTree to redirect the navigation
    return router.parseUrl("/home");
};

Redirects
Generating Guard (CLI)
AngularAngular
routing/common-router-tasks
AngularAngular
routing/activated-route
Logo
Logo