Separate your concerns
with router resolver
@filip.mam

@filip.mam


@filip.mam


@filip.mam

+ SPAs +

=

@filip.mam

Agenda
1. Why resolver?
2. The app
3. (re)Solving problems
Why resolver?
@filip.mam

@filip.mam

SPAs without Angular Router

@filip.mam

SPAs with Angular Router

@filip.mam

SPAs with Angular Router + guards

@filip.mam

Guards
www.app.com
@filip.mam

Guards
/feature-a
/feature-b
/feature-c
@filip.mam

Guards
const routes = [{
path: "feature-a",
component: FeatureAComponent
},
{
path: "feature-b",
component: FeatureBComponent
},
{
path: "feature-c",
component: FeatureCComponent
}]@filip.mam

Guards
/feature-a
/feature-b
/feature-c





@filip.mam

Resolver
/feature-b
U got some data m8?

Static data
Promises
Observables
The app
@filip.mam

@filip.mam

<nav-button routerLink="movies-ranking"></nav-button>
<nav-button routerLink="tv-shows-rankig"></nav-button>
<nav-button routerLink="movie-finder"></nav-button>
<nav-button routerLink="profile"></nav-button>
...
<router-outlet></router-outlet>
...
routes = [{
path: "movies-ranking",
component: MoviesRankingComponent
},
{
path: "tv-shows-ranking",
component: TvShowsRankingComponent
},
...]MoviesRankingComponent
@filip.mam

moviesList: Movie[] = [...]
...
<div *ngFor="let movie of movieList">
...
</div>MoviesRankingComponent

@filip.mam

moviesList: Movie[] = []
...
<div *ngFor="let movie of []">
...
</div>MoviesRankingComponent

@filip.mam

MoviesRankingComponent
@Component({
...
})
class MoviesRankingComponent {
movieList: Movie[] = [];
constructor(http: Http) {
http.get('api/movies').subscribe(
movies => this.movieList = movies;
);
}
}lifecycle with async data
@filip.mam

MoviesRankingComponent
component instantiated with empty data
get request for data created
component rendered with empty data
data comes back, component rerendered
lifecycle with async data
route recognized
@filip.mam

MoviesRankingComponent
with good internet connection


@filip.mam

MoviesRankingComponent
with bad internet connection

Resolver to the rescue
@filip.mam

@filip.mam

MoviesResolver
const routes = [{
path: "movies-ranking",
component: MoviesListComponent
}]
@filip.mam

MoviesResolver
const routes = [{
path: "movies-ranking",
component: MoviesRankingComponent,
resolve: {
movies: MoviesResolver
}
}]@filip.mam

MoviesRankingComponent
with async data
@Component({
...
})
class MoviesRankingComponent {
movieList: Movie[] = [];
constructor(http: Http) {
http.get('api/movies').subscribe(
movies => this.movieList = movies;
);
}
}@filip.mam

MoviesResolver
@Injectable()
class MoviesResolver implements Resolve<Movie[]> {
constructor(http: Http) {}
public resolve(): Observable<Movie[]> {
return this.http.get("api/movies");
}
}@filip.mam

MoviesRankingComponent
with data from resolver
@Component({
...
})
class MoviesRankingComponent {
movieList: Movie[] = this.activatedRoute.snapshot.data.movies;
constructor(private activatedRoute: ActivatedRoute) {}
}@filip.mam

MoviesRankingComponent
with data from resolver
@Component({
...
})
class MoviesRankingComponent {
movieList: Movie[] = this.activatedRoute.snapshot.data.movies;
constructor(private activatedRoute: ActivatedRoute) {}
}
...
const routes = [{
path: "movies-ranking",
component: MoviesRankingComponent,
resolve: {
movies: MoviesResolver
}
}]
@filip.mam

MoviesRankingComponent
reslover fired, get request
data comes back, resolver passes data forward
component constructed with data from resolver
component rendered
lifecycle with resolver
route recognized
Composing resolvers
@filip.mam

@filip.mam

TvShowsResolver
public resolve(): Observable<Movie[]> {
return this.http.get("api/tv-shows");
}
...
const tvShowsRankingRoutes = [{
path: "",
component: moviesRankingComponent,
resolver: {
tvShows: TvShowsResolver
}
}]
...
constructor(route: ActivatedRoute) {
this.tvShows = route.snapshot.data.tvShows
}@filip.mam

const routes = [{
path: "movies-ranking",
component: MoviesRankingComponent,
resolve: {
movies: MoviesResolver
}
},
{
path: "tv-shows-ranking",
component: TvShowsRankingComponent,
resolve: {
movies: TvShowsResolver
}
}]
@filip.mam

MovieFinderResolver
const routes = [{
path: "movies-finder",
component: MoviesFinderComponent,
resolve: {
movieFinderData: MovieFinderResolver
}
}]
@filip.mam

MovieFinderResolver
const routes = [{
path: "movies-finder",
component: MoviesFinderComponent,
resolve: {
movies: MoviesResolver,
tvShows: TvShowsResolver
}
}]
@filip.mam

MovieFinderResolver
const routes = [{
path: "movies-finder",
component: MoviesFinderComponent,
resolve: {
movies: MoviesResolver,
tvShows: TvShowsResolver
}
}]
...
constructor(route: ActivatedRoute) {
this.movies = route.snapshot.data.movies;
this.tvShows = route.snapshot.data.tvShows;
}@filip.mam


App with resolvers
@filip.mam


App with notifications
@filip.mam

NotifitactionService
@Injectable()
export class NotificationService() {
...
public showLoading(): void {
...
}
public hideLoading(): void {
...
}
}@filip.mam

MovieRankingComponent
@Injectable()
class MoviesResolver {
constructor(http: Http, notifitactionService: NotifitactionService) {}
public resolve(): Observable<Movie[]>
notifitactionService.showNotification();
http.get('api/movies').subscribe(movies => {
notifitactionService.hideNotification();
return movies;
);
}
}Notifications with router.events
@filip.mam

@filip.mam

MainComponent
@Component({
...
})
class MainComponent {
constructor(router: Router, notificationService: NotificationService) {
this.router.events.subscribe(event) {
if (event instanceof ResolveStart) {
this.showLoading();
}
if (event (instanceof ResolveEnd) {
this.hideLoading();
}
}
}@filip.mam

App on error

@filip.mam

App on error with notification

Handling errors
@filip.mam

@filip.mam

NotifitactionService
@Injectable()
export class NotificationService() {
...
public showLoading(): void {
...
}
public hideLoading(): void {
...
}
public showError(): void {
...
}
}@filip.mam

MoviesResolver
@Injectable()
class MoviesResolver implement Resolve<Movie[]> {
constructor(http: Http) {}
public resolve(): Observable<Movie[]> {
return this.http.get("api/movies");
}
}@filip.mam

MoviesResolver
@Injectable()
class MoviesResolver implement Resolve<Movie[]> {
constructor(http: Http, notificationService: NotificationService) {}
public resolve(): Observable<Movie[]> {
return this.http.get("api/movies")
.catch(error => {
this.handleError(error);
notificationService.showErorr();
});
}
private handleError(error): void {...}
}@filip.mam

AppModule
@NgModule({
imports: [RouterModule.forRoot(routes, {
errorHandler: AppErrorHandler
})]
})
@filip.mam

AppErrorHandler
class errorHandler() implements ErrorHandler {
constructor(private notificationService: NotificationService) {}
public handleError(error) {
...
this.notificationService.showError();
}
}@filip.mam

Improvements
Prevented loading fails
Created reusable resovlers
Improved UX with notifications
Added error handling
Separated the logic
Thanks!
@filip.mam

Separate your conerns with router resolver - ngPoland
By Filip Mamcarczyk
Separate your conerns with router resolver - ngPoland
- 1,012