grouped menu feature refactored

document updated
test updated
pull/15938/head
masumulu28 3 years ago
parent 7b625e1d23
commit 309eeada03

@ -96,19 +96,12 @@ import { ABP } from '@abp/ng.core';
type GroupType = ABP.Group<string>;
function configureRoutes(routes: RoutesService) {
const myGroup: GroupType = { key:'groupKey', text:'GroupName' };
function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '/your-path',
name: 'Your navigation',
requiredPolicy: 'permission key here',
order: 101,
iconClass: 'fas fa-question-circle',
layout: eLayoutType.application,
group: myGroup
//etc..
group: 'ModuleName::GroupName'
},
{
path: '/your-path/child',
@ -138,11 +131,11 @@ export class AppComponent {
```
...and then in app.module.ts...
- `groupedVisible` method will return `Others` group for ungrouped items, we can define `key` and `text` via `OTHERS_GROUP` injection token for this group
- `groupedVisible` method will return `Others` group for ungrouped items, Default key is `AbpUi::OthersGroup` we can change this `key` via `OTHERS_GROUP` injection token
```js
import { NgModule } from '@angular/core';
import { ABP, OTHERS_GROUP } from '@abp/ng.core';
import { OTHERS_GROUP } from '@abp/ng.core';
import { APP_ROUTE_PROVIDER } from './route.provider';
@NgModule({
@ -150,7 +143,7 @@ import { APP_ROUTE_PROVIDER } from './route.provider';
APP_ROUTE_PROVIDER,
{
provide: OTHERS_GROUP,
useValue: { key: 1, text: 'MyOthersGroup' } as ABP.Group<number>,
useValue: 'ModuleName::MyOthersGroupKey',
},
],
// imports, declarations, and bootstrap
@ -168,9 +161,7 @@ Here is what every property works as:
- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label.
- `layout` defines in which layout the route will be loaded. (default: `eLayoutType.empty`)
- `invisible` makes the item invisible in the menu. (default: `false`)
- `group` is an optional property that is used to group together related routes in an application. It's an object and it have 2 property
- `key` is a generic type property that we use for gather items in same group. (default type: `string`)
- `text` is the display name on menu
- `group` is an optional property that is used to group together related routes in an application. (type: `string`, default: `AbpUi::OthersGroup`)
### Via `routes` Property in `AppRoutingModule`

@ -179,7 +179,7 @@ export class CoreModule {
},
{
provide: OTHERS_GROUP,
useValue: options.othersGroup || { key: 'others', text: '::Others' } as ABP.Group<string>,
useValue: options.othersGroup || 'AbpUi::OthersGroup',
},
IncludeLocalizationResourcesProvider,
],

@ -13,7 +13,7 @@ export namespace ABP {
sendNullsAsQueryParam?: boolean;
tenantKey?: string;
localizations?: Localization[];
othersGroup?: Group<any>;
othersGroup?: string;
}
export interface Child {
@ -68,20 +68,15 @@ export namespace ABP {
invisible?: boolean;
}
export interface Group<TKey = string> {
key: TKey;
text: string;
}
export interface Route extends Nav {
path?: string;
layout?: eLayoutType;
iconClass?: string;
group?: Group<any>;
group?: string;
}
export interface RouteGroup<TKey = string> {
group: Group<TKey>;
export interface RouteGroup {
group: string;
items: TreeNode<Route>[];
}

@ -1,4 +1,4 @@
import { Injectable, Injector, Inject, Optional, OnDestroy } from '@angular/core';
import { Injectable, Injector, Inject, OnDestroy} from '@angular/core';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { ABP } from '../models/common';
import { OTHERS_GROUP } from '../tokens';
@ -182,34 +182,20 @@ export abstract class AbstractNavTreeService<T extends ABP.Nav>
@Injectable({ providedIn: 'root' })
export class RoutesService extends AbstractNavTreeService<ABP.Route> {
constructor(
injector: Injector,
@Optional() @Inject(OTHERS_GROUP) private readonly othersGroup: ABP.Group<any>,
) {
constructor(injector: Injector, @Inject(OTHERS_GROUP) private readonly othersGroup: string) {
super(injector);
}
get groupedVisible(): ABP.RouteGroup[] | undefined {
const groupTree = this.visible.filter(node => node.group);
if (groupTree.length < 1) return;
const map = new Map<ABP.Group, TreeNode<ABP.Route>[]>(groupTree?.map(node => [node.group, []]));
const otherGroup = this.othersGroup;
map.set(otherGroup, []);
for (const node of this.visible) {
const { path, children, group } = node;
if (!group && (children?.length > 0 || path)) {
map.get(otherGroup)?.push(node);
} else if (group) {
map.get(group)?.push(node);
}
}
return Array.from(map.entries()).map<ABP.RouteGroup>(([group, nodes]) => ({
group,
items: nodes,
}));
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 }));
}
}

@ -1,29 +1,26 @@
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { ABP } from '../models';
import { RoutesService } from '../services/routes.service';
import { DummyInjector } from './utils/common.utils';
import { mockPermissionService } from './utils/permission-service.spec.utils';
const updateStream$ = new Subject<void>();
type GroupType = ABP.Group<string>;
export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => {
const injector = new DummyInjector({
PermissionService: mockPermissionService(),
ConfigStateService: { createOnUpdateStream: () => updateStream$ },
...injectorPayload,
});
const othersGroupToken: ABP.Group<number> = { key: 1, text: 'Others' };
return new RoutesService(injector, othersGroupToken);
return new RoutesService(injector, 'OthersGroup');
};
describe('Routes Service', () => {
let service: RoutesService;
const fooGroup: GroupType = { key: 'foo', text: 'FooGroup' };
const barGroup: GroupType = { key: 'bar', text: 'BarGroup' };
const fooGroup = 'FooGroup';
const barGroup = 'BarGroup';
const othersGroup = 'OthersGroup';
const routes = [
{ path: '/foo', name: 'foo' },
@ -75,18 +72,6 @@ describe('Routes Service', () => {
});
});
describe('#addGroup', () => {
it('should have routes with and without group', async () => {
service.add(groupedRoutes);
const grouped = service.visible.filter(f => f.group);
const unGrouped = service.visible.filter(f => !f.group);
expect(grouped.length).toBe(3);
expect(unGrouped.length).toBe(1);
});
});
describe('#groupedVisible', () => {
it('should have groups and items', async () => {
service.add(groupedRoutes);
@ -95,18 +80,15 @@ describe('Routes Service', () => {
expect(tree.length).toBe(3);
expect(tree[0].group.key).toBe('foo');
expect(tree[0].group.text).toBe('FooGroup');
expect(tree[0].group).toBe('FooGroup');
expect(tree[0].items[0].name).toBe('foo');
expect(tree[0].items[0].children[0].name).toBe('y');
expect(tree[1].group.key).toBe('bar');
expect(tree[1].group.text).toBe('BarGroup');
expect(tree[1].group).toBe('BarGroup');
expect(tree[1].items[0].name).toBe('bar');
expect(tree[1].items[1].name).toBe('baz');
expect(tree[2].group.key).toBe(1);
expect(tree[2].group.text).toBe('Others');
expect(tree[2].group).toBe(othersGroup);
expect(tree[2].items[0].name).toBe('z');
});
});

@ -1,4 +1,3 @@
import { InjectionToken } from '@angular/core';
import { ABP } from '../models';
export const OTHERS_GROUP = new InjectionToken<ABP.Group>('OTHERS_GROUP');
export const OTHERS_GROUP = new InjectionToken<string>('OTHERS_GROUP');

Loading…
Cancel
Save