-
-
Save KEIII/e55c99baceb89c0afb32d6bd528e7ca7 to your computer and use it in GitHub Desktop.
// tslint:disable:no-any directive-selector | |
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; | |
/** | |
* Declare a variable in the template. | |
* Eg. <i *ngVar="false as variable">{{ variable | json }}</i> | |
*/ | |
@Directive({selector: '[ngVar]'}) | |
export class NgVarDirective { | |
public context: any = {}; | |
constructor( | |
private vcRef: ViewContainerRef, | |
private templateRef: TemplateRef<any>, | |
) {} | |
@Input() | |
set ngVar(context: any) { | |
this.context.$implicit = this.context.ngVar = context; | |
this.updateView(); | |
} | |
private updateView() { | |
this.vcRef.clear(); | |
this.vcRef.createEmbeddedView(this.templateRef, this.context); | |
} | |
} |
The biggest problem it calls vcRef.clear()
, so this may break a lot of things, for example, if your host component has ViewChild (for example, you created a canvas and accessed canvas context). When variable change, your directive is going to recreate view (destroying currently existing canvas and creating new one). Parent element variable containing CanvasContext will point old and detached canvas instead of created new one. So, use carefully.
p.s. You can also do it this way. It will work but i don't think it's very legal since view context is readonly (so it may became broken with angular updates)
Anyway it will keep previous view and just update child view contexts.
interface AppVariableContext {
$implicit: any;
ngVar: any;
}
@Directive({ selector: '[ngVar]' })
export class NgVarDirective implements OnInit, OnChanges {
@Input('ngVar') value: any;
view!: EmbeddedViewRef<any>;
context: AppVariableContext = {} as any;
constructor(private vcRef: ViewContainerRef, private templateRef: TemplateRef<any>, private cd: ChangeDetectorRef) {}
ngOnInit() {
this.updateContext();
this.view = this.vcRef.createEmbeddedView(this.templateRef, this.context);
}
ngOnChanges() {
if (this.view) {
this.updateContext();
(this.view.context as AppVariableContext).$implicit = this.context.$implicit;
(this.view.context as AppVariableContext).ngVar = this.context.ngVar;
this.view.detectChanges(); // markForCheck?
}
}
private updateContext() {
this.context = {
$implicit: this.value,
ngVar: this.value,
};
}
}
If anyone cares, here is a version, that will retain the type of the variable inside the template:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
interface VarContext<T> {
$implicit: T;
var: T;
}
/**
* Declare a variable in the template.
* Eg. <i *var="false as variable">{{ variable | json }}</i>
*/
@Directive({selector: '[var]'})
export class VarDirective<T = unknown> {
public context: VarContext<T> = {} as any;
constructor(
private vcRef: ViewContainerRef,
private templateRef: TemplateRef<any>,
) {}
@Input()
set var(context: T) {
this.context.$implicit = this.context.var = context;
this.updateView();
}
private updateView() {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.templateRef, this.context);
}
static ngTemplateContextGuard<T>(dir: VarDirective<T>, ctx: any): ctx is VarContext<T> {
return true;
}
}
Not this way. But it works: