File

src/app/components/menu-tree/menu-tree.component.ts

Description

Displays a menu overlay on smaller screens

Metadata

Index

Properties
Methods
Inputs
Accessors

Constructor

constructor(router: Router, scroller: ViewportScroller, overlay: Overlay)

Creates instance of Router, ViewportScroller and Overlay

Parameters :
Name Type Optional
router Router No
scroller ViewportScroller No
overlay Overlay No

Inputs

icon
Type : string
Default value : ''

Icon name for the menu button

overlayClass
Type : string
Default value : ''

Custom class name for the overlay

positions
Type : ConnectedPosition[]
Default value : []

Position details of the overlay

treeClass
Type : string
Default value : ''

Default class name for the menu

treeItems
Type : NavItems[]

Sets the menu items to the datasource

Methods

externalWindow
externalWindow(url: string)

Opens URL in an external window

Parameters :
Name Type Optional
url string No
Returns : void
scrollAfterDetach
scrollAfterDetach()

Scrolls to the id of the selected element

Returns : void
scrollTo
scrollTo(id: string)

Sets the scrollToId with the id of selected menu item

Parameters :
Name Type Optional
id string No
Returns : void

Properties

dataSource
Default value : new MatTreeNestedDataSource<NavItems>()

Data source for the menu tree

hasChild
Default value : () => {...}

Checks if current node has children

isOpen
Default value : false

Flag to check if menu is open

scrollStrategy
Default value : this.overlay.scrollStrategies.block()

Scroll strategy for the overlay

Optional scrollToId
Type : string

Id of the element on page to be scrolled to

treeControl
Default value : new NestedTreeControl<NavItems>((node) => node.children)

Tree Controller

Accessors

treeItems
settreeItems(items: NavItems[])

Sets the menu items to the datasource

Parameters :
Name Type Optional
items NavItems[] No
Returns : void
import { ConnectedPosition, Overlay } from '@angular/cdk/overlay';
import { NestedTreeControl } from '@angular/cdk/tree';
import { ViewportScroller } from '@angular/common';
import { Component, Input } from '@angular/core';
import { MatTreeNestedDataSource } from '@angular/material/tree';
import { Router } from '@angular/router';
import { NavItems } from '../toolbar/nav-items';

/** Displays a menu overlay on smaller screens */
@Component({
  selector: 'menu-tree',
  templateUrl: './menu-tree.component.html',
  styleUrls: ['./menu-tree.component.scss'],
})
export class MenuTreeComponent {
  /** Sets the menu items to the datasource */
  @Input() set treeItems(items: NavItems[]) {
    this.dataSource.data = items;
  }

  /** Icon name for the menu button */
  @Input() icon = '';

  /** Custom class name for the overlay */
  @Input() overlayClass = '';

  /** Default class name for the menu */
  @Input() treeClass = '';

  /** Position details of the overlay */
  @Input() positions: ConnectedPosition[] = [];

  /** Flag to check if menu is open */
  isOpen = false;

  /** Id of the element on page to be scrolled to */
  scrollToId?: string;

  /** Tree Controller */
  treeControl = new NestedTreeControl<NavItems>((node) => node.children);

  /** Data source for the menu tree */
  dataSource = new MatTreeNestedDataSource<NavItems>();

  /** Scroll strategy for the overlay */
  scrollStrategy = this.overlay.scrollStrategies.block();

  /** Creates instance of Router, ViewportScroller and Overlay */
  constructor(
    private readonly router: Router,
    private readonly scroller: ViewportScroller,
    private readonly overlay: Overlay,
  ) {}

  /** Checks if current node has children */
  hasChild = (_: number, node: NavItems) => !!node.children && node.children.length > 0;

  /** Opens URL in an external window */
  externalWindow(url: string): void {
    window.open(url, '_blank');
  }

  /** Sets the scrollToId with the id of selected menu item */
  scrollTo(id: string): void {
    this.router.navigate([], { fragment: id });
    this.scrollToId = id;
  }

  /** Scrolls to the id of the selected element */
  scrollAfterDetach(): void {
    if (this.scrollToId) {
      this.scroller.scrollToAnchor(this.scrollToId);
      this.scrollToId = undefined;
    }
  }
}
<button
  mat-icon-button
  (click)="this.isOpen = !this.isOpen"
  cdkOverlayOrigin
  #trigger="cdkOverlayOrigin"
  class="menu-tree-button"
  [disableRipple]="true"
>
  <mat-icon *ngIf="icon; else default" class="menu-icon">{{ icon }}</mat-icon>
  <ng-template #default>
    <mat-icon>{{ isOpen ? 'menu' : 'menu' }}</mat-icon>
  </ng-template>
</button>

<ng-template
  cdkConnectedOverlay
  [cdkConnectedOverlayOrigin]="trigger"
  [cdkConnectedOverlayOpen]="isOpen"
  [cdkConnectedOverlayHasBackdrop]="true"
  [cdkConnectedOverlayBackdropClass]="$any(['cdk-overlay-dark-backdrop', 'menu-tree-backdrop'])"
  (detach)="isOpen = false; scrollAfterDetach()"
  [cdkConnectedOverlayPanelClass]="['menu-tree-panel', overlayClass]"
  [cdkConnectedOverlayPositions]="positions"
  [cdkConnectedOverlayScrollStrategy]="scrollStrategy"
  (overlayOutsideClick)="!trigger.elementRef.nativeElement.contains($event.target) && (isOpen = false)"
>
  <mat-tree cdkScrollable [dataSource]="dataSource" [treeControl]="treeControl" [class]="treeClass">
    <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle (click)="this.isOpen = !this.isOpen">
      <div *ngIf="node.route" [routerLink]="[node.route]" class="menu-name">
        {{ node.menuName }}
      </div>
      <mat-divider *ngIf="node.divider" class="menu-tree-divider"></mat-divider>
      <div (click)="externalWindow(node.url!)" *ngIf="node.url" class="menu-name">
        {{ node.menuName }}
      </div>
      <div *ngIf="node.id" (click)="scrollTo(node.id)" class="table-of-contents">
        {{ node.menuName }}
      </div>
      <mat-divider *ngIf="node.id"></mat-divider>
    </mat-tree-node>
    <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
      <div class="mat-parent-node" matTreeNodeToggle [attr.aria-label]="'Toggle ' + node.menuName">
        {{ node.menuName }}
        <button mat-icon-button>
          <mat-icon [class.inverse]="treeControl.isExpanded(node)">arrow_drop_down</mat-icon>
        </button>
      </div>
      <div [class.example-tree-invisible]="!treeControl.isExpanded(node)" role="group">
        <ng-container matTreeNodeOutlet></ng-container>
      </div>
    </mat-nested-tree-node>
  </mat-tree>
</ng-template>

./menu-tree.component.scss

:host {
  --mat-icon-color: white;
  .menu-tree-button {
    background-color: #444c65;
    z-index: 2;
  }
}

::ng-deep {
  .example-tree-invisible {
    display: none;
  }

  .mat-tree {
    width: 26rem;
    border: 1px solid #e0e0e0;
    border-radius: 0px 0px 8px 8px;
    height: auto;
    overflow-y: auto;
  }

  .inverse {
    transform: rotate(180deg);
    display: inline-block;
  }

  .menu-tree-backdrop {
    margin-top: 4rem;
  }
}

.mat-parent-node {
  display: flex;
  align-items: center;
}

.mat-tree-node,
.mat-parent-node {
  font-size: 1rem;
  color: #444c65;
  font-weight: 300;
  line-height: 1.5rem;
}

.mat-tree-node {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}

.mat-nested-tree-node:not([aria-expanded='true']):hover,
.mat-tree > .mat-tree-node:hover {
  background: rgba(0, 0, 0, 0.04);
}

.mat-tree-node {
  z-index: 1;
}

.mat-tree-node[aria-level='2']:hover::after {
  position: absolute;
  content: '';
  inset: 0 -2rem;
  z-index: -1;
  background: rgba(0, 0, 0, 0.04);
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""