createGroupMap moved to tree-utils

code refactored
pull/15938/head
masumulu28 3 years ago
parent 8ab24be0ef
commit a1b94fed05

@ -2,7 +2,6 @@ import { EventEmitter, Type } from '@angular/core';
import { Routes } from '@angular/router';
import { Subject } from 'rxjs';
import { eLayoutType } from '../enums/common';
import { TreeNode } from '../utils';
import { Environment } from './environment';
export namespace ABP {
@ -75,11 +74,6 @@ export namespace ABP {
group?: string;
}
export interface RouteGroup {
group: string;
items: TreeNode<Route>[];
}
export interface Tab extends Nav {
component: Type<any>;
}

@ -1,9 +1,15 @@
import { Injectable, Injector, OnDestroy } from '@angular/core';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subscription, map } from 'rxjs';
import { ABP } from '../models/common';
import { OTHERS_GROUP } from '../tokens';
import { pushValueTo } from '../utils/array-utils';
import { BaseTreeNode, createTreeFromList, TreeNode } from '../utils/tree-utils';
import {
BaseTreeNode,
createTreeFromList,
TreeNode,
RouteGroup,
createGroupMap,
} from '../utils/tree-utils';
import { ConfigStateService } from './config-state.service';
import { PermissionService } from './permission.service';
@ -18,6 +24,8 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
private _tree$ = new BehaviorSubject<TreeNode<T>[]>([]);
private _visible$ = new BehaviorSubject<TreeNode<T>[]>([]);
protected othersGroup: string;
get flat(): T[] {
return this._flat$.value;
}
@ -51,6 +59,11 @@ export abstract class AbstractTreeService<T extends { [key: string | number | sy
);
}
protected createGroupedTree(list: TreeNode<T>[]): RouteGroup<T>[] | undefined {
const map = createGroupMap<T>(list, this.othersGroup);
return !map ? undefined : Array.from(map, ([key, items]) => ({ group: key, items }));
}
private filterWith(setOrMap: Set<string> | Map<string, T>): T[] {
return this._flat$.value.filter(item => !setOrMap.has(item[this.id]));
}
@ -158,6 +171,7 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
.createOnUpdateStream(state => state)
.subscribe(() => this.refresh());
this.permissionService = injector.get(PermissionService);
this.othersGroup = injector.get(OTHERS_GROUP);
}
protected isGranted({ requiredPolicy }: T): boolean {
@ -182,24 +196,11 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
@Injectable({ providedIn: 'root' })
export class RoutesService extends AbstractNavTreeService<ABP.Route> {
private readonly othersGroup: string;
constructor(injector: Injector) {
super(injector);
this.othersGroup = injector.get(OTHERS_GROUP);
get groupedVisible(): RouteGroup<ABP.Route>[] | undefined {
return this.createGroupedTree(this.visible);
}
get groupedVisible(): ABP.RouteGroup[] | undefined {
const hasGroup = this.visible.some(node => !!node.group);
if (!hasGroup) return;
const groups = this.visible.reduce((acc, node) => {
const groupName = node.group ?? this.othersGroup;
acc[groupName] ||= [];
acc[groupName].push(node);
return acc;
}, {} as Record<string, TreeNode<ABP.Route>[]>);
return Object.entries(groups).map(([group, items]) => ({ group, items }));
get groupedVisible$(): Observable<RouteGroup<ABP.Route>[] | undefined> {
return this.visible$.pipe(map(visible => this.createGroupedTree(visible)));
}
}

@ -1,4 +1,4 @@
import { Subject } from 'rxjs';
import { Subject, lastValueFrom } from 'rxjs';
import { take } from 'rxjs/operators';
import { RoutesService } from '../services/routes.service';
import { DummyInjector } from './utils/common.utils';
@ -47,9 +47,9 @@ describe('Routes Service', () => {
it('should add given routes as flat$, tree$, and visible$', async () => {
service.add(routes);
const flat = await service.flat$.pipe(take(1)).toPromise();
const tree = await service.tree$.pipe(take(1)).toPromise();
const visible = await service.visible$.pipe(take(1)).toPromise();
const flat = await lastValueFrom(service.flat$.pipe(take(1)));
const tree = await lastValueFrom(service.tree$.pipe(take(1)));
const visible = await lastValueFrom(service.visible$.pipe(take(1)));
expect(flat.length).toBe(5);
expect(flat[0].name).toBe('baz');
@ -72,11 +72,37 @@ describe('Routes Service', () => {
expect(visible[0].children[0].name).toBe('x');
});
});
describe('#groupedVisible', () => {
it('should return grouped route list', () => {
it('should return undefined when there are no visible routes', async () => {
service.add(routes);
const result = await lastValueFrom(service.groupedVisible$.pipe(take(1)));
expect(result).toBeUndefined();
});
it(
'should group visible routes under "' + othersGroup + '" when no group is specified',
async () => {
service.add([
{ path: '/foo', name: 'foo' },
{ path: '/foo/bar', name: 'bar', group: '' },
{ path: '/foo/bar/baz', name: 'baz', group: undefined },
{ path: '/x', name: 'y', group: 'z' },
]);
const result = await lastValueFrom(service.groupedVisible$.pipe(take(1)));
expect(result[0].group).toBe(othersGroup);
expect(result[0].items[0].name).toBe('foo');
expect(result[0].items[1].name).toBe('bar');
expect(result[0].items[2].name).toBe('baz');
},
);
it('should return grouped route list', async () => {
service.add(groupedRoutes);
const tree = service.groupedVisible;
const tree = await lastValueFrom(service.groupedVisible$.pipe(take(1)));
expect(tree.length).toBe(3);

@ -74,6 +74,28 @@ export function createTreeNodeFilterCreator<T extends object>(
};
}
export function createGroupMap<T extends { [key: string]: string }>(
list: TreeNode<T>[],
othersGroupKey: string,
): Map<string, TreeNode<T>[]> | undefined {
if (!list || !Array.isArray(list) || !list.some(node => Boolean(node.group))) return undefined;
const mapGroup = new Map<string, TreeNode<T>[]>();
for (const node of list) {
const group = node?.group || othersGroupKey;
if (typeof group !== 'string') {
throw new Error(`Invalid group: ${group}`);
}
const items = mapGroup.get(group) || [];
items.push(node);
mapGroup.set(group, items);
}
return mapGroup;
}
export type TreeNode<T extends object> = {
[K in keyof T]: T[K];
} & {
@ -82,6 +104,11 @@ export type TreeNode<T extends object> = {
parent?: TreeNode<T>;
};
export type RouteGroup<T extends object> = {
readonly group: string;
readonly items: TreeNode<T>[];
};
export type NodeKey = number | string | symbol | undefined | null;
export type NodeValue<T extends object, F extends (...args: any) => any> = F extends undefined

Loading…
Cancel
Save