在Angular中,如何在不使用指令/模板的情况下插入特定的组件实例? [英] In Angular, how do I insert specific component instances without using directives/templates?
问题描述
因此,假设我有一个组件ExampleComponent
,可在其内容视图中构建QueryList
个SomeOtherComponent
组件.
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>
}
在其模板中,我有一个NgFor,应该在每个NgFor之后插入一个<hr />
元素.
<ng-container *ngFor="let component of someOtherComponents">
<!-- put component here -->
<hr />
</ng-container>
例如,这个(在其他组件中):
<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>
会导致此结果(在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>
但是,这就是问题所在.如何插入该SomeOtherComponent
实例? ng-content
的select
属性不支持组件实例, CDK门户网站不喜欢我,我不想使用模板创建一些复杂的解决方案,并且要求用户将其所有子级都包装在模板中...我该怎么办?
澄清一下:我不想创建SomeOtherComponent
实例.我想做类似于<ng-content select="app-some-other-component">
的操作,但是要插入一个特定的实例(例如 ContentChildren
返回.我也不想使用指令/模板(例如将*thisDirectiveIMadeForJustOneComponentWhichMakesItRequireBeingPlacedInItsOwnModule
放在所有子代上).
注意:还有其他方法可以在组件后插入水平线,并随意提及,只需确保也回答问题即可.这只是一个例子.
您的问题是如何插入特定的组件实例?"但是我从您的解释中了解到,您想通过ng-content
在已插入组件实例的正下方添加行.因为您已经有QueryList
个由ContentChildren
返回的元素.
从这一点开始,我们需要了解有关ViewContainerRef的一件重要事情;
有趣的是,Angular不会在元素内部插入视图,而是将其追加到绑定到ViewContainer的元素之后.
因此,如果我们可以访问QueryList
中元素的ViewContainerRef
个元素,则可以轻松地将新元素追加到这些元素中.并且我们可以使用 read ViewContainerRef个> ContentChildren
查询的元数据属性;
@ContentChildren(SomeOtherComponent, { descendants: true, read: ViewContainerRef }) someOtherComponents: QueryList<ViewContainerRef>;
因为我们有元素的ViewContainerRef
个,所以可以使用 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));
}
}
模板
<ng-content></ng-content>
<ng-template #templateToAppend>
<hr style="color: blue"/>
</ng-template>
演示此处 >
通过使用这种方法来创建指令,以满足您对<ng-content select="app-some-other-component">
我们可以创建一个以TemplateRef
作为@Input()
的指令,并将其附加到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));
});
}
}
使用这种方法,为避免在我们的SomeOtherComponent
与指令之间建立紧密的联系,我们通过创建一个通用类型CustomAppendable
使我们的组件具有某种通用性,并将其用作我们要查询的组件的别名在ContentChildren
注意::我找不到使ContentChildren
查询与模板选择器一起工作的方法.如此处中所述,我们可以将ContentChildren
与模板引用变量或组件类型一起使用.这就是为什么我创建别名的原因.
@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() {}
}
使用这种方法,我们也不需要容器组件,也不需要将指令应用于任何元素.
<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>
演示此处
我希望我能够正确理解您的要求,并且所有这些对您有帮助:)
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 :)
这篇关于在Angular中,如何在不使用指令/模板的情况下插入特定的组件实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!