In Angular, how do I insert specific component instances without using directives/templates?

人走茶凉 提交于 2019-12-03 15:48:56

your question is "how do I insert specific component instances?" but what i understand from your explanation is that, you want to add lines just under the already inserted component instances via ng-content. Because you already have a QueryList of elements returned by ContentChildren.

From this point on we need to understand one important thing about ViewContainerRef;

  1. ViewContainerRef section of this article

What’s interesting is that Angular doesn’t insert views inside the element, but appends them after the element bound to ViewContainer.

So if we can access to ViewContainerRef's of elements in our QueryList we can easily append new elements to those elements. And we can access ViewContainerRef s of elements by using read metadata property of ContentChildren query;

@ContentChildren(SomeOtherComponent, { descendants: true, read: ViewContainerRef }) someOtherComponents: QueryList<ViewContainerRef>;

since we have ViewContainerRefs of our elements we can easily append new elements to these by using createEmbeddedView()

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent implements AfterContentInit {
  @ViewChild("templateToAppend", {static: true}) templateToAppend: TemplateRef<any>;
  @ContentChildren(SomeOtherComponent, { descendants: true, read: ViewContainerRef }) someOtherComponents: QueryList<ViewContainerRef>;

  ngAfterContentInit() {
    this.someOtherComponents.forEach(ap => ap.createEmbeddedView(this.templateToAppend));
  }
}

template

<ng-content></ng-content>

<ng-template #templateToAppend>
    <hr style="color: blue"/>
</ng-template>

demo here

by utilizing this approach to create a directive in order to achive your requirement of having something similar to <ng-content select="app-some-other-component">

we can create a directive that takes a TemplateRef as @Input() and appends it to the ViewContainerRef

export class CustomAppendable { }

@Directive({
  selector: '[appMyCustomAppender]'
})
export class MyCustomAppenderDirective {
  @ContentChildren(CustomAppendable, { descendants: true, read: ViewContainerRef }) appendables: QueryList<ViewContainerRef>;
  @Input() appMyCustomAppender: TemplateRef<any>;

  constructor() { }

  ngAfterContentInit() {
    setTimeout(() => {
      this.appendables.forEach(ap => ap.createEmbeddedView(this.appMyCustomAppender));
    });
  }
}

with this approach, in order not to create tight coupling between our SomeOtherComponent and our directive we make our components somehow generic by creating a common type CustomAppendable and use it as an alias for the components that we want to query in ContentChildren

NOTE: i couldn't find a way to make ContentChildren query work with template selectors. As explained here we can use ContentChildren with template reference variables or Component Types. that's why i created the alias.

@Component({
  selector: 'app-some-other-component',
  templateUrl: './some-other-component.component.html',
  styleUrls: ['./some-other-component.component.css'],
  providers: [{ provide: CustomAppendable, useExisting: SomeOtherComponent }]
})
export class SomeOtherComponent implements OnInit {

  constructor() { }

  ngOnInit() {}

}

also with this approach we don't need the container component and apply our directive any element.

<div [appMyCustomAppender]="templateToAppend">
  <app-some-other-component>underlined</app-some-other-component>
  <app-some-other-component>underlined</app-some-other-component>
  <app-some-other-component2>not underlined</app-some-other-component2>
  <br />
  <app-some-other-component2>not underlined</app-some-other-component2>
</div>
<br />
<app-some-other-component>but not underlined!</app-some-other-component>

<ng-template #templateToAppend>
  <hr  style="color: red"/>
  <br />
</ng-template>

demo here

i am hoping that i was able to understand your requirements correctly and all these are helpful somehow :)

From my understanding of your question it sounds like you want to dynamically insert components. This can be done by pushing the amount of SomeOtherComponent's you want into an array then inside your ngFor you create the someOtherComponent like so:

import {Component, ContentChildren, QueryList} from '@angular/core'
import {SomeOtherComponent}                    from '../some-other-component/some-other-component.component'

@Component({
    selector   : 'app-example',
    templateUrl: './example.component.html',
    styleUrls  : ['./example.component.css']
})
export class ExampleComponent {
    someOtherComponents: any[];
    constructor(){
    this.someOtherComponents.push({data: "insert_stuff_here"});
    this.someOtherComponents.push({data: "insert_stuff_here"})
    }
}

Then in your ngFor

 <ng-container *ngFor="let component of someOtherComponents">
    <someOtherComponent [possibleInputHere]="component.data"></someOtherComponent>
    <hr />
</ng-container>

I hope this is what you're looking for!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!