Using TemplateRef to create a tooltip/popover directive in Angular 2

This post is about a Component created in the context of our application development. There is a demo here, and you can find the full source code here.

Lately, the need arose to create a tooltip directive. This brought up a lot of questions we hadn’t had to face before, such as how to create markup wrapping around rendered content, or rather “what is Angular 2’s transclude?”

Turns out, using TemplateRef is very useful for this, but the road to understanding it wasn’t easy. After seeing it used in a similar fashion by Ben Nadel, I decided to take a stab at it.

TemplateRef is used when using <template> elements, or perhaps most commonly when using *-prefixed directives such as NgFor of NgIf. For *-prefixed directives (or directives in <template> elements, TemplateRef can be injected straight into the constructor of the class. For other components, however, they can be queried via something like the ContentChild decorator.

Initially, I had thought to create two directives: a TooltipDirective to be placed on the parent element, plus a TooltipTemplate directive to be placed in a template, that would then inject itself into the parent. It proved too complex, though, and after finding what could be done with the ContentChild query the implementation became much simpler.

The end result looks like this (simplified for clarity):

@Directive({
    selector: "[tooltip]"
})
export class TooltipDirective implements OnInit {
    @Input("tooltip") private tooltipOptions: any;
    @ContentChild("tooltipTemplate") private tooltipTemplate: TemplateRef < Object > ;

    private tooltip: ComponentRef < Tooltip > ;
    private tooltipId: string;

    constructor(
        private viewContainer: ViewContainerRef,
        public elementRef: ElementRef,
        private componentResolver: ComponentResolver,
        private position: PositionService) {
        this.tooltipId = _.uniqueId("tooltip");
    }

    ngOnInit() {
        // Attach relevant events
    }

    private showTooltip() {
        if (this.tooltipTemplate) {
            this.componentResolver.resolveComponent(Tooltip)
                .then(factory => {
                    this.tooltip = this.viewContainer.createComponent(factory);
                    this.tooltip.instance["content"] = this.tooltipTemplate;
                    this.tooltip.instance["parentEl"] = this.elementRef;
                    this.tooltip.instance["tooltipOptions"] = this.options;
                });
        }
    }

    private hideTooltip() {
        this.tooltip.destroy();
        this.tooltip = undefined;
    }

    private get options(): TooltipOptions {
        return _.defaults({}, this.tooltipOptions || {}, defaultTooltipOptions);
    }
}

@Component({
    selector: "tooltip",
    template: `<div class="inner">
<template [ngTemplateOutlet]="content"></template>
</div>
<div class="arrow"></div>`
})
class Tooltip implements AfterViewInit {
    @Input() private content: TemplateRef < Object > ;
    @Input() private parentEl: ElementRef;
    @Input() private tooltipOptions: TooltipOptions;

    constructor(
        private positionService: PositionService,
        public elementRef: ElementRef) {}

    private position() {
        // top and left calculated and set
    }

    ngAfterViewInit(): void {
        this.position();
    }
}

The TooltipDirective requires a <template #tooltipTemplate> element, that gets rendered through a  Tooltip Component, created and injected with the templateRef containing our content. Essentially, “transcluding” it. The Tooltip component’s role is only to wrap the content with some light markup, and position itself when inserted into the page.

A lot of the actual positioning (not shown here, but in the source code) is done directly to the rendered elements, though – I faced some issued when using the host properties object, that I believe were reintroduced in the latest RC.

All in all, it was a great learning experience, and Angular 2’s <template> surely beats Angular.js’ transclude. Slowly but surely Angular 2 get’s more and more demystified to me, but it is hard work getting there.

A breadcrumb component for @ngrx/router

@ngrx/router is, at the moment, one of the best choices for a router component in Angular 2, and Vladivostok, the third iteration of Angular 2’s official router, will take a very heavy inspiration from it. At the moment we are using it to handle our routes, and the need arose to create a breadcrumb component for certain parts of the application.

You can see a working example here.

@ngrx/router‘s API is very light and sparse, but not necessarily incomplete – a lot of the actual implementation is assumed to be left in the hands of the user. This is powered by the use of Observables for Route, RouteParams, QueryParams, etc. A great example of this assumption is the fact that instead of a set of interfaces like CanReuse/CanActivate/CanDeactivate, the router only ever activates components when they change after a route change – any changes in parameters are handled manually by default. More work, but also a clearer image of what one can and cannot do with the tool, and a lot more control.

The first thing we found was that routes have a property – options – that serves the express purpose of holding custom data. A simple usage is this:

export const routes: Route[] = [{
    path: '/',
    component: Home,
    options: {
        breadcrumb: 'Home Sweet Home'
    },
    children: [{
            path: '/component-a',
            component: ComponentA,
            options: {
                breadcrumb: 'A Component'
            },
            children: [{
                    path: '/one',
                    component: One,
                    options: {
                        breadcrumb: 'The One'
                    }
                },
                [...]
            ]
        },
        [...]
    ]
}, ];

And the breadcrumb component is as such:

@Component({
    selector: 'breadcrumbs',
    directives: [NgFor],
    template: `<span>
<span *ngFor="let breadcrumb of breadcrumbs; let isLast = last">
<a [linkTo]="breadcrumb.path">{{breadcrumb.name}}</a>
<span *ngIf="!isLast"> &gt; </span>
</span>
</span>`
})
export class Breadcrumbs {
    private breadcrumbs: any[];

    constructor(private routerInstruction: RouterInstruction) {
        this.routerInstruction
            .subscribe(
                match => {
                    this.breadcrumbs = this.getBreadcrumbs(match);
                }
            );
    }

    private getBreadcrumbs(match: Match) {
        let breadcrumbs = [];
        let path = '';

        for (let i = 0; i < match.routes.length; i++) {
            path = path[path.length - 1] === '/' ? path.substr(0, path.length - 1) : path;
            path += match.routes[i].path ? this.makePath(match.routes[i], match) : '';
            if ((match.routes[i].options || {}).breadcrumb) {
                breadcrumbs.push({
                    name: match.routes[i].options.breadcrumb,
                    path: path
                });
            }
        }

        return breadcrumbs;
    }

    private makePath(route: Route, match: Match) {
        return pathToRegexp.compile(route.path)(match.routeParams);
    }
}

RouterInstruction is one Observable that gives us all the information we need. By watching it, a Match object containing the array of matched routes is returned. All that was left was to create the urls, as @ngrx/router uses only url strings (as opposed to the array notation you’ll find in @angular/router, for instance) – but as @ngrx/router uses path-to-regexp to parse urls, to it was only a matter of using it to compile from the parsed data, and get the urls.

All in all, a very simple solution. Omitted are the use of translations and using asynchronously loaded data (like a profile name) in the breadcrumb – the former is trivial and very unrelated, and the latter we are using stores for, and it’s perhaps a good topic for another post.

Using Class inheritance to hook to Angular 2 component lifecycle

I was thinking of a way to use class inheritance to hook to certain Component lifecycle hooks, without needing to worry about them in the extending class (no knowledge needed, no super() calls to forget about). This does mean “straying off the path”  a little bit, and there may be better ways to do this.

Observables in angular2 are a powerful thing. Unlike the Angular 1 hero, Promises, they represent streams of asynchronous data, and not just single events. This means that a subscription of an observable doesn’t have an end, not necessarily.

Using ngrx/router, I found myself using them a lot, but precisely because they are streams, they need careful cleanup, or we risk leaving a subscription running after a Component has been destroyed.

A typical way we can do this is using ngOnDestroy:

export class Component implements OnDestroy {
    private subscription: Subscription;
    private count: number;

    constructor(private pingService: PingService) {
        let func = this.ngOnDestroy;

        this.subscription = this.pingService.ping
            .subscribe(
                ping => {
                    this.count = ping;
                }
            );
    }

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
}

Simple enough when on its own, but something that is sure to add a lot of code repetition and complexity to a complex class with more than one subscription. We can automate this, and the best way I found was to extend a base class:

export class SafeUnsubscriber implements OnDestroy {
    private subscriptions: Subscription[] = [];

    protected safeSubscription(sub: Subscription): Subscription {
        this.subscriptions.push(sub);
        return sub;
    }

    ngOnDestroy() {
        this.subscriptions.forEach(element => {
            !element.isUnsubscribed && element.unsubscribe();
        });
    }
}

This makes the previous class simpler:

export class Component extends SafeUnsubscriber {
    private count: number;

    constructor(private pingService: PingService) {
        let func = this.ngOnDestroy;

        let subscription = this.pingService.ping
            .subscribe(
                ping => {
                    this.count = ping;
                }
            );

        this.safeSubscription(subscription);
    }
}

Which is great, but what if we need to use ngOnDestroy on the parent? Conventional inheritance allows us to use super.ngOnDestroy() but in this particular case, I don’t want to leave this as a possibility, but rather always unsubscribe on destroy, regardless of wether or not ngOnDestroy was overwritten.

So in this case, a little hack is acceptable, in my opinion – we can make sure the unsubscriber code always runs on ngOnDestroy, and both prevent mistakes by omission and make the code cleaner in the user:

export class SafeUnsubscriber implements OnDestroy {
    private subscriptions: Subscription[] = [];

    constructor() {
        let f = this.ngOnDestroy;

        this.ngOnDestroy = () => {
            f();
            this.unsubscribeAll();
        };
    }

    protected safeSubscription(sub: Subscription): Subscription {
        this.subscriptions.push(sub);
        return sub;
    }

    private unsubscribeAll() {
        this.subscriptions.forEach(element => {
            !element.isUnsubscribed && element.unsubscribe();
        });
    }

    ngOnDestroy() {
        // no-op
    }
}

Now, even if ngOnDestroy gets overwritten, the private method unsubscribeAll still runs, as the constructor (which always runs, as typescript requires it)  makes sure this happens. ngOnDestroy, on the other hand, only exists as a noop function, to ensure the code runs regardless of whether  or not one was set in the parent component.

How does this work, then? Javascript (and typescript, by extension) uses prototypal inheritance, which means that super is the prototype – this is the reason why typescript makes it mandatory to call super() in the extending Class constructor, before any references to this – so class inheritance expectations are guaranteed. By changing this.ngOnDestroy on the Base Class constructor, we are essentially adding a property to the instance, essentially overriding the prototype – which happens to be a call to the prototype’s version followed by our own.

Pretty dangerous stuff, but pretty useful as well.