在Angular 2中布线期间保持零部件处于活动状态

Keeping a component alive during routing in Angular 2

本文关键字:零部件 活动状态 Angular 布线      更新时间:2023-09-26

我有一个用javascript编写的应用程序,主要使用jquery。它很难看,很难维护,而且迫切需要一个框架,所以我正在将它移植到Angular 2 中使用

我已经用两条路线设置了我的Angular 2应用程序:

路由A:包含带有WebGL画布的组件路由B:包含显示其他数据的组件

根据用户机器的不同,webgl组件的初始化可能需要相当长的时间。

当用户从路由A导航到路径B,然后再次返回路线A

在jquery中,我只是简单地使用了在画布上隐藏和显示。

我的问题与非常相似

Angular 2即使没有导航到,也能保持路线的活力

然而,使用CanReuse的解决方案似乎是无效的,如下所示:

路由更改时不调用routerCanReuse和routerOnReuse

因为它被设计用于处理往返于相同类型组件的路由。

通过覆盖RouteReuseStrategy,可以在路由期间保持组件活动
多亏了这个伟大的后Angular 2组件重用策略

Angular 2目前无法实现这一点。不过,您可以实现自己的RouterOutlet,它可以在路由中保存和恢复组件。


以下是Angular 2的Beta版本:

几天前有一次关于angular github的讨论,他们提出了这个解决方案。

在根组件中,只需将<router-outlet></router-outlet>替换为<persistent-router-outlet></persistent-router-outlet>,导入PersistentRouterOutlet并将其添加到指令中。

粘贴此处是出于备份原因:

import * as hookMod from 'angular2/src/router/lifecycle_annotations';
import * as routerMod from 'angular2/src/router/router';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
    ElementRef, DynamicComponentLoader, Directive, Injector, provide, ComponentRef, Attribute
} from 'angular2/core';
import {
    ComponentInstruction, CanReuse, OnReuse, CanDeactivate,
    RouterOutlet, OnActivate, Router, RouteData, RouteParams, OnDeactivate
} from 'angular2/router';
import {hasLifecycleHook} from 'angular2/src/router/route_lifecycle_reflector';
/**
 * Reference Cache Entry
 */
class RefCacheItem {
    constructor(public componentRef: ComponentRef) {
    }
}
/**
 * Reference Cache
 */
class RefCache {
    private cache: any = {};
    public getRef(type: any) {
        return this.cache[type];
    }
    public addRef(type: any, ref: RefCacheItem) {
        this.cache[type] = ref;
    }
    public hasRef(type: any): boolean {
        return !isBlank(this.cache[type]);
    }
    public removeRef(type: any){
        delete this.cache[type];
    }
    public remove(ref){
      // remove self from cache
      this.removeRef(ref.componentType);
      // remove children
      var parent = ref.location.nativeElement;
      Object.keys(this.cache).forEach((key)=>{
        var child = this.cache[key].componentRef.location.nativeElement;
        if (parent.contains(child)){
          this.removeRef(key)
        }
      })
      // will destroy component and all children
      // sense this is an angular call, all lifecycle methods will be called
      ref.dispose();
    }
}
const refCache = new RefCache();
/**
 * An outlet that persists the child views and re-uses their components.
 *
 * @author Wael Jammal
 */
@Directive({selector: 'persistent-router-outlet'})
export class PersistentRouterOutlet extends RouterOutlet {
    private currentInstruction: ComponentInstruction;
    private currentElementRef;
    private resolveToTrue = PromiseWrapper.resolve(true);
    private currentComponentRef: ComponentRef;
    constructor(elementRef: ElementRef,
                private loader: DynamicComponentLoader,
                private parentRouter: Router,
                @Attribute('name') nameAttr: string) {
        super(elementRef, loader, parentRouter, nameAttr);
        this.currentElementRef = elementRef;
    }
    /**
     * Called by the Router to instantiate a new component during the commit phase of a navigation.
     * This method in turn is responsible for calling the `routerOnActivate` hook of its child.
     */
    public activate(nextInstruction: ComponentInstruction): Promise<any> {
        let previousInstruction = this.currentInstruction;
        this.currentInstruction = nextInstruction;
        if (!refCache.hasRef(nextInstruction.componentType)) {
            let componentType = nextInstruction.componentType;
            let childRouter = this.parentRouter.childRouter(componentType);
            let providers = Injector.resolve([
                provide(RouteData, {useValue: nextInstruction.routeData}),
                provide(RouteParams, {useValue: new RouteParams(nextInstruction.params)}),
                provide(routerMod.Router, {useValue: childRouter})
            ]);
            return this.loader.loadNextToLocation(componentType, this.currentElementRef, providers)
                .then((componentRef) => {
                    refCache.addRef(nextInstruction.componentType, new RefCacheItem(componentRef));
                    this.currentComponentRef = componentRef;
                    if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) {
                        return (<OnActivate>componentRef.instance)
                            .routerOnActivate(nextInstruction, previousInstruction);
                    }
                });
        }
        else {
            let ref = refCache.getRef(nextInstruction.componentType);
            ref.componentRef.location.nativeElement.style.display = 'initial';
            this.currentComponentRef = ref.componentRef;
            return PromiseWrapper.resolve(
                hasLifecycleHook(hookMod.routerOnReuse, this.currentInstruction.componentType) ?
                    (<OnReuse>ref.componentRef.instance).routerOnReuse(nextInstruction, previousInstruction) : true
            );
        }
    }
    /**
     * Called by the Router during the commit phase of a navigation when an outlet
     * reuses a component between different routes.
     * This method in turn is responsible for calling the `routerOnReuse` hook of its child.
     */
    public reuse(nextInstruction: ComponentInstruction): Promise<any> {
        let previousInstruction = this.currentInstruction;
        this.currentInstruction = nextInstruction;
        if (isBlank(this.currentComponentRef)) {
            throw new BaseException(`Cannot reuse an outlet that does not contain a component.`);
        }
        let ref = refCache.getRef(nextInstruction.componentType);
        let currentRef = ref ? ref.componentRef : null;
        return PromiseWrapper.resolve(
            hasLifecycleHook(hookMod.routerOnReuse, this.currentInstruction.componentType) ?
                (<OnReuse>currentRef.instance).routerOnReuse(nextInstruction, previousInstruction) : true
        );
    }
    /**
     * Called by the Router when an outlet disposes of a component's contents.
     * This method in turn is responsible for calling the `routerOnDeactivate` hook of its child.
     */
    public deactivate(nextInstruction: ComponentInstruction): Promise<any> {
        let next = this.resolveToTrue;
        let ref = this.currentComponentRef;
        if (isPresent(ref) && isPresent(this.currentInstruction) &&
            hasLifecycleHook(hookMod.routerOnDeactivate, this.currentInstruction.componentType)) {
            next = PromiseWrapper.resolve(
                (<OnDeactivate>ref.instance)
                    .routerOnDeactivate(nextInstruction, this.currentInstruction));
        }
        return next.then(() => {
            if (isPresent(ref)) {
              if (ref.instance.routerCanReuse && ref.instance.routerCanReuse(nextInstruction, ref.componentType)){
                ref.location.nativeElement.style.display = 'none';
              } else{
                refCache.remove(ref);
              }
            }
        });
    }
    /**
     * Called by the Router during recognition phase of a navigation.
     *
     * If this resolves to `false`, the given navigation is cancelled.
     *
     * This method delegates to the child component's `routerCanDeactivate` hook if it exists,
     * and otherwise resolves to true.
     */
    public routerCanDeactivate(nextInstruction: ComponentInstruction): Promise<boolean> {
        if (isBlank(this.currentInstruction)) {
            return this.resolveToTrue;
        }
        let ref = this.currentComponentRef;
        if (!ref) {
            let foundRef = refCache.getRef(this.currentInstruction.componentType);
            ref = foundRef ? foundRef.componentRef : null;
        }
        if (hasLifecycleHook(hookMod.routerCanDeactivate, this.currentInstruction.componentType)) {
            return PromiseWrapper.resolve(
                (<CanDeactivate>ref.instance)
                    .routerCanDeactivate(nextInstruction, this.currentInstruction));
        }
        return this.resolveToTrue;
    }
    /**
     * Called by the Router during recognition phase of a navigation.
     *
     * If the new child component has a different Type than the existing child component,
     * this will resolve to `false`. You can't reuse an old component when the new component
     * is of a different Type.
     *
     * Otherwise, this method delegates to the child component's `routerCanReuse` hook if it exists,
     * or resolves to true if the hook is not present.
     */
    public routerCanReuse(nextInstruction: ComponentInstruction): Promise<boolean> {
        let result;
        let ref = this.currentComponentRef;
        if (!ref) {
            let foundRef = refCache.getRef(nextInstruction.componentType);
            ref = foundRef ? foundRef.componentRef : null;
        }
        if (isBlank(this.currentInstruction) ||
            this.currentInstruction.componentType !== nextInstruction.componentType) {
            result = false;
        } else if (hasLifecycleHook(hookMod.routerCanReuse, this.currentInstruction.componentType)) {
            result = (<CanReuse>ref.instance)
                .routerCanReuse(nextInstruction, this.currentInstruction);
        } else {
            result = nextInstruction === this.currentInstruction ||
                (isPresent(nextInstruction.params) && isPresent(this.currentInstruction.params) &&
                StringMapWrapper.equals(nextInstruction.params, this.currentInstruction.params));
        }
        return PromiseWrapper.resolve(result);
    }
}