Angular 2从ComponentInstruction获取URL

时间:2016-03-19 00:19:43

标签: angular angular2-routing

我有一个自定义router-outlet来缓存组件。

代码是:https://gist.github.com/waeljammal/467286d64f59f8340a93#file-persistentrouteroutlet-ts-L75

代码示例使用组件类型作为缓存键 - 但是,我需要通过URL缓存,以便我可以缓存同一组件的两个不同实例。

有没有办法根据ComponentInstruction获取网址?

1 个答案:

答案 0 :(得分:3)

所以我现在就开始工作了。错误在于我将PersistenRouterOutlet应用于顶级路由器。

考虑urlPath: /space/settings/general

我的App组件中有一个router-outlet,然后我有WorkspaceComponent绑定到space urlPath

WorkspaceComponent然后托管自己的router-outlet并定义一组子路由 - 从URL中,它会侦听settings/general部分。

要解决此问题,我从我的应用组件中删除了persistent-router-outlet(替换为默认的router-outlet

然后在WorkspaceComponent的模板中,我将默认router-outlet替换为persistent-router-outlet

最后但并非最不重要的 - 子路由器(在WorkspaceComponent内)实例化的组件类型需要实现CanReuse接口,并实现routerCanReuse方法:

routerCanReuse(next: ComponentInstruction, prev: ComponentInstruction) {
    return next.urlPath === prev.urlPath;
}

PersistentRouterOutlet的代码在这里:

"use strict";

import * as hookMod from 'angular2/src/router/lifecycle/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/lifecycle/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]);
    }
}

/**
 * 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 refCache: RefCache = new RefCache();
    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 (!this.refCache.hasRef(nextInstruction.urlPath)) {
            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) => {
                    this.refCache.addRef(nextInstruction.urlPath, new RefCacheItem(componentRef));

                    this.currentComponentRef = componentRef;

                    if (hasLifecycleHook(hookMod.routerOnActivate, componentType)) {
                        return (<OnActivate>componentRef.instance)
                            .routerOnActivate(nextInstruction, previousInstruction);
                    }
                });
        }
        else {
            let ref = this.refCache.getRef(nextInstruction.urlPath);
            ref.componentRef.location.nativeElement.hidden = false;

            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 = this.refCache.getRef(nextInstruction.urlPath);
        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)) {
                ref.location.nativeElement.hidden = true;
            }
        });
    }

    /**
     * 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 = this.refCache.getRef(this.currentInstruction.urlPath);
            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 = this.refCache.getRef(nextInstruction.urlPath);
            ref = foundRef ? foundRef.componentRef : null;
        }

        if (isBlank(this.currentInstruction) || !this.refCache.hasRef(nextInstruction.urlPath) || 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);
    }
}