I have a List of entries and when I click into one list I get into a more detailed view of the item. Now this view load via http-Request (which is taking a little longer than the routing from page to page). So let's say I have the following code:
For the destination-page:
projectNumber: string = '';
ngOnInit() {
}
constructor(
private router: Router,
private route: ActivatedRoute,
private logger: LoggerService,
private projectReceiverService: ProjectReceiverService
) {
this.initProjectDataFromProjectNumber(this.route.snapshot.paramMap.get('projectNum'));
}
initProjectDataFromProjectNumber(projectNumber: string) {
let project = this.projectReceiverService.getProjectByNumber(projectNumber);
this.projectNumber = projectNumber; //getProjectNumber => results in error
}
For the service:
getProjectByNumber(projectNumber: string): Project {
let project: Project;
this.http.get(httpBaseUrl + "/project/getProject/" + projectNumber, httpOptions).subscribe((data) => {
this.logger.info("Received data, processing...");
project = data['projectDto'];
},
(error) => {
this.logger.error("Error when receiving project.");
this.logger.error(error.message);
});;
return project;
}
And for the previous page (where I click the item):
projectClicked(projectNumber: string) {
this.routeTo('projects/' + projectNumber);
}
Now it'd be great if I could somehow manage to route after the http-request has fully successed (since I could e.g. pass the project-object directly when routing and also I wouldn't hit struggles with displaying some data that might not yet be loaded on the detailed-page). How could I do that? I thought about loading it on the previous side before something gets clicked but that'd be hell of an overload to previously load all details for every item.
You can do this relatively easily with route resolver.
It goes like this. In your route config, you give your route path, component, and a resolver
.
const routes = [{
path: 'projects/:projectNumber',
component: DestinationComponent,
resolve: {
project: ProjectResolver
},
}];
Now you have to put this route in the module, and provide the resolver you mentioned. E.g. like this:
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [ProjectResolver],
})
export class ProjectDetailsModule {}
But what is this ProjectResolver? It is simply an injectable class implementing Angular's Resolve interface, which on it's resolve()
method gives back the data, either as data, Promise or Observable. Looks like this:
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
// You will also need your service
import { ProjectService } from './project.service';
@Injectable()
export class ProjectResolver implements Resolve {
// Inject it with where the data comes from
constructor(private projectService: ProjectService) {}
// Now implement the resolve interface. Angular will give you the route,
// from which you can read the parameter.
resolve(route: ActivatedRouteSnapshot): Observable<Project> {
const projectNumber = route.snapshot.params['projectNumber'];
return this.projectService.getProjectByNumber(projectNumber);
}
}
Now, we have to fix a little your service - the http method is usually async, so you have to return the observable instead:
// I'll change the return type, from Project, to Obsevable<Project>
getProjectByNumber(projectNumber: string): Observable<Project> {
return this.http.get(`${ httpBaseUrl }/project/getProject/${ projectNumber }`, httpOptions)
// Instead of subscribe, you'll use a pipe, to map. So that others can actually subscribe (in our case the Angular router)
.pipe(
map(data => {
this.logger.info("Received data, processing...");
project = data['projectDto'];
return project;
// This whole `map(...)` part could have been a one-liner:
// map(data => data.projectDto)
// so alltogether:
// this.http.get().pipe( map(data => data.projectDto), catchError(this.logger.error));
},
catchError(err => this.logger.error('Error getting project:', err.message)),
);
}
Only one thing left. How do we get the data once in the component? From the route, again.
// in the component
ngOnInit() {
this.activatedRoute.data.subscribe(project => this.project = project);
// or something like this for immediate value:
// this.data = this.route.snapshot.data;
}
Note that this will hold your page in "loading" until the data is actually back. If you wanna fetch the data in parallel, that is a different answer, but can also be done.