Skip to content

Instantly share code, notes, and snippets.

@KEIII
Last active November 24, 2021 08:36
Show Gist options
  • Save KEIII/e55c99baceb89c0afb32d6bd528e7ca7 to your computer and use it in GitHub Desktop.
Save KEIII/e55c99baceb89c0afb32d6bd528e7ca7 to your computer and use it in GitHub Desktop.
Missed Angular *ngVar directive (from https://stackoverflow.com/a/43172992/1847657)
// 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);
}
}
@nullifiedtsk
Copy link

nullifiedtsk commented Feb 28, 2021

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,
    };
  }
}

@Akxe
Copy link

Akxe commented Nov 24, 2021

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;
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment