Skip to main content

Smoother UX on Page Load with Angular Resolvers

Resolving your data quickly with Angular resolvers improves page loading, user experience, and lets you handle errors immediately. We’ll create a Profile Resolver to resolve profile data.


Why use an Angular Resolver?

Let’s breakdown the Angular Resolver

export interface Resolve<T> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return 'data';
}
}


Step 1: Creating the Resolver

Annotate the Profile Resolver class with an injectable decorator

@Injectable({   providedIn: 'any'})
@Injectable({providedIn: 'any'})
export class ProfileResolver implements Resolve<Profile> {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T {
return;
}

Retrieving the data from the database

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Profile } from '@globals/classes/profile';
import { SeoService } from '@services/seo/seo.service';
import { ProfileService } from '@services/profile/profile.service';
import { from, Observable } from 'rxjs';
import { catchError, take, tap } from 'rxjs/operators';

@Injectable({
	providedIn: 'any'
})
export class ProfileResolver implements Resolve<Observable<Profile | boolean>> {
	
	constructor(private profileService: ProfileService,
              	private router: Router,
	            private seoService: SeoService) {}
	
	resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Profile | boolean> {
		
		if (!route.paramMap.has('userSlug') {
		  return from(this.router.navigate(['/search']));
		}
		const userSlug = route.paramMap.get('userSlug');
		return this.profileService.getProfile$(userSlug)
			.pipe(take(1), tap(user => {
				this.seoService.updateTitle(`${ user.displayName }'s Profile`);
			}), catchError(() => from(this.router.navigate(['/profiles'])));
	}
}


Bonus: Populate Dynamic SEO metadata quickly before the route has loaded

Step 2: Inject the resolver into the routing module

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProfileResolver } from '@app/resolvers/profile-resolver';


const routes: Routes = [
	{
		path   : ':userSlug',
		resolve: { data: ProfileResolver }
	}
];

@NgModule({
	imports: [ RouterModule.forChild(routes) ],
	exports: [ RouterModule ]
})
export class ProfileRoutingModule {}


Step 3: Initialise the component with resolved data

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { concatMap, pluck } from 'rxjs/operators';
import { ProfileService } from '@services/profile/profile.service.ts';

@Component({
	selector       : 'ngx-profile',
	templateUrl    : './profile.component.html',
	styleUrls      : [ './profile.component.scss' ],
	changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProfileCardComponent implements OnInit {
  	profile$: Observable<Profile>;
    
	constructor(private route: ActivatedRoute, private profileService: ProfileService) {}
  
  	ngOnInit(): void {
    		this.profile$ = this.route.params.pipe(concatMap(({userSlug}) => this.profileService.getProfile$(userSlug)))
			.pipe(startWith(this.route.snapshot.data['data']));
  	}
  

}

What immediately accessible data does for the component

There are multiple ways that the component can load fragmented

That’s it! We have a resolver with an improved UX on page load.

Any questions, let me know in the 




 

Comments