14

I have simplest Angular structural directive:

import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[hello]' })
export class HelloDirective {
  constructor(
    private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) {
      this.viewContainer.createEmbeddedView(this.templateRef);
  }
}

I use it this way:

<div *hello>Hello Directive</div>

It shows me "Hello Directive" message as expected. Now I want to change the content by wrapping it with some another component:

<my-component>Hello Directive</my-component>

And I want the directive to do it for me. I know that I can use a Component paradigm and create HelloComponent instead of HelloDirective and use ng-template etc with the template defined by template or templateUrl property on the @Component decorator... But is there an approach that could be used with a Directive paradigm to achieve such a result?

dhilt
  • 18,707
  • 8
  • 70
  • 85
  • Do you want to transclude directive's contents into component? – yurzui Nov 18 '17 at 04:02
  • @yurzui Yes, and then pass the result into the initial template instead of directive's content. So that it could be interpreted as `
    Hello Directive
    ` at the end (instead of `
    Hello Directive
    `).
    – dhilt Nov 18 '17 at 04:13
  • Does `my-component` contain `ng-content`? – yurzui Nov 18 '17 at 04:16
  • @yurzui It could be fully customized to provide proper behavior. No limitations on `my-component` implementation in this task. – dhilt Nov 18 '17 at 04:28

1 Answers1

17

You can create component dynamically and pass projectable nodes to it. So it could look like

@Directive({ selector: '[hello]' })
export class HelloDirective implements OnInit, DoCheck {
  templateView: EmbeddedViewRef<any>;
  constructor(
      private templateRef: TemplateRef<any>,
      private viewContainer: ViewContainerRef,
      private resolver: ComponentFactoryResolver) {
  }

  ngOnInit() {
    this.templateView = this.templateRef.createEmbeddedView({});
    const compFactory = this.resolver.resolveComponentFactory(MyComponent);
    this.viewContainer.createComponent(
      compFactory, null, this.viewContainer.injector, [this.templateView.rootNodes])
  }

  ngDoCheck(): void {
    if (this.templateView) {
        this.templateView.detectChanges();
    }
  }
}

You have to add MyComponent to entryComponents array of your @NgModule

Complete example can be found on Stackblitz

See also

Reza
  • 18,865
  • 13
  • 88
  • 163
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • If I use an nested component inside hello directive, it doesn't work. Do you have a sugesstion to fix that: https://stackblitz.com/edit/angular-97ckey?embed=1&file=app/app.component.html – Murat Çorlu Jun 14 '18 at 10:13
  • 2
    @MuratÇorlu Did you mean hello component didn't get input? That's because its view is not part of angular change detection tree. Try this https://stackblitz.com/edit/angular-jrtnmn?file=app/app.component.ts – yurzui Jun 14 '18 at 10:21
  • Great, thanks. Another question is, now hello component in hello directive is initializing when directive is embedded the content, but not destroying when content is removed: https://stackblitz.com/edit/angular-v3a4gn?file=app%2Fapp.component.html do you also have a suggestion to destroy hello component when we remove content from dom? – Murat Çorlu Jun 14 '18 at 11:29
  • @yurzui I have had a dx-text-box from devextreme, and code was not working, after I fixed the issue I updated your answer (since many developers copy paste and they want it to work out of the box) – Reza Jul 17 '20 at 02:12