Angular Material Router Awareness

instructor

Author

Alex Patterson

1/3/2021

Last Updated: 3/15/2021

section

Setup

We can start from the previous lesson and build out our material router. Previous Lesson: Angular Material Reactive Forms Update Firestore

git clone https://github.com/AJONPLLC/lesson13-angular-material-reactive-forms.git

This will give us a solid base to start working from, however if you are creating a new firebase project you should change the environment/environment.ts file to match your firebase details. If you have never done this please see Angular Navigation Firestore as this will provide more details on how to update.Make sure you update your npm packages

npm install

Open/Close Books Drawer

If you are not familiar with Angular Input and Output Properties, please do give it a good read so you can understand why we are using the syntax. For the visual learner out there ( ) looks like the letter O in Output and [ ] kinda looks like the N for iNput. That is how I try to remember it ????.

Template

  • [opened]="showBooksDrawer$ | async" will take an observable as input with a true/false value, which will open or close the drawer.
  • (activate)="onBooksDrawerActivate($event)" will output the event observable when the drawer is activated
  • (deactivate)="onBooksDrawerDeactivate($event)" will output the event observable when the drawer is deactivated

src/app/modules/books/

<mat-drawer-container>
    <mat-drawer [opened]="showBooksDrawer$ | async" mode="side">
        <router-outlet name="book-drawer" (activate)="onBooksDrawerActivate($event)"
            (deactivate)="onBooksDrawerDeactivate($event)"> </router-outlet>
    </mat-drawer>
    <mat-drawer-content>
        <router-outlet></router-outlet>
    </mat-drawer-content>
</mat-drawer-container>

Component

Now that we can pass observables into the mat-drawer and receive events out from router-outlet, we need to act upon these movements.We can use RxJS's BehaviorSubject to provide an initial value and a stream for observing changes to that value.showBooksDrawer$ = new BehaviorSubject(false); will now show a closed drawer upon creation because we are defaulting to a false value./src/app/modules/books/book-drawer/

showBooksDrawer$ = new BehaviorSubject(false);

The below methods will handle the output that is emmited by the router-outlet. You can find more details in the Angular docs.

/src/app/modules/books/book-drawer/

onBooksDrawerActivate(e) {
    this.showBooksDrawer$.next(true);
}
onBooksDrawerDeactivate(e) {
    this.showBooksDrawer$.next(false);
}

Material Fab Button for book add

If you have never heard of a fab button it stands for Floating Action Button. You can see many different material examples to compare what you think might work best for your situation.Below you will see that we are going to create a pink button (our accent color). So that we can add books to our book list only when we are verified user and in the correct view.

fab button

Adding Required Modules

We need to import both the MatButtonModule for the FAB button, as well as the MatIconModule for the plus sign.

/src/app/modules/books/

import { MatButtonModule, MatIconModule, MatSidenavModule } from '@angular/material';
...
imports: [...MatIconModule, MatButtonModule]

Positioning FAB

We will now need to update the styles so that we can correctly position this new button. Other platforms are good about placing this very specifically and then allowing you to override. If you forget to do this in Angular Material it does not have a default./src/app/modules/books/

.app-fab--absolute {
    position: fixed; bottom: 1rem; right: 1rem;
}
@media (min-width: 1024px) {
    .app-fab--absolute {
        bottom: 2rem; right: 2rem;
    }
}

Adding FAB to template

Although this is using a button html tag, you then add attributes (technically directives):

  • mat-fab to provide the rounded shape in our directive.
  • color="accent" to give this our pink color Check Material Theming for more details.
  • class="app-fab-absolute is setting up our class that we defined above in our styles
  • routerLink="/books/new" this is the directive for the Angular router to take action when the button is clicked, see Angular docs
  • aria-label="Add" allows for accessiblity features ARIA
  • <mat-icon aria-label="Add Book">add</mat-icon> is the entire component required for the add type icon.

/src/app/modules/books/

<button mat-fab color="accent" class="app-fab--absolute" routerLink="/books/new" aria-label="Add">
    <mat-icon aria-label="Add Book">add</mat-icon>
</button>

Unsubscribing from Subscriptions

Please note I have since learned an easier way using a seperate Subject and it is used in the next chapter of this course, here is the source.

If we don't remove subscription from the Observables, we have the chance of memory leaks in our application. So a bit of refactoring our subscriptions allows us to add them all together in an array of type Subscription.

  • subs: Array<Subscription> = []; array for subscriptions.
  • OnDestroy you must add the implementation for OnDestroy, so we can remove the subscriptions prior to removing the component (aka directive).

/src/app/modules/books/

 ngOnDestroy(): void { this.subs.forEach(sub => sub.unsubscribe()); }

Router Events Subscription

In our constructor we can now subscribe to events that happen on the Angular Router instance. We actually only care about the NavigationEnd, and also make sure that we are at the /books level. At this point we can properly add a true value to the showBooksAdd$ BehaviorSubject and the UI will react accordingly and update to show the button.

/src/app/modules/books/

constructor(private router: Router) {
    /* Only add Book Add Fab on /books */
    this.subs.push(this.router.events.subscribe(e => {
        if (e instanceof NavigationEnd && e.urlAfterRedirects === '/books') {
            this.showBooksAdd$.next(true);
        } else {
            this.showBooksAdd$.next(false);
        }
    }));
}

Hide FAB Button

Now that we are correctly passing the boolean (true/false) value to our BehaviorSubject we can observe this within the Angular UI and react. This is as simple as adding the NgIf directive. The easiest way to accomplish this is by usig the async pipe which will automatically subscribe and unsubscribe from our showBooksAdd$ observable.

  • *ngIf="(showBooksAdd$ | async)" allows for hiding or showing our button determined by the router position.

/src/app/modules/books/

<button
    mat-fab color="accent"
    class="app-fab--absolute"
    routerLink="/books/new"
    aria-label="Add"
    *ngIf="(showBooksAdd$ | async)"
>
    <mat-icon aria-label="Add Book">add</mat-icon>
</button>

Summary

When we are on the /books path we want to see the button.

books path

When we are on any other subpath of /books/* we want to hide the button, for example the chapter section.

chapter path