Greedy navigation or Priority navigation or prio navigation is a handy way to display responsive navigation to your user. Bit how to build it using Angular Material Design? Well in this example you build your own greedy nav component. Let's take a look.
So the basic idea of prio nav or greedy nav is to calculate how much space complete navigation elements is taking, and then calculate how much space we currently have. And if the total needed space for all navigations elements exceeds available space then move elements from navigation to drop-down navigation until we have enough space again.
Completed working example of greedy navigation / priority navigation by using Material Design with Angular 7 - you can find on stackblitz: https://stackblitz.com/edit/angular-udcj4c
So first thing first, import Angular Material and flex layout to your app.module.ts file:
So the basic idea of prio nav or greedy nav is to calculate how much space complete navigation elements is taking, and then calculate how much space we currently have. And if the total needed space for all navigations elements exceeds available space then move elements from navigation to drop-down navigation until we have enough space again.
Completed working example of greedy navigation / priority navigation by using Material Design with Angular 7 - you can find on stackblitz: https://stackblitz.com/edit/angular-udcj4c
So first thing first, import Angular Material and flex layout to your app.module.ts file:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import {FlexLayoutModule} from '@angular/flex-layout'; import { AppComponent } from './app.component'; import { MainNavComponent } from './Components/main-nav/main-nav.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatToolbarModule, MatSidenavModule, MatCardModule, MatMenuModule, MatButtonModule } from '@angular/material'; @NgModule({ imports: [ BrowserModule, FormsModule, MatButtonModule, MatToolbarModule, MatSidenavModule, MatCardModule, MatMenuModule, MatButtonModule, BrowserAnimationsModule ], declarations: [ AppComponent,MainNavComponent ], providers: [MainNavComponent], bootstrap: [AppComponent] }) export class AppModule { }
Ok now we will import main navigation component into app.component.html:
<div fxFlex fxLayout="column" fxFlex="100"> <app-main-nav fxFlex fxLayout="column" fxFlex="100" class="mat-elevation-z6"></app-main-nav> </div>
Since main navigation component is not created let's make it. First let's set up HTML part. Inside of app folder create folder Components and inside of components folder create main-nav folder. In this folder create these 3 files:
main-nav.component.html
main-nav.component.scss
main-nav.component.ts
Lets put greedy navigation html structure. Put this HTML code in main-nav.component.htm
main-nav.component.html
main-nav.component.scss
main-nav.component.ts
Lets put greedy navigation html structure. Put this HTML code in main-nav.component.htm
<div fxFlex fxLayout="column" fxFlex="100"> <mat-sidenav-container class="sidenav-container" style="height:100%;"> <mat-toolbar class="toolbar-top" color="primary" id="header-toolbar"> <div id="toolbar-left-section" style="display: flex;flex: 0 0;"> ☀ </div> <span fxFlex style="width:50px;"></span> <div id="prio-nav" style="display: flex;flex: 1;justify-content: center;"> <button *ngFor="let menu of mainMenu; let i = index" type="button" aria-label="Toggle sidenav" mat-button > {{menu.title}} </button> </div> <div id="drop-down-items" *ngIf="showDropDownPrio"> <button mat-button [matMenuTriggerFor]="menu" [matMenuTriggerData]="hiddenPrioMenu"> ▾ </button> <mat-menu #menu="matMenu" [innerHtml]="hiddenMenuList" id="hidden-prio-nav"> <ng-template matMenuContent> <button *ngFor="let menu of hiddenPrioMenu; let i = index" type="button" aria-label="Toggle sidenav" mat-button > {{menu.title}} </button> </ng-template> </mat-menu> </div> <span fxFlex style="width:50px;"></span> <div id="toolbar-right-section" style="display: flex; flex: 0 0; justify-content: flex-end;"> ☀ </div> </mat-toolbar> </mat-sidenav-container> </div>
As you can see our parent navigation container, with id "header-toolbar", contains 3 elements: toolbar-left-section, prio-nav, toolbar-right-section. We will calculate needed space for all of these 3 elements, then calculate how much space is needed for header-toolbar and if prio-nav + side elements are exceeding header toolbar width then move some elements from prio nav to new drop down list and display that drop down list.
To achieve this goal, we will take widths of all elements, create array of widths breaking points based on prio-nav each elements width. Then on resize, or on page load, we will make needed calculations.
So in our main-nav.component.ts we will first add object which will hold our navigation items and set global vars:
To achieve this goal, we will take widths of all elements, create array of widths breaking points based on prio-nav each elements width. Then on resize, or on page load, we will make needed calculations.
So in our main-nav.component.ts we will first add object which will hold our navigation items and set global vars:
mainMenu = [ {title:'Home'}, {title:'Sport'}, {title:'News'}, {title:'Politics'}, {title:'Mixed Martial Arts'} ]; hiddenPrioMenu = []; showDropDownPrio = false; breakWidths = [];
next, we will use Angular method ngAfterViewInit to get the needed witdhs of prio-nav elements:
ngAfterViewInit(){ // get prio nav elemenz var prioNav = document.getElementById('prio-nav'); var totalSpace = 0; var breakWidths = []; var numOfItems = 0; // loop over all elements and set sum of widths for each prio nav element for (var i = 0; i < prioNav.children.length; i++) { totalSpace += prioNav.children[i].clientWidth; this.breakWidths.push(totalSpace); numOfItems += 1; } // call calculation method this.checkCalculcation(); }
And also on resize call checkCalcucation method:
onResize(event) { // call calculation method this.checkCalculcation(); }
NOTE: to call this onReize method we need to include window resize event to our component:
@Component({ selector: 'app-main-nav', templateUrl: './main-nav.component.html', styleUrls: ['./main-nav.component.scss'], // call onResize() method when browser window is resized host: { '(window:resize)': 'onResize($event)' } })
And finally let's create our main calcualtion method:
checkCalculcation(){ // get current space of parent element of prio-nav var availableSpace = document.getElementById('header-toolbar').offsetWidth; // get needed space for all elements in prio-nav parent element + last width of visible elements from breakwidth array var totalNeededSpace = (document.getElementById('toolbar-right-section').offsetWidth)+ (document.getElementById('toolbar-left-section').offsetWidth) + this.breakWidths[this.mainMenu.length - 1]+ 200; // if we need more space than we have - hide last element if(totalNeededSpace > availableSpace){ // push last elemen from mainMenu to hiddenPrioMenu this.hiddenPrioMenu.push(this.mainMenu[this.mainMenu.length-1]); // remove that last element from mainMenu this.mainMenu = this.mainMenu.filter(item => item !== this.mainMenu[this.mainMenu.length-1]); // show hidden prio menu this.showDropDownPrio = true; // apply changes this.cdr.detectChanges(); // call this method to recalculate for rest of nav elements this.checkCalculcation(); }else{ // if we have elements in hidden prio menu if(this.hiddenPrioMenu.length>0){ // push last element from hidden menu to main menu this.mainMenu.push(this.hiddenPrioMenu[this.hiddenPrioMenu.length-1]); // remove element from hidden menu this.hiddenPrioMenu = this.hiddenPrioMenu.filter(item => item !== this.hiddenPrioMenu[this.hiddenPrioMenu.length-1]); // if we have or not elements in hiddenPrioMenu hide it or show it if(this.hiddenPrioMenu.length>0){ this.showDropDownPrio = true; }else{ this.showDropDownPrio = false; } } // apply changes this.cdr.detectChanges(); } }
Full working example you can find on https://stackblitz.com/edit/angular-udcj4c