A route resolver performs prerequisite work before Angular activates a route. Use one when navigating immediately would show an incomplete page while data loads. Unlike an activation guard, which can prevent navigation, a resolver delays activation until its work produces a result. section 26.2.1 section 26.2.2
Wait for product data
A five-second delay in a REST data source makes the problem visible: the application initially shows an incomplete layout. A model resolver instead returns a Promise that resolves when the model’s Products signal contains products. Angular waits for that Promise before activating the route. This particular condition assumes that at least one product will arrive; an empty product list would not satisfy it. section 26.2.1
Listing 26.2. The contents of the model.resolver.ts file in the src/app/model folder
import { Injectable, effect } from "@angular/core";
import { ActivatedRouteSnapshot, RouterStateSnapshot }
from "@angular/router";
import { Model } from "./repository.model"
import { Product } from "./product.model";
@Injectable()
export class ModelResolver {
private promise: Promise<Product[]>;
constructor(private model: Model) {
this.promise = new Promise((resolve) => {
effect(() => {
if (this.model.Products().length > 0) {
resolve(this.model.Products());
}
})
});
}
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot) {
return this.promise;
}
}
The resolver injects the model, watches its Products signal with effect, and returns the resulting Promise from resolve. section 26.2.1
A resolver function—or a class’s resolve method—receives an ActivatedRouteSnapshot for the destination and a RouterStateSnapshot whose url describes the current route. These arguments can tailor the work to a navigation; the product example does not use them. A returned Observable delays activation until it emits, a Promise until it resolves, and another result only until it is produced. section 26.2.1
Listing 26.4. Applying a resolver in the app.routing.ts file in the src/app folder
import { Routes, RouterModule, mapToResolve } from "@angular/router";
import { TableComponent } from "./core/table.component";
import { FormComponent } from "./core/form.component";
import { NotFoundComponent } from "./core/notFound.component";
import { ProductCountComponent } from "./core/productCount.component";
import { CategoryCountComponent } from "./core/categoryCount.component";
import { ModelResolver } from "./model/model.resolver";
const childRoutes: Routes = [
{ path: "",
children: [{ path: "products", component: ProductCountComponent },
{ path: "categories",
component: CategoryCountComponent },
{ path: "", component: ProductCountComponent }],
resolve: { model: mapToResolve(ModelResolver) }
}
];
const routes: Routes = [
{ path: "form/:mode/:id", component: FormComponent },
{ path: "form/:mode", component: FormComponent },
{ path: "does", redirectTo: "/form/create", pathMatch: "prefix" },
{ path: "table", component: TableComponent, children: childRoutes },
{ path: "table/:category", component: TableComponent,
children: childRoutes },
{ path: "", redirectTo: "/table", pathMatch: "full" },
{ path: "**", component: NotFoundComponent }]
export const routing = RouterModule.forRoot(routes, {
bindToComponentInputs: true
});
After registering the class as a provider, attach it to a route with resolve: { model: mapToResolve(ModelResolver) }. The example places it on a parent route with product and category child routes. section 26.2.1
Waiting avoids displaying the destination’s incomplete content, but it can leave the user wondering why navigation has paused. Display a loading message or other placeholder until the resolver’s condition is satisfied. section 26.2.1 section 26.3
