在Angular中,如何在不使用指令/模板的情况下插入特定的组件实例? [英] In Angular, how do I insert specific component instances without using directives/templates?

查看:53
本文介绍了在Angular中,如何在不使用指令/模板的情况下插入特定的组件实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,假设我有一个组件ExampleComponent,可在其内容视图中构建QueryListSomeOtherComponent组件.

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-contentselect属性不支持组件实例, CDK门户网站不喜欢我,我不想使用模板创建一些复杂的解决方案,并且要求用户将其所有子级都包装在模板中...我该怎么办?

澄清一下:我不想创建SomeOtherComponent实例.我想做类似于<ng-content select="app-some-other-component">的操作,但是要插入一个特定的实例(例如 ContentChildren 返回.我也不想使用指令/模板(例如将*thisDirectiveIMadeForJustOneComponentWhichMakesItRequireBeingPlacedInItsOwnModule放在所有子代上).

注意:还有其他方法可以在组件后插入水平线,并随意提及,只需确保也回答问题即可.这只是一个例子.

解决方案

您的问题是如何插入特定的组件实例?"但是我从您的解释中了解到,您想通过ng-content已插入组件实例的正下方添加行.因为您已经有QueryList个由ContentChildren返回的元素.

从这一点开始,我们需要了解有关ViewContainerRef的一件重要事情;

  1. 本文的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;

  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 :)

这篇关于在Angular中,如何在不使用指令/模板的情况下插入特定的组件实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆