Angular 2 get URL from ComponentInstruction

2019-05-15 01:33发布

I have a custom router-outlet that caches components.

The code for that is this: https://gist.github.com/waeljammal/467286d64f59f8340a93#file-persistentrouteroutlet-ts-L75

The code example uses the component type as the cache key - however, I need mine to cache by the URL so that I can cache two different instances of the same component.

Is there any way to get the URL based on the ComponentInstruction?

1条回答
【Aperson】
2楼-- · 2019-05-15 02:03

So I got this working now. The mistake was that I was applying the PersistenRouterOutlet to the top level router.

Consider the urlPath: /space/settings/general.

I have a router-outlet in my App component, and then I have a WorkspaceComponent that was bound to the space urlPath.

The WorkspaceComponent then hosts its own router-outlet and defines a set of child routes - from the URL, this listens to the settings/general part.

To solve this, I removed persistent-router-outlet from my App component (replaced with the default router-outlet.

Then in the template for the WorkspaceComponent I replaced the default router-outlet with persistent-router-outlet.

Last but not least - the component types that will be instantiated by the child router (inside WorkspaceComponent) needs to implement the CanReuse interface, and implement the routerCanReuse method:

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

The code for the PersistentRouterOutlet is here:

"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);
    }
}
查看更多
登录 后发表回答