Monday, July 17, 2017

more than you ever wanted to know about Angular 4 routing

  1. Picking back up from bullet point nine here, I can fish back out the id like so at an Angular 4 component:
    let id = this.whereAmI.snapshot.params['id'];
    This will require some wire up. Firstly like so:
    import { ActivatedRoute } from '@angular/router';
    ...and, yes, there is a second step:
    constructor(private whereAmI: ActivatedRoute){}
  2. The trick above will not work if you are in the component with the id already. It will work when you navigate there, but then when you manually change the id at the url line or click a link that forces the id to change the component will not update as Angular will refuse to reload components willy-nilly. To get around this chase ActivatedRoute with ,Params at the import statement and then do something like so:
    this.whereAmI.params.subscribe(
       (gunk: Params) => {
          this.idNow = gunk['id'];
       }
    );
  3. Alright, the params thing above is an example of a subscription that will get destroyed when the component is also destroyed, but this isn't always the case. There will be plenty of times when a subscription will not die in such a circumstance and you must do the work to kill it. (Don't let it linger.) For starters, in those cases, you'll need this import:
    import { Subscription } from 'rxjs/Subscription';
    Moreover, the component needs to implement the OnDestroy lifecycle hook and declare a Subscription variable somewhere in the component outside of the methods (not in a method) so that the variable is available component-wide. Let's call our variable Foo, alright? When we start the subscription we would need to assign it to Foo like so:
    this.Foo = this.whereAmI.params.subscribe(
       (gunk: Params) => {
          this.idNow = gunk['id'];
       }
    );

    Again, this use case is really not a use case wherein we need to undertake this headache, but it ironically also won't hurt. Anyhow, long story short, we destroy the subscription like this:
    ngOnDestroy(){
       this.Foo.unsubscribe();
    }
  4. How do I add the url line variables separated by ampersands with a leading question mark and the hashtag/numbers symbol/pound sign led setting for a named anchor which was once upon the time used for midpage links that were not supposed to reload the page and evoke another round trip to the server? Alrightly, let's suppose our url is to be http://www.example.com/whatever/blossom?bar=bubbles&baz=buttercup#qux assuming a route with a path of 'whatever/:foo' which would mean that an a tag with [routerLink]="['/whatever','blossom']" in its guts would also get [queryParams]="{bar: 'bubbles', baz: 'buttercup'}" and [fragment]="qux" in its guts at a template. At the TypeScript side this looks like:
    this.myRouter.navigate(['/whatever','blossom'], {queryParams:
       {
          bar: 'bubbles',
          baz: 'buttercup'
       }
    }, fragment: 'qux');
  5. fish this stuff back out with this.whereAmI.snapshot.queryParams, this.whereAmI.snapshot.fragment, this.whereAmI.queryParams.subscribe(), and this.whereAmI.fragment.subscribe()
  6. All of the variables pulled out of params, queryParams, and fragment are going to be strings. You must cast them to other types if you need other types. Did you know that you may just use + in lieu of <number> for casting to a number?
  7. There is a way to have a cascading effect with nested routes so that the components summoned into the <router-outlet></router-outlet> tag may have <router-outlet></router-outlet> tags of their own wherein a nested component within the nested component may vary based on the URL. That looks like this:
    const myRoutes = [
       {path: '', component: StartingOutComponent},
       {path: 'animal', component: AnimalComponent, children: [
          {path 'bat', component: BatComponent},
          {path 'cat', component: CatComponent},
          {path 'rat', component: RatComponent}
       ]}
    ];

    The URLs for the five items above might be http://www.example.com/ and http://www.example.com/animal and http://www.example.com/animal/bat and http://www.example.com/animal/cat and http://www.example.com/animal/rat in that order.
  8. this.myRouter.navigate(['/yin','yang'], {queryParamsHandling: 'preserve'}); will preserve your query parameters as you navigate away and this.myRouter.navigate(['/yin','yang'], {queryParamsHandling: 'merge'}); will mix them in with the other parameters to come where you land.
  9. {path:'biscuit', redirectTo:'/muffin'} is a way to make one route fall over to another and if you have {path:'**', redirectTo:'/muffin'} as your very last route all routes that can't be found will get redirected to the route speced.
  10. As your routing grows fat, it's a good idea to pull it out of the godmost wrapper module gateway into everything and give it its own module. By convention this will be called AppRoutingModule and will live in a file named app-routing-module.ts and you will need to have RouterModule as one of the exports in this new module and you will need to have AppRoutingModule as an import in your highfalutin boss module enveloping the rest of the app.
  11. Alight, you may protect a route from access from the unauthorized by putting canActivate: [AuthGuard] in line in a route and this in turn will reference a file that very specifically should by convention live in a TypeScript file called specifically auth-guard.service.ts. AuthGuard, a service, and another sister service for actually making database calls to see if the user at hand is authenticated (let's call it WhateverService) are going to need to be looped in as services at the outermost module as variables at the constructor, etc. WhateverService will vary from app to app but AuthGuard should really be something pretty specific. The bullet points so far have all been stuff I've picked up in watching a series of online trainings and I've been trying to reshape what I learn before I regurgitate it to you in the name of not being guilty of plagiarism, but AuthGuard needs to be so specific that what follows is just outright stolen from elsewhere. Can you guess where? Anyhow this is what the most simple skeleton for AuthGuard should look like before you loop in code specific to the WhateverService such as, of course, looping it in as a variable at the constructor signature.
    import {
       CanActivate,
       ActivatedRouteSnapshot,
       RouterStateSnapshot
    } from '@angular/router';
    @Injectable()
    import { Observable } from 'rxjs/Observable';
    export class AuthGuard implements CanActivate {
       canActivate(route: ActivatedRouteSnapshot, state RouterStateSnapshot):
             Observable<boolean> | Promise<boolean> | boolean {
       }
    }

    Alright, you have to have the canActivate if you implement CanActivate and it should return something even though we don't see it do so above. Well, it should return something based on what WhateverService hands back from a request like so:
    isAuthenticated() {
       const promise = new Promise(
          (resolve, reject) => {
             setTimeout(() => {
                resolve(this.loggedIn);
             }, 800);
          }
       };
       return promise;
    }

    canActivate is gonna hand back this.router.navigate(['/']); ultimately to deny someone or true; to let them through.
  12. There is a way to allow anyone to see a route but to wall off that route's child routes behind maybe-we'll-let-you-in security. The trick, specificially, is to use canActivateChild: [AuthGuard] instead of canActivate: [AuthGuard] (the setting still goes at the parent route), and all you have to do to set that up is to refactor the AuthGuard skeleton for route guards like so:
    import {
       CanActivate,
       CanActivateChild,
       ActivatedRouteSnapshot,
       RouterStateSnapshot
    } from '@angular/router';
    @Injectable()
    import { Observable } from 'rxjs/Observable';
    export class AuthGuard implements CanActivate, CanActivateChild {
       canActivate(route: ActivatedRouteSnapshot, state RouterStateSnapshot):
             Observable<boolean> | Promise<boolean> | boolean {
       }
       canActivateChild(route: ActivatedRouteSnapshot, state RouterStateSnapshot):
             Observable<boolean> | Promise<boolean> | boolean {
          return this.canActivate(route, state);
       }
    }

    Again, this is just a skeleton. This will not work as long as the canActivate method returns nothing. The thing this does not show yet is your own code for cross talking with another service yet that talks to your database and finds out if Alice and Bob are active users. Once more, either a rerouting or just true will be returned and there is much wiggle room for how it may be returned. It could just be returned outright or it may come back as part of an observable or a promise. In the case of a promise you would be catching the promise and then doing a .then to have at what will happen when the promise resolves.
  13. There is a way to do a CanDeactivate to give users "Are you sure?" messages when they attempt to leave a route. You do that with code like so:
    import { Observable } from 'rxjs/Observable';
    import { CanDeactivate } from '@angular/router';
    import { MyInterface } from './my-interface.ts';
    export class MyGuard implements CanDeactivate<MyInterface> {
       canDeactivate(component: MyInterface,
             currentRoute: ActivatedRouteSnapshot,
             currentState: RouterStateSnapshot,
             nextState?: RouterStateSnapshot): Observable<boolean> |
                   Promise<boolean> | boolean {
          return component.leavingSoSoon();
       }
    }

    You'll need to make a file for the interface suggested too:

    import { Observable } from 'rxjs/Observable';
    export interface MyInterface {
       leavingSoSoon: () => Observable<boolean> | Promise<boolean> | boolean;
    }

    Alright, in order to make this stuff work you put a canDeactivate inline at the route and the component that uses it must implement the interface specified, I guess MyInterface in this case. This will force the component to have a leavingSoSoon method inside of it like this:
    leavingSoSoon(): Observable<boolean> | Promise<boolean> | boolean {
       return confirm('Leaving so soon?');
    }
  14. You may jam little messages inline in a route by putting data: {talky: 'I am talking.'} or something similar inline in a route and then pull this content back out with:
    let x = this.whereAmI.snapshot.data['talky'];
    and...
    this.y = this.whereAmI.data.subscribe(
       (gunk: Data) => {
          this.z = gunk['talky'];
       }
    );
  15. resolver: {talky: MyResolver} will behave like the data setting but will, in a roundabout way, take one of the other route params or queryParms or fragments and find a friendly message for it which will be fished out the same manner as suggested in the bullet above. You'll need an interface:

    interface MyMessagingObject {
       id: number,
       name: string,
       status: string
    }

    It gets used by something like so which has to be looped in at the providers of the outermost module:
    import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot }
          from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    import { MyMessagingObject } from './my-messaging-object.ts';
    export class MyResolver implements Resolve<MyMessagingObject> {
       resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
             Observable<MyMessagingObject> | Promise<MyMessagingObject> |
             MyMessagingObject {
       }
    }

    Again I am showing a skeleton here. The resolve route has to return something. I recommend doing a dot dot dot off of "route" to get the parameter you need and then to go fishing with it to look up from another service whatever you need to fill out MyMessagingObject which you will ultimately get back instead of a mere string.

  16. RouterModule.forRoot(myRoutes, {useHash: true}) as a slight change up from the other stuff at the link at the top of these bullets lets us have routes with a hastag in the middle of them such as http://www.example.com/#/whatever and you really only want to do this if you are accomodating some really old browser. It is possible that the routing won't work without this hack.

No comments:

Post a Comment