问题描述:
angular单页面应用,有列表和编辑两个页面。列表页包含多个查询条件及分页,选中一条数据进行编辑,路由更新至编辑页,编辑完成后返回列表页,此时,客户希望列表页保持离开时的状态不变,只单独更新这一条数据,其他情况下列表页正常初始化。
解决方案:
对列表页使用路由复用策略,根据上一个页面是否是编辑页来判断是否进行初始化。如果是编辑页跳转过来,此时列表页不应该执行ngOninit函数,且对单条数据进行更新,如果不是,那列表页执行ngOninit,正常初始化。
上代码:
1.新建route-strategy.service.ts
import {
ActivatedRouteSnapshot,
DetachedRouteHandle,
RouteReuseStrategy
} from '@angular/router';
import { Injectable } from '@angular/core';
interface IRouteConfigData {
reuse: boolean;
}
interface ICachedRoute {
handle: DetachedRouteHandle;
data: IRouteConfigData;
}
@Injectable()
export class ZwRouteReuseStrategy implements RouteReuseStrategy {
private static routeCache = new Map<string, ICachedRoute>();
private static waitDelete: any; // 当前页未进行存储时需要删除
private static currentDelete: any; // 当前页存储过时需要删除
/** 进入路由触发,判断是否是同一路由 */
shouldReuseRoute(
future: ActivatedRouteSnapshot,
curr: ActivatedRouteSnapshot
): boolean {
const IsReturn =
future.routeConfig === curr.routeConfig &&
JSON.stringify(future.params) == JSON.stringify(curr.params);
return IsReturn;
}
/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断,这里判断是否有data数据判断是否复用 */
shouldDetach(route: ActivatedRouteSnapshot): boolean {
if (this.getRouteData(route)) {
return true;
}
return false;
}
/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象 */
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle):void | null{
// const url = this.getFullRouteUrl(route);
const url = this.getRouteUrl(route);
const data = this.getRouteData(route);
if (
ZwRouteReuseStrategy.waitDelete &&
ZwRouteReuseStrategy.waitDelete === url
) {
// 如果待删除是当前路由,且未存储过则不存储快照
ZwRouteReuseStrategy.waitDelete = null;
return null;
} else {
// 如果待删除是当前路由,且存储过则不存储快照
if (
ZwRouteReuseStrategy.currentDelete &&
ZwRouteReuseStrategy.currentDelete === url
) {
ZwRouteReuseStrategy.currentDelete = null;
return null;
} else {
if (handle) {
ZwRouteReuseStrategy.routeCache.set(url, { handle, data });
this.addRedirectsRecursively(route);
}
}
}
}
/** 若 path 在缓存中有的都认为允许还原路由 */
shouldAttach(route: ActivatedRouteSnapshot): boolean {
// const url = this.getFullRouteUrl(route);
const url = this.getRouteUrl(route);
const handle = ZwRouteReuseStrategy.routeCache.has(url)
? ZwRouteReuseStrategy.routeCache.get(url)?.handle
: null;
const data = this.getRouteData(route);
const IsReturn =
data && ZwRouteReuseStrategy.routeCache.has(url) && handle != null;
return IsReturn;
}
/** 从缓存中获取快照,若无则返回nul */
retrieve(route: ActivatedRouteSnapshot): any {
const url = this.getRouteUrl(route);
const data = this.getRouteData(route);
const IsReturn =
data && ZwRouteReuseStrategy.routeCache.has(url)
? ZwRouteReuseStrategy.routeCache.get(url)?.handle
: null;
return IsReturn;
}
private addRedirectsRecursively(route: ActivatedRouteSnapshot): void {
const config = route.routeConfig;
if (config) {
if (!config.loadChildren) {
const routeFirstChild = route.firstChild;
const routeFirstChildUrl = routeFirstChild
? this.getRouteUrlPaths(routeFirstChild).join('/')
: '';
const childConfigs = config.children;
if (childConfigs) {
const childConfigWithRedirect = childConfigs.find(
c => c.path === '' && !!c.redirectTo
);
if (childConfigWithRedirect) {
childConfigWithRedirect.redirectTo = routeFirstChildUrl;
}
}
}
route.children.forEach(childRoute =>
this.addRedirectsRecursively(childRoute)
);
}
}
private getRouteUrl(route: ActivatedRouteSnapshot) {
return (
route['_routerState'].url.replace(/\//g, '_') +
'_' +
(route.routeConfig?.loadChildren ||
route.routeConfig?.component?.toString()
.split('(')[0]
.split(' ')[1])
);
}
private getFullRouteUrl(route: ActivatedRouteSnapshot): string {
return this.getFullRouteUrlPaths(route)
.filter(Boolean)
.join('/')
.replace('/', '_');
}
private getFullRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
const paths = this.getRouteUrlPaths(route);
return route.parent
? [...this.getFullRouteUrlPaths(route.parent), ...paths]
: paths;
}
private getRouteUrlPaths(route: ActivatedRouteSnapshot): string[] {
return route.url.map(urlSegment => urlSegment.path);
}
private getRouteData(route: ActivatedRouteSnapshot): IRouteConfigData {
return (
route.routeConfig &&
(route.routeConfig.data as IRouteConfigData) &&
route.routeConfig?.data?.reuse
);
}
/** 用于删除路由快照*/
public deleteRouteSnapshot(url: string): void {
if (url[0] === '/') {
url = url.substring(1);
}
url = url.replace('/', '_');
if (ZwRouteReuseStrategy.routeCache.has(url)) {
ZwRouteReuseStrategy.routeCache.delete(url);
ZwRouteReuseStrategy.currentDelete = url;
} else {
ZwRouteReuseStrategy.waitDelete = url;
}
}
public clear() {
ZwRouteReuseStrategy.routeCache.clear();
}
public clearExcept(list) {
if (!list || !ZwRouteReuseStrategy.routeCache) return;
try {
let waitDelete:any = [];
ZwRouteReuseStrategy.routeCache.forEach((value: ICachedRoute, key) => {
let handle: any = value.handle;
let url = handle.route.value._routerState.snapshot.url;
if (list.indexOf(url) < 0) {
waitDelete.push(key);
}
});
waitDelete.forEach(item => {
ZwRouteReuseStrategy.routeCache.delete(item);
});
} catch (error) {
console.log('clearExcept error', error);
}
}
}
2.app.module.ts中引入
providers:[{ provide: RouteReuseStrategy, useClass: ZwRouteReuseStrategy }]
3.配置路由,各个模按需配置路由复用策略
在xxx.routing.module.ts文件中,在需要路由复用的模块路由上加上data: { reuse:true },这样该模块就不会默认执行ngOninit函数了。
const routes: Routes = [
{ path: '', redirectTo: 'list' },
{ path: "list", component: ListComponent, data: { reuse:true } },
{ path: "detail/:id", component: DetailComponent}
];
4.模块内部逻辑list.component.ts
constructor(private router:Router) {
this.router.events.pipe(filter(event => event instanceof NavigationStart)).subscribe((event: NavigationStart) => {
if(event.url==='/app/xxx/list'){ // 防止在其他模块时这里也执行,也就是在自己模块处理该问题即可
// 当前是列表页,且来源页是编辑页时,不刷新页面
if (this.router.url.indexOf("/app/xxx/detail")!==-1) {
const idArr = this.router.url.split("/");
const id = idArr[idArr.length-1];// 获取编辑的那条数据的id
this.updateSingle(id);//单条更新
} else if (this.router.url.indexOf("/app/xxx/detail")==-1) {
// 不是编辑页跳转过来的,都走ngOninit函数
this.ngOnInit();
}
}
});
}
ngOnInit(): void{
// 根据各个筛选条件及页码查询列表
this.searchList();
}
searchList(){
//获取列表数据
}
updateSingle(id){
// 单条更新数据
}