Reuse Route Tab
IMPORT MODULE

Reuse route tab are extremely common for admin interfaces, and the problem of component data is not lost when switching routes.

The newly opened is always the current page, and the route reuse means that when we leave the current page, if the reuse condition is met (ie: matching mode), all component states (including subcomponents) of the current route are saved, wait for the next time you enter the route to restore the component data based on the cache.

Usage

The default ReuseTabModule does not register RouteReuseStrategy. If you need route reuse, the first step is to register it:

Register

How to use in ng-alain, pls refer to global-config.module.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

<reuse-tab #reuseTab></reuse-tab>
<router-outlet (activate)="reuseTab.activate($event)"></router-outlet>

Note: If you do not specify the (activate) event, you cannot refresh current tab when uncached.

In src/app/layout/layout.module.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"> Reset values.

0. Menu (Default)

Press the (Menu) to configure.

Reusable:

{ text:'Dashboard' }
{ text:'Dashboard', reuse: true }

Not reusable:

{ text:'Dashboard', reuse: false }

1. MenuForce

Press the (Menu) to force the configure.

Reusable:

{ text:'Dashboard', reuse: true }

Not reusable:

{ text:'Dashboard' }
{ text:'Dashboard', reuse: false }

2. URL

Valid for all routes, can be combined with excludes filtering without reusing.

Tab Text

Get the tab text in the following order:

  1. Overwrite text within the component with `ReuseTabService.title = 'new title'

  2. The data property in the routing configuration

  3. text property in menu data

Use ReuseTabService example:

export class DemoReuseTabEditComponent implements OnInit {
  id: any;

  constructor(private route: ActivatedRoute, private reuseTabService: ReuseTabService) {}

  ngOnInit(): void {
    this.route.params.subscribe(params => {
      this.id = params.id;
      this.reuseTabService.title = `Edit ${this.id}`;
    });
  }
}

Reuse Events

Route reusing does not touch the Angular component lifecycle hooks (eg: ngOnInit, etc.), but often requires data to be refreshed during the reuse process, so two new lifecycle hooks are provided to temporarily resolve such problems.

OnReuseInit Interface

  • _onReuseInit(type?: ReuseHookOnReuseInitType): void;

Triggered when the current route is in the reusing process, The values of type are:

-init when routing process -refresh when refresh action via tab

OnReuseDestroy Interface

  • _onReuseDestroy(): void;

Triggered when the current route allows reusing and leave route.

A simple example:

@Component()
export class DemoComponent {
  _onReuseInit(type: ReuseHookOnReuseInitType) {
    console.log('_onReuseInit', type);
  }
  _onReuseDestroy() {
    console.log('_onReuseDestroy');
  }
}

Scroll Position

Turning on keepingScroll will restore the previous scrollbar position after reused, pls attention to detail:

Make sure to use the routing option scrollPositionRestoration to manage the scrollbar position.

  • true: Keep the previous scroll bar position

  • false: Do't operate on the scroll bar

  • If all page used route reuse, you can set scrollPositionRestoration: 'disabled' to avoid delayed scrolling.

  • If part of the page used route reuse, it's limited by scrollPositionRestoration priority value, there will be a 1ms delay to restore the scrollbar position.

API

ReuseTabService

Property

PropertyDescriptionTypeDefault
[max]Maximum of reusenumber10
[mode]Matching ModeReuseTabMatchMode0
[debug]Whether Debug modebooleanfalse
[keepingScroll]Keep the scrollbar positionbooleanfalse
[keepingScrollContainer]Keep the scroll bar containerElementwindow
[excludes]Exclusion rules, limited by mode=URLRegExp[]-
[items]Get cached routesReuseTabCached[]-
[count]Get the number of cached routesnumber-
[change]Cache change callbackObservable<ReuseTabNotify>-
[title]Customize the current titlestring-
[closable]Customize the current closable stateboolean-

Method

NameDescriptionType
index(url)Returns the index of the first element in the caches, and -1 otherwisenumber
exists(url)Exists cache by URLboolean
get(url)Get cache data by URLReuseTabCached
getTitle(url, route?: ActivatedRouteSnapshot)Get titlestring
clearTitleCached()Clear all title cachesvoid
getClosable(url, route?: ActivatedRouteSnapshot)Get closable statestring
clearClosableCached()Clear cached of closablevoid
remove(url)Remove the tag, touch change remove eventvoid
move(url, position)Move of caches, touch change move eventvoid
clear()Clear caches, touch change clear eventvoid
refresh()Just touch change refresh eventvoid
replace(url)Force closed current route (including the non-closable) and navigate back to the newUrl routevoid

reuse-tab

PropertyDescriptionTypeDefault
[i18n]Context Menu internationalization, support HTMLReuseContextI18n-
[mode]Matching ModeReuseTabMatchMode0
[debug]Whether Debug modebooleanfalse
[max]Maximum of reusenumber10
[keepingScroll]Keep the scrollbar positionbooleanfalse
[keepingScrollContainer]Keep the scroll bar containerstring | Elementwindow
[excludes]Exclusion rules, limited by mode=URLRegExp[]-
[allowClose]Whether to allow close tabbooleantrue
[customContextMenu]Custom context click menuReuseCustomContextMenu[]-
[tabBarExtraContent]Extra content in tab barTemplateRef<void>-
[tabBarStyle]Tab bar style objectobject-
[tabBarGutter]The gap between tabsnumber-
[tabType]Basic style of tabsline, cardline
[tabMaxWidth]The maximum width of each tab, unit: pxnumber-
[routeParamMatchMode]Match the pattern when routing parameters are included, for example:/view/:id
- strict Strict mode /view/1, /view/2 Different pages
- loose Loose mode /view/1, /view/2 Same page and only one tab of component
strict,loosestrict
[disabled]Whether to disabledbooleanfalse
[titleRender]Custom rendering of the titleTemplateRef<{ $implicit: ReuseItem }>-
[storageState]Whether to store the state, keep the last browser statebooleanfalse
[canClose]A function to determine what should be closed(options: { item: ReuseItem; includeNonCloseable: boolean }) => Observable<boolean>-
(close)Close callback eventEventEmitter-
(change)Callback when switchingEventEmitter-

Context Menu

The non-closeable item is forcibly removed when the keyboard ctrl is pressed.

ReuseTabCached

PropertyDescriptionTypeDefault
[title]Titlestring-
[url]URLstring-
[closable]Whether to allow closeboolean-

ReuseTabNotify

PropertyDescriptionTypeDefault
[active]Event typestitle,close,closeRight,clear,move,closable,refresh,add-

ReuseContextI18n

PropertyDescriptionTypeDefault
[close]Closestring-
[closeOther]Close other tabsstring-
[closeRight]Close tabs to the rightstring-
[clear]Clear tabsstring-

ReuseCustomContextMenu

PropertyDescriptionTypeDefault
[id]Identifier for current context menustring-
[title]Title for current context menustring-
[fn]Process method for current context menu(item: ReuseItem, menu: ReuseCustomContextMenu) => void-
[disabled]Whether to disabled(item: ReuseItem) => boolean-

Route data

By routing the data property, you can provide partial global configuration for some pages, for example:

// Specify no route
{ path: 'p1', component: DemoComponent, data: { reuse: false } }
// Specify title
{ path: 'p1', component: DemoComponent, data: { title: 'New Title' } }
PropertyDescriptionTypeDefault
[reuse]Whether to reuseboolean-
[title]Titlestring-
[titleI18n]Ii18n title keystring-
[reuseClosable]Whether to allow closeboolean-
[keepingScroll]Keep the scrollbar positionboolean-

Note: The above data can also be reflected in the Menu data.

FAQ

How to Debug

Route reuse preserves component data state, which may bring another drawback. The Angular lifecycle hook is not triggered during the reuse process. In most cases, it can run normally. There are several common problems:

  • OnDestroy may handle the external style in component (eg: body), which can be resolved by Reuse Events

  • Turn on the debug mode to help you analyze

Max

Limiting the maximum number of reuse can reduce memory growth. There are several issues to be aware of:

  • max Forces a close and ignores the closable state when value changes

  • When it's out of max range, it will turn off the first open tab (Only closable), ingore close when all pages are non-closable

Not supported QueryString parameters

Route reuse preserves uses URLs to distinguish whether the same page, and QueryString query parameters will be repeatedly misused, so not supported, and the QueryString part is forced to be ignored.