So, let's say I have a component, ExampleComponent
, that builds a QueryList
of SomeOtherComponent
components in its content view.
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 {
@ContentChildren(SomeOtherComponent)
someOtherComponents: QueryList<SomeOtherComponent>
}
In its template, I have an NgFor that should insert a <hr />
element after each one.
<ng-container *ngFor="let component of someOtherComponents">
<!-- put component here -->
<hr />
</ng-container>
So that, for example, this (in some other component):
<app-example>
<app-some-other-component>blablabla</app-some-other-component>
<app-some-other-component>hello world</app-some-other-component>
<app-some-other-component>testing</app-some-other-component>
</app-example>
Would result in this (in the HTML):
<app-example>
<app-some-other-component>blablabla</app-some-other-component>
<hr />
<app-some-other-component>hello world</app-some-other-component>
<hr />
<app-some-other-component>testing</app-some-other-component>
<hr />
</app-example>
However, this is where the problem shows up. How do I insert that SomeOtherComponent
instance? ng-content
's select
attribute doesn't support component instances, CDK portals don't like me, I don't want to create some complicated solution using templates and require the user to wrap all of their children in templates... What do I do?
To clarify: I do NOT want to create SomeOtherComponent
instances. I want to do something similar to <ng-content select="app-some-other-component">
, but instead insert a specific INSTANCE (like one inside the QueryList
returned by ContentChildren
). I also don't want to use a directive/template (like putting *thisDirectiveIMadeForJustOneComponentWhichMakesItRequireBeingPlacedInItsOwnModule
on all children).
Note: There are other ways to insert horizontal rules after components, and feel free to mention those, just make sure to answer the question too. This is just an example.
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;
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 ViewContainerRef
s 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!
来源:https://stackoverflow.com/questions/56578385/in-angular-how-do-i-insert-specific-component-instances-without-using-directive