src/lib/navigation/navigation-item/navigation-item.component.ts
OnChanges
OnDestroy
AfterViewInit
| changeDetection | ChangeDetectionStrategy.OnPush |
| encapsulation | ViewEncapsulation.None |
| selector | li[rxap-navigation-item] |
| imports |
RouterLinkActive
RouterLink
NgIf
MatRippleModule
MatIconModule
IconDirective
MatDividerModule
forwardRef(() => NavigationComponent)
NgClass
|
| styleUrls | ./navigation-item.component.scss |
| templateUrl | ./navigation-item.component.html |
Properties |
|
Methods |
|
Inputs |
| item | |
| Required : true | |
| level | |
Default value : 0
|
|
| Public asNavigationItem | ||||||
asNavigationItem(item: NavigationItem | NavigationDividerItem)
|
||||||
|
Parameters :
Returns :
NavigationItem
|
| Public isNavigationDividerItem | ||||||
isNavigationDividerItem(item: NavigationItem | NavigationDividerItem)
|
||||||
|
Parameters :
Returns :
NavigationDividerItem
|
| Public isNavigationItem | ||||||
isNavigationItem(item: NavigationItem | NavigationDividerItem)
|
||||||
|
Parameters :
Returns :
NavigationItem
|
| onClick |
onClick()
|
|
Returns :
void
|
| Protected Readonly _subscription |
Default value : new Subscription()
|
| Public Readonly active |
Default value : signal(false)
|
| Public children |
Type : Navigation | null
|
Default value : null
|
| Public Readonly collapsed |
Default value : computed(() => this.layoutService.collapsed())
|
| Protected Readonly elementRef |
Default value : inject(ElementRef)
|
| Public Readonly item |
| Public Readonly itemClasses |
Default value : computed(() => {
let classes = `level-${ this.level() * 4 }`;
if (this.collapsed()) {
classes += ' invisible';
}
return classes;
})
|
| Protected Readonly layoutService |
Default value : inject(LayoutService)
|
| Public Readonly level |
Default value : 0
|
| Protected Readonly renderer |
Default value : inject(Renderer2)
|
| Protected Readonly router |
Default value : inject(Router)
|
import {
animate,
style,
transition,
trigger,
} from '@angular/animations';
import {
NgClass,
NgIf,
} from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
ElementRef,
forwardRef,
inject,
input,
OnChanges,
OnDestroy,
Renderer2,
signal,
SimpleChanges,
viewChild,
ViewEncapsulation, AfterViewInit,
} from '@angular/core';
import { MatRippleModule } from '@angular/material/core';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import {
NavigationEnd,
Router,
RouterLink,
RouterLinkActive,
} from '@angular/router';
import { IconDirective } from '@rxap/material-directives/icon';
import { coerceArray } from '@rxap/utilities';
import {
debounceTime,
Subscription,
} from 'rxjs';
import {
filter,
startWith,
tap,
} from 'rxjs/operators';
import { LayoutService } from '../../layout.service';
import {
Navigation,
NavigationDividerItem,
NavigationItem,
} from '../navigation-item';
import { NavigationComponent } from '../navigation.component';
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'li[rxap-navigation-item]',
templateUrl: './navigation-item.component.html',
styleUrls: ['./navigation-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
animations: [
trigger('sub-nav', [
transition(':enter', [
style({
display: 'block',
height: '0',
overflow: 'hidden',
}),
animate(150, style({ height: '*' })),
]),
transition(':leave', [
style({ overflow: 'hidden' }),
animate(300, style({ height: '0' })),
style({ display: 'none' }),
]),
]),
],
imports: [
RouterLinkActive,
RouterLink,
NgIf,
MatRippleModule,
MatIconModule,
IconDirective,
MatDividerModule,
forwardRef(() => NavigationComponent),
NgClass,
]
})
export class NavigationItemComponent
implements OnChanges, OnDestroy, AfterViewInit {
public readonly level = input(0);
public children: Navigation | null = null;
public readonly item = input.required<NavigationItem>();
public readonly active = signal(false);
public readonly itemClasses = computed(() => {
let classes = `level-${ this.level() * 4 }`;
if (this.collapsed()) {
classes += ' invisible';
}
return classes;
});
protected readonly layoutService = inject(LayoutService);
public readonly collapsed = computed(() => this.layoutService.collapsed());
protected readonly _subscription = new Subscription();
protected readonly router = inject(Router);
protected readonly elementRef = inject(ElementRef);
protected readonly renderer = inject(Renderer2);
onClick() {
if (this.layoutService.isMobile() || this.layoutService.collapsed()) {
this.layoutService.opened.set(false);
}
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['item']) {
const item: NavigationItem = changes['item'].currentValue;
this.children =
item.children && item.children.length ? item.children : null;
}
}
public ngAfterViewInit() {
this._subscription.add(
this.router.events
.pipe(
filter((event) => event instanceof NavigationEnd),
debounceTime(100),
startWith(true),
tap(() => {
let isActive = true;
const urlParts = this.router.url.split('/');
if (urlParts[0] === '') {
urlParts[0] = '/';
}
const routerLink = coerceArray(this.item().routerLink).map(fragment => fragment === '/' ? ['/'] : fragment.split('/')).flat();
if (routerLink[0] === '') {
routerLink[0] = '/';
}
for (let i = 0; i < routerLink.length; i++) {
if (urlParts[i] !== routerLink[i]) {
isActive = false;
break;
}
}
this.active.set(isActive);
if (isActive) {
this.renderer.addClass(this.elementRef.nativeElement, 'active');
} else {
this.renderer.removeClass(this.elementRef.nativeElement, 'active');
}
}),
)
.subscribe(),
);
}
public ngOnDestroy() {
this._subscription?.unsubscribe();
}
// region type save item property
// required to check the type of the item property in the ngFor loop
public isNavigationDividerItem(
item: NavigationItem | NavigationDividerItem,
): item is NavigationDividerItem {
return (item as any)['divider'];
}
public isNavigationItem(
item: NavigationItem | NavigationDividerItem,
): item is NavigationItem {
return !this.isNavigationDividerItem(item);
}
public asNavigationItem(
item: NavigationItem | NavigationDividerItem,
): NavigationItem {
if (!this.isNavigationItem(item)) {
throw new Error('The item is not a NavigationItem');
}
return item;
}
// endregion
}
<div [ngClass]="{
'border-l-4 text-accent-400 border-accent-600': active(),
}">
<a [routerLink]="item().routerLink"
class="h-12 pl-4 pr-5 flex flex-row justify-between items-center gap-x-4 text-black"
matRipple
routerLinkActive
(click)="onClick()"
>
<span
[ngClass]="itemClasses()"
class="grow whitespace-nowrap"
>
{{ item().label }}
</span>
@if (item().icon; as icon) {
<mat-icon [rxapIcon]="icon"></mat-icon>
}
</a>
@if (item().children?.length && active()) {
<mat-divider></mat-divider>
<ul [@sub-nav]
[items]="children ?? []"
[level]="level() + 1"
rxap-navigation
>
</ul>
<mat-divider></mat-divider>
}
</div>
./navigation-item.component.scss