7

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.

7
  • why not create a simple <ng-template><span>{{inputString</span></ng-template>, and then using the createEmbeddedView function? Commented Nov 12, 2020 at 18:55
  • 1
    Where would I create it? A directive has no template. Commented Nov 12, 2020 at 18:57
  • 1
    I understand your point, but I can't think of a valid use case. Could you elaborate? Commented Nov 12, 2020 at 19:06
  • I'm creating something akin to *ngFor, but with a separator between (like a .join). Commonly, a separator is a simple string, like a comma. I'd like to be able to write <x *ngJoin="let item of items; separator: ','"></x> instead of <x *ngJoin="let item of items; separator: separatorTpl"></x> <ng-template #separatorTpl>,</ng-template>. Also I'd like to provide comma as the default value of @Input() separator. If its type is a TemplateRef, there's no way of giving it a default value (impossible to create TemplateRef from code -- or is it?). Commented Nov 12, 2020 at 19:49
  • You want to add some deafult value inside ng-template if it's a type TemplateRef? Commented Nov 18, 2020 at 19:04

2 Answers 2

2
+50

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This seems to do the trick, thanks. The issue with pipe is that, while a separator is a string, the elements themselves are in my case not strings, but custom chunks of HTML.
0

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.

2 Comments

See the fourth comment on the question. Also your answer is already mentioned in my question.
Also not sure about the statement “ViewContainerRef can render only components”. It can render views as well (createEmbeddedView). Maybe Renderer can help somehow? There were talks and promises by the team that Ivy would allow us to create templates in code, but I don't see it anywhere now that Ivy is stable -- have these ideas been discarded?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.