Search code examples
angularangular-routerangular-router-events

Hide element if named router-outlet has active route


We are currently developing an Angular 13 application that is split into several independent modules that are maintained by different teams and each module represents the UI for a different application that is backed by its own microservice. These modules are allowed to depend on our "SharedModule", but not the other way around.

Now, we want to implement a "Help Center" which consists of a question mark button that floats in the lower right corner and if clicked provides help for the currently loaded page. The thing is, that we want to keep the content of each modules Help Center pages inside that module, furthermore, some modules do not need Help Center pages.

Our current solution adds a component to our main layout:

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

<router-outlet></router-outlet>

<app-help></app-help>

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

The app-help component has said floating button,

<button mat-fab (click)="openHelpCenter()">
  <mat-icon>help</mat-icon>
</button>

that opens a Material dialog with a named router-outlet inside:

@Component({
  selector: 'app-help',
  templateUrl: './help.component.html',
  styleUrls: ['./help.component.scss']
})
export class HelpComponent {


  constructor(
    private dialog: MatDialog,
  ) {
  }

  openHelpCenter() {
    this.dialog.open(HelpDialogComponent, {
      width: '300px'
    })
  }
}

@Component({
  selector: 'app-help-dialog',
  template: '<router-outlet name="help"></router-outlet>'
})
export class HelpDialogComponent {

  constructor() {
  }
}

Inside each module the teams may now implement routes that use the named router-outlet:

...
{
        path: '',
        component: HelpComponent,
        outlet: 'help'
}
...

which "injects" the modules HelpComponent inside the Dialog according to the currently loaded url.

This works fairly well, however, we have an open problem:

How to we hide the floating button if the router-outlet is empty, i.e. if you are on a page that does not provide help?

I believe the easiest solution (i.e. the solution that manages stuff with minimal amount of code inside the modules) would be to listen to Router events inside HelpComponent, e.g. NavigationEnd, and then find out whether there is an "active" routing for the "help"-router-outlet and set visibility accordingly. (similar to the RouterLinkActive-Directive)

However, I am not able to find out how to retrieve this information from the Router-Framework.

Of course, I would also appreciate ideas on how to tackle the problem differently.


Solution

  • After reading through the Angular Router source, I stumbled upon ChildrenOutletContexts which seems to be a map of the currently "active" outlets.

    I adjusted my Component to get it injected and check it every time a navigation completes:

    import {
      ChildrenOutletContexts,
      NavigationEnd,
      Router
    } from "@angular/router";
    import {filter} from "rxjs";
    import {map} from "rxjs/operators";
    
    @Component({
      selector: 'app-help',
      templateUrl: './help.component.html',
      styleUrls: ['./help.component.scss']
    })
    export class HelpComponent {
      helpOutletActive$ = this.router.events.pipe(
        filter(event => event instanceof NavigationEnd),
        map(() => this.contexts.getContext('help') !== null)
      )
    
      constructor(
        private dialog: MatDialog,
        private router: Router,
        private contexts: ChildrenOutletContexts
      ) {
      }
    
      openHelpCenter() {
        this.dialog.open(HelpDialogComponent, {
          width: '300px'
        })
      }
    }
    

    According to my tests this seems to produce the desired result