How to render a string from structural directive?

Viewed 386

ViewContainerRef offers methods createEmbeddedView (which accepts TemplateRef, for rendering templates) and createComponent (which accepts ComponentFactory, for rendering components). But what about a simple string?

The only workaround I see is to have a DummyComponent and then feed a document.createTextNode('string') to projectableNodes.

@Component({ template: `<ng-content></ng-content>` }) class DummyComponent {}
const dummyComponentFactory = this.cfr.resoveComponentFactory(DummyComponent)
const nodes = [[document.createTextNode('string')]]
this.viewContainerRef.createComponent(dummyComponentFactory, 0, injector, nodes)

But this is just abusing the API and has an enormous overhead for rendering a simple string.

2 Answers

If you really want to achieve it via directive, its possible. Please refer to directive code below :-

import {
  Directive,
  Input,
  NgIterable,
  OnChanges,
  OnInit,
  Renderer2,
  TemplateRef,
  ViewContainerRef
} from "@angular/core";

@Directive({
  selector: "[ngJoin]"
})
export class NgJoinDirective<T, U extends NgIterable<T> = NgIterable<T>>
  implements OnInit, OnChanges {
  @Input() ngJoinOf: any[];
  @Input() ngJoinSeparator: string = ",";
  constructor(
    private _viewContainer: ViewContainerRef,
    private _template: TemplateRef<any>,
    private renderer2: Renderer2
  ) {}

  ngOnChanges() {
    this.createText();
  }

  ngOnInit() {
    this.createText();
  }

  createText() {
    if (this.ngJoinOf) {
      this._viewContainer.clear();
      console.log(this.ngJoinOf);
      const container = this._template.createEmbeddedView({});
      const parentNode = container.rootNodes[0];
      const textNode = this.renderer2.createText(
        this.ngJoinOf.join(this.ngJoinSeparator)
      );
      this.renderer2.insertBefore(parentNode, textNode, parentNode.firstChild);
      this._viewContainer.insert(container);
    }
  }
}

and used in html like below :-

<p *ngJoin="let item of arr;separator: '/'">

if you will not give separator it will take , as default separator.

Working Stackblitz :-

https://stackblitz.com/edit/angular-rumkzc?file=src/app/app.component.html

If you are just looking to print array via join in template using a separator, pipe will be a ideal choice instead of directive. Such a pipe is provided by a library.

https://github.com/fknop/angular-pipes

The pipe name is join.

Note :- I have not handled, any edge cases, you might want to handle.

Why not using a pipe to transform your data?

In any case, ViewContainerRef can render only components, so - you can create a component with an Input of the string you want to show, and create it dynamically. Something like that:

@Component({ template: `{{text}}` }) class DummyComponent {
   @Input text: string;
}

But I think a pipe is more suitable for your situation.

Related