Skip to content

Commit

Permalink
feat(abc:reuse-tab): add provide (#1707)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Nov 12, 2023
1 parent fcbb0a7 commit 2f85357
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 122 deletions.
48 changes: 9 additions & 39 deletions packages/abc/reuse-tab/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ title: reuse-tab
subtitle: Reuse Route Tab
cols: 1
module: import { ReuseTabModule } from '@delon/abc/reuse-tab';
standalone: true
---

Reuse route tab are extremely common for admin interfaces, and the problem of component data is not lost when switching routes.
Expand All @@ -13,51 +14,20 @@ The newly opened is always the current page, and the route reuse means that when

## Usage

The default `ReuseTabModule` does not register `RouteReuseStrategy`. If you need route reuse, the first step is to register it:
1. Provide `provideReuseTabConfig()` configuration in `app.config.ts` file
2. Add `<reuse-tab>` in the `src/app/layout/basic/basic.component.ts` file, like this:

**Register**

> How to use in ng-alain, pls refer to [global-config.module.ts](https://github.com/ng-alain/ng-alain/blob/master/src/app/global-config.module.ts#L32).
```ts
// global-config.module.ts
import { RouteReuseStrategy } from '@angular/router';
import { ReuseTabService, ReuseTabStrategy } from '@delon/abc/reuse-tab';
alainProvides.push({
provide: RouteReuseStrategy,
useClass: ReuseTabStrategy,
deps: [ReuseTabService],
} as any);
```

**Add Component**

> In `src/app/layout/basic/basic.component.ts`
```html
<reuse-tab #reuseTab></reuse-tab>
<router-outlet (activate)="reuseTab.activate($event)" (attach)="reuseTab.activate($event)"></router-outlet>
```diff
- <router-outlet />
+ <reuse-tab #reuseTab />
+ <router-outlet (activate)="reuseTab.activate($event)" (attach)="reuseTab.activate($event)" />
```

> **Note: If you do not specify the `(activate)` event, you cannot refresh current tab when uncached.**
> In `src/app/layout/layout.module.ts`
```ts
import { ReuseTabModule } from '@delon/abc/reuse-tab'; // add
@NgModule({
imports: [
// ...
ReuseTabModule, // add
],
// ...
})
export class LayoutModule {}
```

## Matching Mode

Inject the `ReuseTabService` class anywhere in the project (recommended: `startup.service.ts`) and set the `mode` property, or pass `<reuse-tab [mode]="0"></reuse-tab> ` Reset values.
Inject the `ReuseTabService` class anywhere in the project and set the `mode` property, or pass `<reuse-tab [mode]="0" />` Reset values.

**0. Menu (Default)**

Expand Down Expand Up @@ -310,4 +280,4 @@ Route reuse preserves uses URLs to distinguish whether the same page, and QueryS

### Multi-application cache processing

Allows overriding `REUSE_TAB_CACHED_MANAGER` to change the cache storage, for example when using a micro-frontend (similar to [ngx-planet](https://github.com/worktile/ngx-planet)) can rewrite cached data to `window` guaranteed data sharing.
Use `provideReuseTabConfig(storeKey: 'newKey')` Or overriding `REUSE_TAB_CACHED_MANAGER` to change the cache storage, for example when using a micro-frontend (similar to [ngx-planet](https://github.com/worktile/ngx-planet)) can rewrite cached data to `window` guaranteed data sharing.
49 changes: 9 additions & 40 deletions packages/abc/reuse-tab/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ title: reuse-tab
subtitle: 路由复用标签
cols: 1
module: import { ReuseTabModule } from '@delon/abc/reuse-tab';
standalone: true
---

复用标签在中台系统非常常见,本质是解决不同路由页切换时组件数据不丢失问题。
Expand All @@ -13,52 +14,20 @@ module: import { ReuseTabModule } from '@delon/abc/reuse-tab';

## 如何使用

默认 `ReuseTabModule` 并不会注册 `RouteReuseStrategy`,这是因为若默认在模块内注册会导致所有引入 `@delon/abc` 模块都会强制使用路由复用(不管是否模板是否包括 `<reuse-tab>`)。因此:
1.`app.config.ts` 下提供 `provideReuseTabConfig()` 配置
2. 在需要展示标签的地方调用 `<reuse-tab>`,一般在 `src/app/layout/basic/basic.component.ts`,例如:

**注册RouteReuseStrategy**

> ng-alain 使用方式参考:[global-config.module.ts](https://github.com/ng-alain/ng-alain/blob/master/src/app/global-config.module.ts#L32)
```ts
// global-config.module.ts
import { RouteReuseStrategy } from '@angular/router';
import { ReuseTabService, ReuseTabStrategy } from '@delon/abc/reuse-tab';
alainProvides.push({
provide: RouteReuseStrategy,
useClass: ReuseTabStrategy,
deps: [ReuseTabService],
} as any);
```

**添加组件**

> 位置 `src/app/layout/basic/basic.component.ts`
```html
<reuse-tab #reuseTab></reuse-tab>
<router-outlet (activate)="reuseTab.activate($event)" (attach)="reuseTab.activate($event)"></router-outlet>
```diff
- <router-outlet />
+ <reuse-tab #reuseTab />
+ <router-outlet (activate)="reuseTab.activate($event)" (attach)="reuseTab.activate($event)" />
```

> **注意:若不指定 `(activate)` 事件,无法刷新未缓存过的当前标签页。**
> 位置 `src/app/layout/layout.module.ts`
```ts
import { ReuseTabModule } from '@delon/abc/reuse-tab'; // 新增 import

@NgModule({
imports: [
// ...
ReuseTabModule, // 导入模块
],
// ...
})
export class LayoutModule {}
```

## 匹配模式

在项目的任何位置(建议:`startup.service.ts`)注入 `ReuseTabService` 类,并设置 `mode` 属性,或通过 `<reuse-tab [mode]="0"></reuse-tab>` 重新设置值,包括:
在项目的任何位置注入 `ReuseTabService` 类,并设置 `mode` 属性,或通过 `<reuse-tab [mode]="0" />` 重新设置值,包括:

**0、Menu(推荐,默认值)**

Expand Down Expand Up @@ -311,4 +280,4 @@ export class DemoComponent implements OnReuseInit, OnReuseDestroy {

### 多应用缓存处理

允许覆盖 `REUSE_TAB_CACHED_MANAGER` 改变缓存存储,例如在使用微前端(类似[ngx-planet](https://github.com/worktile/ngx-planet))可以重写缓存数据到 `window` 下来实现数据共享。
使用 `provideReuseTabConfig(storeKey: 'newKey')` 或通过覆盖 `REUSE_TAB_CACHED_MANAGER` 改变缓存存储 ,例如在使用微前端(类似[ngx-planet](https://github.com/worktile/ngx-planet))可以重写缓存数据到 `window` 下来实现数据共享。
94 changes: 94 additions & 0 deletions packages/abc/reuse-tab/provide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
ENVIRONMENT_INITIALIZER,
EnvironmentProviders,
Provider,
inject,
makeEnvironmentProviders
} from '@angular/core';
import { RouteReuseStrategy } from '@angular/router';

import { REUSE_TAB_CACHED_MANAGER, ReuseTabCachedManagerFactory } from './reuse-tab.cache';
import { ReuseTabMatchMode, ReuseTabRouteParamMatchMode } from './reuse-tab.interfaces';
import { ReuseTabService } from './reuse-tab.service';
import { REUSE_TAB_STORAGE_KEY, REUSE_TAB_STORAGE_STATE, ReuseTabLocalStorageState } from './reuse-tab.state';
import { ReuseTabStrategy } from './reuse-tab.strategy';

export enum ReuseTabFeatureKind {
CacheManager,
Store
}

export interface ReuseTabFeature<KindT extends ReuseTabFeatureKind> {
ɵkind: KindT;
ɵproviders: Provider[];
}

function makeFeature<KindT extends ReuseTabFeatureKind>(kind: KindT, providers: Provider[]): ReuseTabFeature<KindT> {
return {
ɵkind: kind,
ɵproviders: providers
};
}

/**
* Configures reuse-tab to be available for injection.
*
* @see {@link withLocalStorage}
* @see {@link withCacheManager}
*/
export function provideReuseTabConfig(options?: {
debug?: boolean;
mode?: ReuseTabMatchMode;
routeParamMatchMode?: ReuseTabRouteParamMatchMode;
max?: number;
excludes?: RegExp[];
storeKey?: string;
cacheManager?: ReuseTabFeature<ReuseTabFeatureKind.CacheManager>;
store?: ReuseTabFeature<ReuseTabFeatureKind.Store>;
}): EnvironmentProviders {
const providers: Provider[] = [
{
provide: REUSE_TAB_STORAGE_KEY,
useValue: options?.storeKey ?? '_reuse-tab-state'
},
(options?.cacheManager ?? withCacheManager()).ɵproviders,
(options?.store ?? withLocalStorage()).ɵproviders,
{
provide: RouteReuseStrategy,
useClass: ReuseTabStrategy,
deps: [ReuseTabService]
},
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useValue: () => {
const srv = inject(ReuseTabService);
if (options?.debug) srv.debug = options.debug;
if (options?.mode) srv.mode = options.mode;
if (options?.routeParamMatchMode) srv.routeParamMatchMode = options.routeParamMatchMode;
if (options?.max) srv.max = options.max;
if (options?.excludes) srv.excludes = options.excludes;
}
}
];

return makeEnvironmentProviders(providers);
}

export function withCacheManager(): ReuseTabFeature<ReuseTabFeatureKind.CacheManager> {
return makeFeature(ReuseTabFeatureKind.CacheManager, [
{
provide: REUSE_TAB_CACHED_MANAGER,
useFactory: () => new ReuseTabCachedManagerFactory()
}
]);
}

export function withLocalStorage(): ReuseTabFeature<ReuseTabFeatureKind.Store> {
return makeFeature(ReuseTabFeatureKind.Store, [
{
provide: REUSE_TAB_STORAGE_STATE,
useFactory: () => new ReuseTabLocalStorageState()
}
]);
}
1 change: 1 addition & 0 deletions packages/abc/reuse-tab/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './reuse-tab.interfaces';
export * from './lifecycle_hooks';
export * from './reuse-tab.state';
export { REUSE_TAB_CACHED_MANAGER, ReuseTabCachedManager } from './reuse-tab.cache';
export * from './provide';
32 changes: 14 additions & 18 deletions packages/abc/reuse-tab/reuse-tab-context-menu.component.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
<ul nz-menu>
<li
*ngIf="item.active"
nz-menu-item
(click)="click($event, 'refresh')"
data-type="refresh"
[innerHTML]="i18n.refresh"
></li>
@if (item.active) {
<li nz-menu-item (click)="click($event, 'refresh')" data-type="refresh" [innerHTML]="i18n.refresh"></li>
}
<li
nz-menu-item
(click)="click($event, 'close')"
Expand All @@ -21,15 +17,15 @@
[nzDisabled]="item.last"
[innerHTML]="i18n.closeRight"
></li>
<ng-container *ngIf="customContextMenu!.length > 0">
<li nz-menu-divider></li>
<li
*ngFor="let i of customContextMenu"
nz-menu-item
[attr.data-type]="i.id"
[nzDisabled]="isDisabled(i)"
(click)="click($event, 'custom', i)"
[innerHTML]="i.title"
></li>
</ng-container>
@if (customContextMenu!.length > 0) {
<li nz-menu-divider></li>
@for (i of customContextMenu; track $index) {
<li
nz-menu-item
[attr.data-type]="i.id"
[nzDisabled]="isDisabled(i)"
(click)="click($event, 'custom', i)"
[innerHTML]="i.title"
></li>
} }
</ul>
5 changes: 4 additions & 1 deletion packages/abc/reuse-tab/reuse-tab-context-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@angular/core';

import { DelonLocaleService } from '@delon/theme';
import { NzMenuModule } from 'ng-zorro-antd/menu';

import {
CloseType,
Expand All @@ -27,7 +28,9 @@ import {
},
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NzMenuModule]
})
export class ReuseTabContextMenuComponent implements OnInit {
private _i18n!: ReuseContextI18n;
Expand Down
3 changes: 2 additions & 1 deletion packages/abc/reuse-tab/reuse-tab-context.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { ReuseContextCloseEvent, ReuseContextI18n } from './reuse-tab.interfaces

@Component({
selector: 'reuse-tab-context',
template: ``
template: ``,
standalone: true
})
export class ReuseTabContextComponent implements OnDestroy {
private sub$: Subscription = new Subscription();
Expand Down
3 changes: 2 additions & 1 deletion packages/abc/reuse-tab/reuse-tab-context.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { ReuseCustomContextMenu, ReuseItem } from './reuse-tab.interfaces';
exportAs: 'reuseTabContextMenu',
host: {
'(contextmenu)': '_onContextMenu($event)'
}
},
standalone: true
})
export class ReuseTabContextDirective {
@Input('reuse-tab-context-menu') item!: ReuseItem;
Expand Down
19 changes: 11 additions & 8 deletions packages/abc/reuse-tab/reuse-tab.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
[nzTabBarGutter]="tabBarGutter"
[nzTabBarStyle]="tabBarStyle"
>
<nz-tab *ngFor="let i of list; let index = index" [nzTitle]="titleTemplate" (nzClick)="_to(index)">
@for (i of list; track $index) {
<nz-tab [nzTitle]="titleTemplate" (nzClick)="_to($index)">
<ng-template #titleTemplate>
<div
[reuse-tab-context-menu]="i"
Expand All @@ -16,16 +17,18 @@
[attr.title]="i.title"
>
<span [class.reuse-tab__name-width]="tabMaxWidth" [style.max-width.px]="tabMaxWidth">
<ng-container
*ngIf="titleRender; else defaultTitle"
[ngTemplateOutlet]="titleRender"
[ngTemplateOutletContext]="{ $implicit: i }"
/>
<ng-template #defaultTitle>{{ i.title }}</ng-template>
@if (titleRender) {
<ng-template [ngTemplateOutlet]="titleRender" [ngTemplateOutletContext]="{ $implicit: i }" />
} @else {
{{ i.title }}
}
</span>
</div>
<i *ngIf="i.closable" nz-icon nzType="close" class="reuse-tab__op" (click)="_close($event, index, false)"></i>
@if (i.closable) {
<i nz-icon nzType="close" class="reuse-tab__op" (click)="_close($event, $index, false)"></i>
}
</ng-template>
</nz-tab>
}
</nz-tabset>
<reuse-tab-context [i18n]="i18n" (change)="contextMenuChange($event)" />
Loading

0 comments on commit 2f85357

Please sign in to comment.