Getting the most out of Angular Router

@filip.mam

@filip.mam

filip.mam

@filip.mam

@filip.mam

+

+

=

SPAs

@filip.mam

Getting the most out of Angular Router

@filip.mam

Agenda

  • Why Router?

  • Meet the app

  • Level up the app!

ROUTING

LAZY LOADING

RESOLVING

@filip.mam

Why router?

@filip.mam

making SPAs with no Router

@filip.mam

making SPAs with Router

@filip.mam

Meet the app

@filip.mam

<app-nav></app-nav>

<app-movies-ranking></app-movies-ranking>
<app-tv-shows-ranking></app-tv-shows-ranking>
<app-movie-finder></app-movie-finder>

app.html

app-nav.html

<cw-button>Movies ranking</cw-buton>
<cw-button>TV shows ranking</cw-buton>
<cw-button>Movie finder</cw-buton>

@filip.mam

<app-nav></app-nav>

<app-movies-ranking *ngIf="activeIndex === 0"></app-movie-base>
<app-tv-shows-ranking *ngIf="activeIndex === 1"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>

app.html

app-nav.html

<cw-button (click)="activeIndex = 0">Movies ranking</cw-buton>
<cw-button (click)="activeIndex = 1">TV shows ranking</cw-buton>
<cw-button (click)="activeIndex = 2">Movie finder</cw-buton>

Chapter 1

 

The ugly duck

@filip.mam

@filip.mam

<app-nav></app-nav>

<app-welcome-text *ngIf="activeIndex === undefined"></app-welcome-text>
<app-movies-ranking *ngIf="activeIndex === 0"></app-movies-ranking>
<app-tv-shows-ranking *ngIf="activeIndex === 1"></app-tv-shows-ranking>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>

app.html

@filip.mam

<app-nav></app-nav>

<app-welcome-text *ngIf="activeIndex === undefined"></app-welcome-text>
<app-movies-ranking *ngIf="activeIndex === 0"></app-movies-ranking>
<app-tv-shows-ranking *ngIf="activeIndex === 1"></app-tv-shows-ranking>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 3"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 4"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 5"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 6"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 7"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 8"></app-box-office>

app.html

@filip.mam

<app-nav></app-nav>

<app-welcome-text *ngIf="activeIndex === undefined"></app-welcome-text>
<app-movies-ranking *ngIf="activeIndex === 0"></app-movies-ranking>
<app-tv-shows-ranking *ngIf="activeIndex === 1"></app-tv-shows-ranking>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 3"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 4"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 5"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 6"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 7"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 8"></app-box-office>
<app-welcome-text *ngIf="activeIndex === 9"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 10"></app-movie-base>
<app-actors-base *ngIf="activeIndex === 11"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 12"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 33"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 14"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 15"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 16"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 17"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 18"></app-box-office>
<app-welcome-text *ngIf="activeIndex === 19"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 20"></app-movie-base>

app.html

@filip.mam

<app-nav></app-nav>

<app-welcome-text *ngIf="activeIndex === undefined"></app-welcome-text>
<app-movies-ranking *ngIf="activeIndex === 0"></app-movies-ranking>
<app-tv-shows-ranking *ngIf="activeIndex === 1"></app-tv-shows-ranking>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 3"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 4"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 5"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 6"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 7"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 8"></app-box-office>
<app-welcome-text *ngIf="activeIndex === 9"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 10"></app-movie-base>
<app-actors-base *ngIf="activeIndex === 11"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 12"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 33"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 14"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 15"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 16"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 17"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 18"></app-box-office>
<app-welcome-text *ngIf="activeIndex === 19"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 20"></app-movie-base>
<app-actors-base *ngIf="activeIndex === 21"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 22"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 23"></app-box-office>
<app-goodbay-text *ngIf="activeIndex === 24"></app-welcome-text>
<app-actors-base *ngIf="activeIndex === 25"></app-movie-base>
<app-tag-base *ngIf="activeIndex === 26"></app-actors-base>
<app-tag-finder *ngIf="activeIndex === 27"></app-movie-finder>
<app-show-office *ngIf="activeIndex === 28"></app-box-office>
<app-welcome-text *ngIf="activeIndex === 29"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 30"></app-movie-base>
<app-actors-base *ngIf="activeIndex === 31"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 32"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 33"></app-box-office>

app.html

@filip.mam

@filip.mam

Chapter 2

 

The Route Knight

@filip.mam

@filip.mam

What? When? Where?

@filip.mam


const routes = [{
    path: "movie-finder",
    component: MovieFinderComponent
}]








const routes = [{
    path: "movie-finder",
    component: MovieFinderComponent
}]



@NgModule(
    declarations: [MovieFinderComponent],
    imports: [RouterModule.forRoot(routes)]
)
export class AppModule {}

app.module.ts

Routing configuration - When? What?

@filip.mam

<header>Need something to watch?</header>

<app-nav></app-nav>

<app-welcome-text></app-welcome-text>
<app-movie-base></app-movie-base>
<app-actors-base></app-actors-base>
<app-movie-finder></app-movie-finder>
<app-box-office></app-box-office>

app.html

<header>Need something to watch?</header>

<app-nav></app-nav>
<header>Need something to watch?</header>

<app-nav></app-nav>

<router-outlet></router-outlet>

Routing configuration - Where?

@filip.mam


const routes = [{
    path: "movie-finder",
    component: MovieFinderComponent
}]







app.module.ts


const routes = [{
    path: "movie-finder",
    component: MovieFinderComponent
},
{
    path: "tv-shows-base",
    component: ActorsBaseComponent
},
{
    path: "movies-base",
    component: MoviesBaseComponent
}]

@filip.mam

app-nav.html

<cw-button routerLink="movies-base">Movies base</cw-buton>
<cw-button routerLink="tv-shows-base">TV shows base</cw-buton>
<cw-button routerLink="movie-finder">Movie finder</cw-buton>
<cw-button (click)="goToMovieBase()">Movies base</cw-buton>

...

goToMovieBase(): void {
    this.routerService.navigate('/movie-base');
}
<cw-button (click)="activeIndex = 0">Movies ranking</cw-buton>
<cw-button (click)="activeIndex = 1">TV shows ranking</cw-buton>
<cw-button (click)="activeIndex = 2">Movie finder</cw-buton>

@filip.mam

app.html

<header>Need something to watch?</header>

<app-nav></app-nav>

<app-welcome-text *ngIf="activeIndex === undefined"></app-welcome-text>
<app-movie-base *ngIf="activeIndex === 0"></app-movie-base>
<app-actors-base *ngIf="activeIndex === 1"></app-actors-base>
<app-movie-finder *ngIf="activeIndex === 2"></app-movie-finder>
<app-box-office *ngIf="activeIndex === 3"></app-box-office>

<header>Need something to watch?</header>

<app-nav></app-nav>

<router-outlet></router-outlet>

@filip.mam

Improvments

1. Fix app architecture

@filip.mam

main.js

movies

ranking

tvShows

ranking

movie

finder

commons

vendors

main.pack.js

@filip.mam

movie

ranking

tvShows

ranking

movie

finder

commons

vendors

main.pack.js

@filip.mam

@filip.mam

Chapter 3

 

Lazy load all the things

@filip.mam

commons

vendors

main.pack.js

@filip.mam

shows ranking

commons

vendors

main.pack.js

1.pack.js

@filip.mam

shows

ranking

commons

vendors

main.pack.js

1.pack.js

movie

finder

       .js

@filip.mam

Lazy load components

  modules

@filip.mam


@Component({
    ...
})
export class MovieFinderComponent {
    ...
}

movie-finder.component.ts


@NgModule(
    declarations: [MovieFinderComponent]
)
export class MovieFinderModule {}

movie-finder.module.ts


@NgModule(
    declarations: [MovieFinderComponent],
    imports: [RouterModule.forChildren(movieFinderRoutes)]
)
export class MovieFinderModule {}


const movieFinderRoutes = [{
    path: "",
    component: MovieFinderComponent
}]

@filip.mam

app.module.ts


const routes = [{
    path: "movie-finder",
    component: MovieFinderComponent
},
{
    path: "tv-shows-base",
    component: ActorsBaseComponent
},
{
    path: "movies-base",
    component: MoviesBaseComponent
}]

const routes = [{
    path: "movie-finder",
    loadChildren:
},
{
    path: "tv-shows-base",
    loadChildren:
},
{
    path: "movies-base",
    loadChildren:
}]

const routes = [{
    path: "movie-finder",
    loadChildren: "./modules/movie-finder.module/#MovieFinderModule"
},
{
    path: "tv-shows-base",
    loadChildren: "./modules/tv-shows-base.module/#TvShowsBaseModule"
},
{
    path: "movies-base",
    loadChildren: "./modules/movies-base.module/#MoviesBaseModule"
}]

@filip.mam

Improvments

1. Fix app architecture

2. Reduce the size of bootstrapped app

@filip.mam

MovieRankingComponet

movieList = [...]

...

<div *ngFor="let movie of movieList">
      {{movie.title}} {{movie.year}}
</div>

@filip.mam

@Component({
    ...
})
class MoviesRankingComponent {
    movieList: Movie[] = [
        {title: "Godfather", year: "1972"},
        {title: "Inception", year: "2010"},
        ...
    ]
}

MovieRankingComponet

with static data

@filip.mam

MovieRankingComponet

with async data

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

@filip.mam

component created with empty fields

asynchronus data request

data comes back, empty fields are filled

1

2

3

MovieRankingComponet

lifecycle with async data

@filip.mam

2

3

async request time

@filip.mam

2

3

async request time

@filip.mam

Chapter 4

 

(re)solving problems

@filip.mam

/movies-ranking

/movie-finder

/tv-shows-ranking

@filip.mam

/movie-finder

I need some data m8

@filip.mam

const moviesRankingRoutes = [{
    path: "",
    component: moviesRankingComponent,
    resolver: {
        movies: MoviesResolver
    }
}]

movie-ranking.module.ts

@filip.mam

@Injectable()
class MoviesResolver implement Resolve<Movie[]> {
    
    constructor(http: Http) {}

    public resolve(): Observable<Movie[]> {
        return this.http.get("api/movies");
    }
}

movies-resolver.service.ts

@filip.mam

route recognized

resolver fired, http get request

data resolved and ready to be served

1

2

3

component is constructed

4

MovieRankingComponet

with resolver

@filip.mam

resolver

@Component({
    ...
})
class MovieFinderComponent {
    
    movieList: Movie[] = [];
    
    constructor(route: ActivatedRouteSnapshot) {
        this.movieList = route.data.movies   
    }
}

movie-finder.component.ts

@filip.mam

Improvments

1. Fix app architecture

2. Reduce the size of bootstrapped app

3. Prevent loading fails

@filip.mam

Chapter 5

 

Composing resolvers

@filip.mam

movie-ranking.module.ts

public resolve(): Observable<Movie[]> {
    return this.http.get("api/tv-shows");
}

...

const tvShowsRankingRoutes = [{
    path: "",
    component: moviesRankingComponent,
    resolver: {
        tvShows: TvShowsResolver
    }
}]

...

constructor(route: ActivatedRouteSnapshot) {
    this.tvShows = route.data.tvShows   
}

@filip.mam

movie-ranking.module.ts

const movieFinderRoutes = [{
    path: "",
    component: moviesRankingComponent,
    resolver: {
        movies: MoviesResolver,
        tvShows: TvShowsResolver
    }
}]

...

constructor(route: ActivatedRouteSnapshot) {
    this.movies = route.data.movies;       
    this.tvShows = route.data.tvShows;   
}

@filip.mam

Improvments

1. Fix app architecture

2. Reduce the size of bootstrapped app

3. Prevent loading fails

4. Create reusable resolvers

@filip.mam

@filip.mam

@filip.mam

Chapter 6

 

Router events

@filip.mam

@Component({
    template: "<div>LOADING...</div>",
    styleUrls: ["./stlyes.url"]
})
class LoaderComponent {
    ...
}

loader.component.ts

@filip.mam

<header>Need something to watch?</header>

<app-nav></app-nav>

<router-outlet></router-outlet>

<router-outlet name="loader"></router-outlet>

app.html

@filip.mam

routes = [
    ...,
    {
        path: "loader",
        component: LoaderComponent,
        outlet: "loader"
    }
]

app.module.ts

@filip.mam

constructor(router: Router) {}

...

this.router.events.subscribe(event) {

    if (event instanceof ResolveStart) {
        this.showLoader();
    }

    if (event (instanceof ResolveEnd) {
        this.hideLoader();
    }

}

app.ts

@filip.mam

@filip.mam

Improvments

1. Fix app architecture

2. Reduce the size of bootstrapped app

3. Prevent loading fails

4. Create reusable resolvers

5. Added loading mechanism

@filip.mam

Chapter 7

 

Thanks!

Get the most out of Angular Router - Angular Kraków #2

By Filip Mamcarczyk

Get the most out of Angular Router - Angular Kraków #2

  • 866