Skip to content

Instantly share code, notes, and snippets.

@vmasek
Created July 25, 2019 17:56
Show Gist options
  • Save vmasek/271c40be40160c8c09d20e9daaa1a561 to your computer and use it in GitHub Desktop.
Save vmasek/271c40be40160c8c09d20e9daaa1a561 to your computer and use it in GitHub Desktop.
let dynamic directive
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
/**
* This interface represents context of the directive.
*
* It defines two properties ($implicit and viLet) which enables same behavior with different syntax.
* It is not required to have both, it is just a way to provide support for both syntax.
*/
interface LetContext<T extends { [key: string]: unknown }> {
/**
* Current item exposed in implicit value variable.
* This enables us to use the let syntax
*
* @example
* <ng-container *viLet="data$ | async; let data">
* Data: {{data}}
* </ng-container>
*/
$implicit?: T; // current item exposed as implicit value
/**
* Current item exposed as key matching the directive name.
* This adds support for `as` syntax in template.
*
* @example
* <ng-container *viLet="data$ | async as data">
* Data: {{data}}
* </ng-container>
*/
viLet?: T;
[key: string]: unknown;
}
@Directive({
selector: '[viLet]',
})
export class LetDirective<T> {
private readonly context: LetContext<T> = {};
constructor(
private readonly viewRef: ViewContainerRef,
private readonly templateRef: TemplateRef<LetContext<T>>,
) {
this.viewRef.createEmbeddedView(this.templateRef, this.context);
}
@Input()
set viLet(o: T) {
this.context.viLet = this.context.$implicit = o;
Object.entries(o || {}).map(([key, value]) => (this.context[key] = value));
}
}
<h1
*viLet="{
click: mouseClick$ | async,
move: mouseMove$ | async,
interval: interval$ | async
} as mouse"
>
click [{{ mouse?.click?.screenX }}, {{ mouse?.click?.screenY }}]
<br />
move [{{ mouse?.move?.screenX }}, {{ mouse?.move?.screenY }}]
<br />
seconds from start {{ mouse?.interval }}
<br />
</h1>
<h1
*viLet="
{
click: mouseClick$ | async,
move: mouseMove$ | async,
interval: interval$ | async
};
click as click;
move as move;
interval as interval
"
>
click [{{ click?.screenX }}, {{ click?.screenY }}]
<br />
move [{{ move?.screenX }}, {{ move?.screenY }}]
<br />
seconds from start {{ interval }}
<br />
</h1>
<h1
*viLet="
{
click: mouseClick$ | async,
move: mouseMove$ | async,
interval: interval$ | async
};
let click = click;
let move = move;
let interval = interval
"
>
click [{{ click?.screenX }}, {{ click?.screenY }}]
<br />
move [{{ move?.screenX }}, {{ move?.screenY }}]
<br />
seconds from start {{ interval }}
<br />
</h1>
<ng-container *viLet="(mouseClick$ | async) as click">
<ng-container *viLet="(mouseMove$ | async) as move">
<h1 *viLet="(interval$ | async) as interval">
click [{{ click?.screenX }}, {{ click?.screenY }}]
<br />
move [{{ move?.screenX }}, {{ move?.screenY }}]
<br />
seconds from start {{ interval }}
<br />
</h1>
</ng-container>
</ng-container>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment