13

I have this

https://angular-dynamic-component-append.stackblitz.io/

I managed to dynamically append an element, but it doesn't get compiled. I saw many tutorials like this

But it's not really what I need. And often they use the hashtag notation to identify the container.

I need to append a component to any element which may have my custom directive on it.

I'd also need to use the bind value of the directive to control a [hidden] attribute on the appended element.

THE GOALS

  1. Override behaviour of existing component:
    • adding an attribute to show/hide
    • adding a class to customize appearance
  2. Reduce html coding
    • No need to write the entire component <my-comp></mycomp>
    • No need to know the class
    • Automatic behaviour if the class name is changed
      1. Changing the element on which the directive is applied
    • The final goal will be to add a class to the contaner element

Expected source

<div [myDirective]="myBoolean">
    <p>some content</p>
</div>

Expected compiled

<div [myDirective]="myBoolean" class="myDirectiveClass1">
    <p>some content</p>
     <someComponent [hidden]="myBoolean" class="myDirectiveClass2"></someComponent>
</div>

Is there a way to achieve this?

Thank you in advance

7
  • It is a much more complex and convoluted process than what you have tried. In your case it is just a <mat-card> so you don't need to do it at all. But if you need to dynamically create an element at runtime without registering it ahead of time, you are hosed. Commented Jan 23, 2018 at 21:26
  • what do you mean it doesn't get compiled? Commented Jan 23, 2018 at 22:52
  • @LaurentSchwitter: does the mat-card above look the same as the mat-card below in my fiddle? Did you ask yourself why? Commented Jan 24, 2018 at 7:46
  • 1
    @AluanHaddad Sorry but I don't understand "hosed" I can't find a translation...what do you mean? (I'm italian :D ) Commented Jan 24, 2018 at 7:49
  • 1
    @AluanHaddad I managed to do everything I needed. As soon as I can I'll answer this question. Commented Jan 24, 2018 at 16:05

3 Answers 3

8

It's pretty simple. I just made an example to you.

Please, read the comments inside loader directive.

https://github.com/garapa/studying/tree/master/loader

EDIT:

You component:

export class LoaderComponent {

  loading;

  constructor() { }

}

Your directive

export class LoaderDirective implements OnDestroy {

  private componentInstance: ComponentRef<LoaderComponent> = null;

  @Input()
  set appLoader(loading: boolean) {
    this.toggleLoader(loading);
  }

  constructor(
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) { }

  toggleLoader(loading: boolean) {
    if (!this.componentInstance) {
      this.createLoaderComponent();
      this.makeComponentAChild();
    }

    this.componentInstance.instance.loading = loading;
  }

  private createLoaderComponent() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(LoaderComponent);
    this.componentInstance = this.viewContainerRef.createComponent(componentFactory);
  }

  private makeComponentAChild(){
    const loaderComponentElement = this.componentInstance.location.nativeElement;
    const sibling: HTMLElement = loaderComponentElement.previousSibling;
    sibling.insertBefore(loaderComponentElement, sibling.firstChild);
  }

  ngOnDestroy(): void {
    if (this.componentInstance) {
      this.componentInstance.destroy();
    }
  }

}

You module

@NgModule({
  ...
  entryComponents: [
    LoaderComponent
  ]
})
Sign up to request clarification or add additional context in comments.

3 Comments

This is nice, but I can't believe I need to create a "getElement" method on each parent component...
I managed to do everything I needed. As soon as I can I'll answer this question. I was inspired by your code. The problem here is that you need to know you are in the "LoaderComponent". I wanted something more "universal". Let me know if you want to be tagged when I answer. Bye and thank you
I just upped your answer, since it was useful ;)
3

Here's the way I got it working

import {
  Renderer2,
  Directive,
  Input,
  ElementRef,
  OnChanges,
  ViewEncapsulation
} from "@angular/core";
import { MatSpinner } from "@angular/material";

@Directive({
  selector: "[myDirective]"
})
export class MyDirective {

  @Input()
  set myDirective(newValue: boolean) {
    console.info("myDirectiveBind", newValue);
    if (!!this._$matCard) {
      const method = newValue ? "removeClass" : "addClass";
      this.renderer[method](this._$matCard, "ng-hide");
    }
    this._myDirective = newValue;
  }

  private _myDirective: boolean;
  private _$matCard;

  constructor(private targetEl: ElementRef, private renderer: Renderer2) {
    this._$matCard = this.renderer.createElement('mat-card');
    const matCardInner = this.renderer.createText('Dynamic card!');
    this.renderer.addClass(this._$matCard, "mat-card");
    this.renderer.appendChild(this._$matCard, matCardInner);
    const container = this.targetEl.nativeElement;
    this.renderer.appendChild(container, this._$matCard);
  }


}

import {
  Component,
  ElementRef,
  AfterViewInit,
  ViewEncapsulation
} from '@angular/core';

@Component({
  selector: 'card-overview-example',
  templateUrl: 'card-overview-example.html',
  styleUrls: ['card-overview-example.css']
})
export class CardOverviewExample {
  
  hideMyDirective = !1;

  constructor(private _elementRef: ElementRef) { }

  getElementRef() {
    return this._elementRef;
  }

  ngAfterViewInit() {
    let element = this._elementRef.nativeElement;
    let parent = element.parentNode;
    element.parentNode.className += " pippo";

  }
}
.ng-hide {
  display: none;
}
<mat-card>Simple card</mat-card>
<div class="text-center">
  <button (click)="hideMyDirective = !hideMyDirective">
    Toggle show dynamic card
</button>
</div>
<br />
<span>hideMyDirective: {{hideMyDirective}}</span>
<hr />
<div class="myDiv" [myDirective]="hideMyDirective">
    <ul>
      <li>My content</li>
      </ul>
</div>

3 Comments

When mat-card is returned, does this also apply the angular material css ? Because when i tried to do something same, the css is not applied. Thanks in advance...
I have to make a blitz with this code...honestly I don't remember. I use different approaches now to dinamically append components!
This method is only good for attaching elements to the DOM. It does not compile component. This particular instance may appear to work because the mat-card component does little more than add classes; which are being added here manually.
1

Inside the component html file to which component is to be inserted:

<div #target>
</div>

Inside the component ts file to which component is to be inserted:

'Component_to_insert' -> is the component to be inserted inside another component.

import { Component_to_insert } from 'path';
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, AfterViewInit } from '@angular/core';

@Component({
selector: 'component-name',
templateUrl: 'component.html',
styleUrls: ['component.scss'],
entryComponents: [Component_to_insert]
})

export class ManagetemplatesPanelComponent implements AfterViewInit {

    @ViewChild('target', { read: ViewContainerRef }) entry: ViewContainerRef;

    constructor(private resolver: ComponentFactoryResolver) { }

    ngAfterViewInit() {
     this.createComponent();
    }

    createComponent() {
      this.entry.clear();
      const factory = this.resolver.resolveComponentFactory(Component_to_insert);
      const componentRef = this.entry.createComponent(factory);
    }
}

3 Comments

This doesn't quite answer. I already came across this approach. And it works. But what I needed was something universal, which doesn't require to know the component to insert. More or less something working as the $compile worked in AngularJS. It was enough to append the HTML and compile it to make it work..
The approach has been good in Angular 6+ versions. It was an effective method for showing components without including components inside children with angular routing.
It works even on Angular 4 and 5. I'm not saying it's not an effective method. But it doesn't do what I asked.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.