Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 get URL from ComponentInstruction

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?

like image 888
MartinHN Avatar asked Jan 23 '26 16:01

MartinHN


1 Answers

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);
    }
}
like image 100
MartinHN Avatar answered Jan 25 '26 12:01

MartinHN



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!