I'm trying to make a nested menu with angular routes.
What I need is to apply class to a nested route if it's active and apply class to a parent component if its child is active.
How do I achieve this? For now I'm doing recursive menu building for ease of use when I need multilevel nesting.
component.html
<a (click)="toggleActive()" [class.active]="isActive" [routerLink]="menuItem.link" *ngIf="menuItem.link; else noLink">
<i *ngIf="menuItem.faClass" class="fa fa-{{menuItem.faClass}}"></i>
{{menuItem.name}} <span *ngIf="menuItem.children && menuItem.children.length > 0" class="fa fa-chevron-down"></span>
</a>
<ng-container *ngIf="menuItem.children && menuItem.children.length > 0">
<ul class="nav child_menu" [class.active]="isActive" routerLinkActive="active"
*ngFor="let item of menuItem.children">
<li menu-item [menuItem]="item" (checkActive)="updateActiveState()"></li>
</ul>
</ng-container>
<ng-template #noLink>
<a (click)="toggleActive()" [class.active]="isActive">
<i *ngIf="menuItem.faClass" class="fa fa-{{menuItem.faClass}}"></i>
{{menuItem.name}} <span *ngIf="menuItem.children && menuItem.children.length > 0" class="fa fa-chevron-down"></span>
</a>
</ng-template>
component.ts
@Component({
selector: '[menu-item]',
templateUrl: './menu-item.component.html',
styleUrls: ['./menu-item.component.scss'],
host: {
'[class.active]': 'hostActive && isActive',
'[class.active-sm]': 'hostActiveSm && isActive'
}
})
export class MenuItemComponent implements OnInit, AfterViewInit, OnChanges {
public menuSize;
public isActive = false;
@Input() menuItem: MenuItem;
@Output() checkActive: EventEmitter<void> = new EventEmitter<void>();
@ViewChild(MenuItemComponent) menuComponent: MenuItemComponent;
private hostActive = true;
private hostActiveSm = false;
@HostBinding('class') hostClass = this.hostActive && this.isActive ? 'active' : this.hostActiveSm && this.isActive ? 'active-sm' : '';
constructor(
private router: Router,
private uss: UiStateService,
) {
}
ngOnInit() {
this.uss.menuSubject.subscribe(msg => {
this.menuSize = msg;
if (this.menuSize === MenuSizes.sm) {
this.hostActive = false;
this.hostActiveSm = true;
} else {
this.hostActive = true;
this.hostActiveSm = false;
}
this.updateActiveState();
});
this.router.events.subscribe((e: Event) => {
if (e instanceof NavigationEnd) {
this.updateActiveState();
}
});
}
ngOnChanges() {
}
ngAfterViewInit() {
this.updateActiveState();
}
public toggleActive(): void {
this.isActive = !this.isActive;
// if (this.menuComponent) {
// this.menuComponent.isActive = true;
// }
}
private updateActiveState(): void {
// reset previous state
this.isActive = false;
if (this.menuComponent) {
this.menuComponent.isActive = false;
}
// Check state of item with no els
const url = this.router.url;
console.log('URL', url, 'Menu link', this.menuItem.link);
console.log(url.match('/' + this.menuItem.link + '$/'));
if (this.menuItem && this.menuItem.link && url.match('/' + this.menuItem.link + '$/')) {
this.isActive = true;
this.checkActive.emit();
}
if (this.menuComponent) {
console.log('Menu component');
console.log(this.menuComponent, this.menuComponent.menuItem.link);
this.isActive = true;
}
}
}
Is there a way of knowing from a component whether its route is active? The trick is that I'm using [routerLink] and not a routerLink so that I can pass . as a link to the root page.
Update
The best I could recreate stackblitz
Try to visit "Dasboard". It should add an "active" class to parent li. If you add it yourself you would see the applied class on it's element and a colored bar on the right.