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
- 923