Dynamically append component to div in Angular 5

35,681

Solution 1

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>

Solution 2

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
  ]
})

Solution 3

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);
    }
}
Share:
35,681
Sampgun
Author by

Sampgun

I'm a Web Developer in Bergamo. I'm developing with Angular6+ and Material and Ionic5+ applications at this time. I deal with frontend architecture and development of web applications. I also like programming with Java, PHP, AngularJS, Bootstrap 3+ and Vue 2+. I enjoy discovering new technologies and create plugins and reusable components. I know how to get the best from CMS like Wordpress, creating themes and plugins to extend its functionalities. I often deal with backend technologies, especially Java and NodeJS (NestJS).

Updated on July 09, 2022

Comments

  • Sampgun
    Sampgun almost 2 years

    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

  • Sampgun
    Sampgun over 6 years
    This is nice, but I can't believe I need to create a "getElement" method on each parent component...
  • Sampgun
    Sampgun over 6 years
    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
  • Sampgun
    Sampgun over 6 years
    I just upped your answer, since it was useful ;)
  • Dhiraj Dhakal
    Dhiraj Dhakal over 5 years
    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...
  • Sampgun
    Sampgun over 5 years
    I have to make a blitz with this code...honestly I don't remember. I use different approaches now to dinamically append components!
  • Sampgun
    Sampgun about 5 years
    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..
  • Kanish Mathew
    Kanish Mathew about 5 years
    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.
  • Sampgun
    Sampgun about 5 years
    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.
  • iamsar
    iamsar over 2 years
    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.