Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,194 changes: 636 additions & 558 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 0 additions & 38 deletions projects/ngx-material-navigation-showcase/.eslintrc.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div style="max-width: fit-content; margin-left: auto; margin-right: auto;">
<h1>Blog Post 42</h1>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-blog-post',
templateUrl: './blog-post.component.html'
})
export class BlogPostComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div style="max-width: fit-content; margin-left: auto; margin-right: auto;">
<h1>Blog</h1>
<a routerLink="./42">Zu Blogeintrag 42</a>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
import { RouterLinkWithHref } from '@angular/router';

@Component({
selector: 'app-blog',
templateUrl: './blog.component.html',
imports: [RouterLinkWithHref]
})
export class BlogComponent {}
24 changes: 23 additions & 1 deletion projects/ngx-material-navigation-showcase/src/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ export const navbarRows: NavbarRow<NavRoute>[] = [
collapse: 'md',
position: 'center'
},
{
type: NavElementTypes.INTERNAL_LINK,
name: 'Blog',
route: {
title: 'Blog',
path: 'blog',
loadComponent: () => import('./components/blog/blog.component').then(m => m.BlogComponent)
}
},
{
type: NavElementTypes.BUTTON,
name: 'Reload Page',
Expand Down Expand Up @@ -176,7 +185,20 @@ const extraRoute: NavRoute = {
}
};
// Extract the angular routes from the given configuration. This can be used in the app.routing.module.ts
export const routes: NavRoute[] = NavUtilities.getAngularRoutes(navbarRows, footerRows, [extraRoute]);
export const routes: NavRoute[] = NavUtilities.getAngularRoutes(navbarRows, footerRows, [
{
title: (snapshot) => `Blog Post ${snapshot.params['id']}`,
path: 'blog/:id',
loadComponent: () => import('./components/blog-post/blog-post.component').then(m => m.BlogPostComponent),
data: {
breadcrumbConfig: {
name: (snapshot) => `Blog Post ${snapshot.params['id']}`,
parentRoute: 'blog'
}
}
},
extraRoute
]);

function conditionWithInjection(): boolean {
const router: Router = inject(Router);
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-material-navigation/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-material-navigation",
"version": "20.1.2",
"version": "20.1.3",
"license": "MIT",
"repository": {
"url": "https://github.com/Service-Soft/ngx-material-navigation"
Expand Down
10 changes: 7 additions & 3 deletions projects/ngx-material-navigation/src/models/nav-route.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Data, ResolveFn, Route } from '@angular/router';
import { ActivatedRouteSnapshot, Data, ResolveFn, Route, RouterStateSnapshot } from '@angular/router';
import { IconDefinition } from '@fortawesome/angular-fontawesome';

import { PageNotFoundConfig } from './page-not-found-config.model';
Expand Down Expand Up @@ -37,15 +37,19 @@ export interface DefaultNavRouteDataType extends Data {
/**
* The name of the breadcrumb.
*/
name?: string,
name?: string | ((snapshot: ActivatedRouteSnapshot, stateSnapshot: RouterStateSnapshot) => string),
/**
* An optional icon used in the breadcrumb.
*/
icon?: IconDefinition,
/**
* An optional aria label used in the breadcrumb.
*/
ariaLabel?: string
ariaLabel?: string,
/**
* The route of the parent of this route.
*/
parentRoute?: string
}
}

Expand Down
77 changes: 62 additions & 15 deletions projects/ngx-material-navigation/src/services/nav.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { EnvironmentInjector, inject, Injectable, runInInjectionContext } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';

import { Breadcrumb } from '../models/breadcrumb.model';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class NgxMatNavigationService {
return this.breadcrumbsSubject.value;
}

constructor(private readonly router: Router) {
constructor(private readonly router: Router, private readonly injector: EnvironmentInjector) {
this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
this.updateAnchors();
Expand All @@ -80,26 +80,73 @@ export class NgxMatNavigationService {
}

const breadcrumbs: Breadcrumb[] = [];
for (const r of route.snapshot.pathFromRoot) {
const data: DefaultNavRouteDataType = r.data;
for (const snapshot of route.snapshot.pathFromRoot) {
const data: DefaultNavRouteDataType = snapshot.data;
if (data.pageNotFoundConfig) {
this.breadcrumbsSubject.next([]);
return;
}

breadcrumbs.push({
name: data.breadcrumbConfig?.name ?? r.title ?? 'Start',
route: r.url.map(segment => segment.path).join('/'),
name: this.resolveBreadcrumbName(data, undefined, snapshot),
route: snapshot.url.map(segment => segment.path).join('/'),
icon: data.breadcrumbConfig?.icon,
ariaLabel: data.breadcrumbConfig?.ariaLabel
});
if (data.pageNotFoundConfig) {
this.breadcrumbsSubject.next([]);
return;

if (data.breadcrumbConfig?.parentRoute && !breadcrumbs.find(b => b.route === data.breadcrumbConfig?.parentRoute)) {
const parentRoute: Route | undefined = this.router.config.find(r => r.path === data.breadcrumbConfig?.parentRoute);

if (!parentRoute) {
throw new Error('parent route could not be resolved');
}

const parentData: DefaultNavRouteDataType | undefined = parentRoute.data;
breadcrumbs.splice(breadcrumbs.length - 1, 0, {
name: this.resolveBreadcrumbName(parentData, parentRoute, undefined),
route: data.breadcrumbConfig?.parentRoute,
icon: parentData?.breadcrumbConfig?.icon,
ariaLabel: parentData?.breadcrumbConfig?.ariaLabel
});
}
}

const [first, second] = [...breadcrumbs];
if (breadcrumbs.length === 2 && first.route === '' && second.route === '') {
this.breadcrumbsSubject.next([]);
return;
this.breadcrumbsSubject.next(breadcrumbs);
}

private resolveBreadcrumbName(
navData: DefaultNavRouteDataType | undefined,
route: Route | undefined,
snapshot: ActivatedRouteSnapshot | undefined
): string {
const res: string | undefined = runInInjectionContext(this.injector, () => {
if (navData?.breadcrumbConfig?.name == undefined) {
return undefined;
}
if (typeof navData.breadcrumbConfig.name === 'string') {
return navData.breadcrumbConfig.name;
}

const route: ActivatedRoute = inject(ActivatedRoute);
return navData.breadcrumbConfig.name(snapshot ?? route.snapshot, this.router.routerState.snapshot);
});
if (res) {
return res;
}

this.breadcrumbsSubject.next(breadcrumbs);
if (snapshot) {
return snapshot.title ?? 'Start';
}

if (route) {
if (route.title == undefined) {
return 'Start';
}
if (typeof route.title === 'string') {
return route.title;
}
}

return 'Start';
}
}