Skip to content

Commit

Permalink
feat(menu): add fullPath and fullUrl to config
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Malkevich committed Feb 27, 2019
1 parent 9c59d59 commit 3dc5f31
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<ndm-dynamic-menu-items></ndm-dynamic-menu-items>
</ul>
<li *ndmDynamicMenuItem="let config; let item = item">
<a [routerLink]="config.fullUrl">{{ item.label }}</a>
<a [routerLink]="config.fullUrl" routerLinkActive="active">{{
item.label
}}</a>
<ndm-dynamic-menu-items></ndm-dynamic-menu-items>
</li>
<li *ndmDynamicMenuToggle="let item = item; let ctx = context">
Expand Down
89 changes: 80 additions & 9 deletions projects/dynamic-menu/src/lib/dynamic-menu.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { Injectable, Injector } from '@angular/core';
import { Route, RouteConfigLoadEnd, Router } from '@angular/router';
import { EMPTY, zip } from 'rxjs';
import {
ActivatedRoute,
NavigationEnd,
Route,
RouteConfigLoadEnd,
Router,
Params,
} from '@angular/router';
import { combineLatest, EMPTY, zip } from 'rxjs';
import {
delay,
filter,
map,
publishBehavior,
refCount,
startWith,
tap,
} from 'rxjs/operators';

import { DynamicMenuExtrasToken } from './dynamic-menu-extras';
Expand All @@ -28,6 +36,10 @@ export class DynamicMenuService {
? this.router.events.pipe(filter(e => e instanceof RouteConfigLoadEnd))
: EMPTY;

private navigationEnd$ = this.router.events.pipe(
filter(e => e instanceof NavigationEnd),
);

private dynamicMenuRoutes$ = this.configChanged$.pipe(
startWith(null),
map(() => this.getDynamicMenuRoutes()),
Expand All @@ -38,9 +50,18 @@ export class DynamicMenuService {
map(() => this.getSubMenuMap()),
);

private dynamicMenu$ = zip(this.dynamicMenuRoutes$, this.subMenuMap$).pipe(
private basicMenu$ = zip(this.dynamicMenuRoutes$, this.subMenuMap$).pipe(
delay(0),
map(([routes, subMenuMap]) => this.buildFullUrlTree(routes, subMenuMap)),
);

private dynamicMenu$ = combineLatest(
this.basicMenu$,
this.navigationEnd$.pipe(startWith(null)),
).pipe(
map(([basicMenu]) =>
this.updateFullPaths(basicMenu, this.router.routerState.root),
),
publishBehavior([] as DynamicMenuRouteConfig[]),
refCount(),
);
Expand Down Expand Up @@ -98,13 +119,60 @@ export class DynamicMenuService {
return config.path === '**' || !!config.redirectTo;
}

private updateFullPaths(
menu: DynamicMenuRouteConfig[],
route: ActivatedRoute | null,
): DynamicMenuRouteConfig[] {
return menu.map(m => {
return {
...m,
fullUrl: this.applyParams(m.fullPath, route),
data: {
...m.data,
menu: {
...m.data.menu,
children: this.updateFullPaths(
m.data.menu.children,
route && route.firstChild,
),
},
},
};
});
}

private applyParams(path: string[], route: ActivatedRoute | null): string[] {
if (!route || !/:/.test(path.join(''))) {
return path;
}

const params = this.collectParamsFrom(route);

return path.map(p =>
Object.keys(params).reduce((acc, param) => {
return acc.replace(`:${param}`, params[param]);
}, p),
);
}

private collectParamsFrom(route: ActivatedRoute): Params {
let params = route.snapshot.params;

// Look in first child in case we have params in root but they are in child
if (route.firstChild) {
params = { ...params, ...route.firstChild.snapshot.params };
}

return params;
}

private buildFullUrlTree(
node: RoutesWithMenu,
subMenuMap: SubMenuMap[],
): DynamicMenuRouteConfig[] {
return this.buildUrlTree(node, (config, parentConfig) => {
const path = parentConfig
? parentConfig.fullUrl || [parentConfig.path]
? parentConfig.fullPath || [parentConfig.path]
: [];

if (config.data && config.data.menu) {
Expand All @@ -114,11 +182,14 @@ export class DynamicMenuService {
);
}

return {
...config,
// tslint:disable-next-line: no-non-null-assertion
fullUrl: [...path, config.path!].filter(p => p != null),
};
// tslint:disable-next-line: no-non-null-assertion
const fullPath: string[] = [...path, config.path!].filter(p => p != null);

// Setting to `pathUrl` for now.
// Will be updated later after navigation.
const fullUrl: string[] = fullPath;

return { ...config, fullPath, fullUrl };
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('DynamicMenuItemsComponent', () => {
tpl,
{},
{
fullPath: 'full-path' as any,
fullUrl: 'full-url' as any,
data: {
menu: {
Expand Down Expand Up @@ -134,6 +135,7 @@ describe('DynamicMenuItemsComponent', () => {
tpl,
{},
{
fullPath: 'full-path' as any,
fullUrl: 'full-url' as any,
data: {
menu: {
Expand Down Expand Up @@ -170,6 +172,7 @@ describe('DynamicMenuItemsComponent', () => {
tpl,
{},
{
fullPath: 'full-path' as any,
fullUrl: 'full-url' as any,
data: {
menu: {
Expand Down Expand Up @@ -233,6 +236,7 @@ describe('DynamicMenuItemsComponent', () => {
tpl,
{},
{
fullPath: 'full-path' as any,
fullUrl: 'full-url' as any,
data: {
menu: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ describe('DynamicMenuComponent', () => {
{ href: '/path2', label: 'Path 2' },
]);
});

it('should render one level menu', () => {
const menuService = TestBed.get(
DynamicMenuService,
Expand Down
9 changes: 9 additions & 0 deletions projects/dynamic-menu/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ export interface DynamicDataWithMenu extends DataWithMenu {

export interface DynamicMenuRouteConfig extends RouteWithMenu {
data: DynamicDataWithMenu;
/**
* Represents unprocessed full path from root to route.
* Only calculated once a router config is loaded.
*/
fullPath: string[];
/**
* Represents processed full path from root to route.
* It updates with every navigation.
*/
fullUrl: string[];
}

Expand Down

0 comments on commit 3dc5f31

Please sign in to comment.