17 martie 2020

Angular - ziua 4 - Routing

Routing - functionalitate oferita de Angular pentru a da impresia mai multor pagini pe care  poate naviga un utilizator. In realitate este o singura pagina pe care se plimba mai multe componente.

Pasii pentru inregistrarea rutelor:

1. Rutele trebuie declarate in app.module.ts, exemplu:

const appRoutes = Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent }
]

2. In app.module.ts -> @NgModule -> imports, se adauga: RouterModule.forRoot(appRoutes)

3. In fisierul html se adauga: <router-outlet></router-outlet> in locul componentelor care fusesera adaugate folosind selectori (adica in exemplu, in locul lui <app-home> si <app-users>).

La acest moment, accesarea paginii de baza (http://localhost:4200/ si http://localhost:4200/users va duce utilizatorul catre cele 2 componente, dar nu exista navigatie in interior).

4. Adaugarea navigatiei:
- actualizare cu <a href="/"> sau <a href="/users" in html => dezavantaj ca se va face refresh la pagina de fiecare data cand se acceseaza acele link-uri
- inlocuire <a href> cu <a routerLink="/users"> pentru a scapa de refresh. Alternativa este <a [routerLink]="['/users']">

Alternativ (primii 2 pasi):
1. Outsourcing - se creeaza un nou modul "app-routing.module.ts", in care se declara const appRoutes ca la pct.1 (dar in afara  definitiei clasei). Clasa va fi adnotata cu:
@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule]
})
2. In app.module.ts -> @NgModule -> imports, se adauga AppRoutingModule

Tips<li routerLinkActive="active"> marcheaza ruta activa (via Bootstrap). Se adauga pe <li> care detine link-ul pe care s-a navigat.

Rutare programatica
Se injecteaza Router => se adauga Router in constructor, dupa care se poate folosi intr-o metoda din Typescript cu sintaxa: this.router.navigate(['/users']);

Cai relative: in constructor se injecteaza in plus route: ActivatedRoute
this.router.navigate(['/users'], {relativeTo: this.route});

Cai dinamice:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent }
    { path: 'users/:id', component: UsersComponent }
]
Acum se poate accesa un anume utilizator cu id=3 la http://localhost:4200/users/3:
- id este un camp dintr-o variabila user definita in UsersComponent.
- acestei componente i se injecteaza in constructor route: ActivatedRoute
- id-ul se poate obtine apoi prin this.routes.snapshot.params['id'];
Se pot adauga si alte elemente dinamice:  { path: 'users/:id/:name', component: UsersComponent }

!!! Pentru actualizarea obiectului din Typescript cand se apeleaza un link asemanator cu rutare, pentru ca schimbarile sa se propage in template, se adauga: this.route.params.subscribe( (params: Params) -> { this.user.id = params['id']; });
!!! Pentru ca subscriptia sa nu traiasca la nesfarsit in ciuda distrugerii componentei din care face parte, se poate salva ca variabila, iar in ngOnDestroy() se va apela: this.subscription.unsubscribe();

Cai care contin parametri de tip query:
<a [routerLink]="['/users']" [queryParams]="{allowEdit: '1'}" fragment="loading" href="#"> // va duce catre http://localhost:4200/users?allowEdit=1#loading

Echivalent in Typescript: this.router.navigate(['/users'], {queryParams: {allowEdit: '1'}}, fragment: 'loading');

Accesul la parametrii de tip query (non-reactiv la schimbari):
- se injecteaza route: ActivatedRoute in constructor
- se acceseaza prin: this.route.snapshot.queryParams si this.route.snapshot.fragment
Reactiv: se apeleaza subscribe() pe cele 2 variabile, in ngOnInit().

Tips: const id = +this.route.snapshot.params['id']; // va cauza ca id-ul sa fie interpretat ca un numar si nu un string (default)

Nested routes:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent, children: [
        { path: ':id', component: UsersComponent }
    ]}
]

Pastrarea parametrilor query - this.router.navigate(['/users'], {relativeTo: this.route, queryParamsHandling: 'preserve'});  // optiuni: merge, preserve

Redirectari pentru pagini invalide - se adauga in app.module.ts:

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent },
    { path: 'not-found', component: PageNotFoundComponent },
    { path: '**', redirectTo: '/not-found' }
]

'**' este wildcard pt orice altceva. Ordinea este importanta (redirectarea va fi intotdeauna ultima ruta adaugata)
Tips: { path: '', redirectTo: '/somewhere-else', pathMatch: 'full' } pentru ca '' sa nu mai faca match cu absolut orice

Route Guards - functionalitati care se executa inainte sa se incarce o ruta, sau imediat dupa parasirea ei.

export class AuthGuard implements CanActivate {
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
         return this.authService.isAuthenticated().then(
            (authenticated: boolean) => { if (authenticated) { return true; } else { router.navigate(['/']); } });
    }
}

Apoi,  pentru a proteja rutele:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', canActivate: [AuthGuard], component: UsersComponent } // va fi accesibila doar daca canActivate() din AuthGuard intoarce true
}

Protejarea rutelor-copii: AuthGuard va implementa si interfata CanActivateChild -> se suprascrie metoda canActivateChild cu aceiasi parametri, in care doar se cheama canActivate(...)

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', canActivateChild: [AuthGuard], component: UsersComponent, children: [
        { path: ':id', component: UsersComponent }
    ]}
]

Transmiterea de date statice unei rute:

1. in declararea rutei se adauga:
const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent },
    { path: 'not-found', component: PageNotFoundComponent, data: {message: 'Page not found!'} },
    { path: '**', redirectTo: '/not-found' }
]

2. in TypeScript se injecteaza un ActivatedRoute, iar in ngOnInit() se adauga: this.errorMessage = this.route.snapshot.data['message'];
Eventual se poate inlocui cu urmatoarea linie, daca vrem sa fim la curent cu schimbarile:
this.route.data.subscribe( (data: Data) => { this.errorMessage = data['message']; } );

Transmiterea de date dinamice unei rute:

1. in declararea rutei se adauga:

const appRoutes : Routes [
    { path: '', component: HomeComponent },
    { path: 'users', component: UsersComponent, resolve: {detectie: DetectionResolver} }
]
// aici detectie este un keyword ales

2. se declara DetectionResolver ca implementand Resolve<MyDataType>, in care trebuie implementata metoda resolve:

resolve (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<MyDataType> | Promise<MyDataType> | MyDataType {
    // aici se poate folosi un serviciu injectat deja in constructor pentru a putea aduce date din exterior
    // se va returna obiectul MyDataType continand informatiile dorite (sau un Observable, sau un Promise)
}

3. in TypeScript-ul componentei, in care s-a injectat un route: ActivatedRoute, se poate acum obtine obiectul MyDataType cu informatiile dorite, folosind in cazul de mai sus: this.route.snapshot.data['detectie'];

---
Sursa: cap. 11-12 Udemy

Niciun comentariu: