How do you keep a navigation component synchronized with a non-trivial application? For example, in Angular 2's simple Tour of Heroes application, the app.component.ts
has static (i.e. unchanging) declarative navigation:
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
But in more complex applications, the navigation component is often dynamic. In my case the nav
has a sidenav, a back button, component-specific actions, and user information; and, the sidenav and actions change depending on the role of the user. This is a fairly typical scenario -- think of Google Gmail, Inbox, Calendar, Docs, etc.
Right now, my nav
is in a single my-header
component implemented with lots of *ngIf
testing for the current component and role of user -- it's an ugly nightmare:
<md-toolbar color="primary" class="fixed-toolbar">
<div *ngIf="isAuthenticated()">
<md-menu #mainmenu="mdMenu">
<a md-menu-item routerLink="/marketplaces" routerLinkActive="active">Marketplaces</a>
<a md-menu-item routerLink="/people" routerLinkActive="active">People</a>
<span *ngIf="isAdministrator()">
<md-divider></md-divider>
<a md-menu-item routerLink="/accounts" routerLinkActive="active">Accounts</a>
</span>
</md-menu>
<button *ngIf="_navigation?.toolbar == 'default'" md-icon-button [md-menu-trigger-for]="mainmenu">
<md-icon>menu</md-icon>
</button>
<button *ngIf="_navigation?.toolbar == 'market'" md-icon-button
(click)="closeAndReturn($event, 'marketplaces')"
aria-label="Back to Marketplaces">
<md-icon>close</md-icon>
</button>
<button *ngIf="_navigation?.toolbar == 'person'" md-icon-button
(click)="closeAndReturn($event, 'users')"
aria-label="Back to People">
<md-icon>close</md-icon>
</button>
</div>
<!-- ^^^ nightmare continues vvv -->
The my-header
component is simply included in the app.component.html
:
<my-header></my-header>
<router-outlet></router-outlet>
<my-footer></my-footer>
But neither the my-header
component nor the my-footer
component are in the router tree so my-header
's injected ActivatedRoute
is always the root node which means I can't even transition absolute back navigation:
(click)="closeAndReturn($event, 'marketplaces')"
to relative navigation:
(click)="closeAndReturn($event, '../')"
which means duplicating the router tree in this one component to stay synchronized -- this is obviously not the Right Thing to do...
Any ideas on how do solve this, presumably super-common, challenge of keeping a navigation component in sync with a complex application?