Router
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
.
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
.
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'employees', component: EmployeesComponent },
{ path: 'employees/:id', component: EmployeeComponent },
...
];
<router-outlet>
<router-outlet>
The router Directive to indicate the place where the Angular router will load the Component of the currently selected route.
routerLink
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
queryParams
to the routerLink
<!-- employee?order=asc -->
<a routerLink="/employee" [queryParams]="{ order: 'asc', ... }"></a>
Passing fragments
to the routerLink
fragments
to the routerLink
<!-- employee#loading -->
<a routerLink="/employee" [fragment]="'loading'"></a>
routerLinkActive
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()
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 Urlstring
.For
dynamic
path, must use anArray
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 specifiedrelativeTo
will be the main Url where the paths will be added to.queryParamsHandling
: How navigate handlesqueryParams
andfragments
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
queryParams
// employess/5/edit?allowEdit=1
this.router.navigate(["/employee", 5, "edit"], { queryParams: { allowEdit: "1" } });
Passing fragments
fragments
// employess/5/edit#loading
this.router.navigate(["/employee", 5, "edit"], { fragment: "loading" });
Preserving queryParams
and fragments
- after Navigate with queryParamsHandling
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.
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.
export const employeesRoutes: Routes = [
// employees/:id
{ path: ":id", component: EmployeeComponent },
// employees/:id/edit
{ path: ":id/edit", component: EditEmployeeComponent },
];
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
pathMatch
optionprefix
: 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
static
titlesconst appRoutes: Routes = [
{ path: "", component: HomeComponent, title: "Home" },
{ path: "employees", component: EmployeesComponent, title: "Employees" },
];
dynamic
title
dynamic
titleYou 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
static
and dynamic
Data to a RouteYou can use both methods at the same time.
static
data
static
dataYou 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
)
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
dynamic
data - with ResolveFn
functionsYou 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
toalways
orparamsOrQueryParamsChange
.
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)
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()
input()
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
/: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
/:userId/:name
@Component({ ... })
export class UserComponent {
// user/:userId
userId = input.required<string>();
name = input.required<string>();
}
Retriving Url queryParams ?allowEdit=value
?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
#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
ActivatedRoute
serviceWith Observables
a Component can by default access it's own route params AND parent route params.
Fetching data the Reactive way - with (Observable
)
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
)
.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
?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
#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
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
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
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
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");
};
Last updated