在Angular中,如何不使用指令/模板插入特定的组件实例?

时间:2019-06-13 10:20:35

标签: angular angular-components

因此,假设我有一个组件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的{​​{1}}属性不支持组件实例,CDK portals不喜欢我,我不想使用模板创建一些复杂的解决方案,并且要求用户包装所有内容他们的孩子在模板中...我该怎么办?

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

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

2 个答案:

答案 0 :(得分:6)

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

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

  1. ViewContainerRef section of this article
  

有趣的是,Angular不会在元素内插入视图,而是将其追加到绑定到ViewContainer的元素之后。

因此,如果我们可以访问ViewContainerRefQueryList个元素,则可以轻松地向这些元素追加新元素。而且我们可以使用ViewContainerRef查询的read元数据属性来访问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>

演示here

通过使用这种方法来创建指令,以满足您对<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查询与模板选择器一起工作的方法。如here所述,我们可以将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>

演示here

我希望我能够正确理解您的要求,并且所有这些都对您有所帮助:)

答案 1 :(得分:-2)

据我对您问题的理解,听起来您想动态插入组件。这可以通过将所需的SomeOtherComponent的数量推入数组中,然后在ngFor内创建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 {
    someOtherComponents: any[];
    constructor(){
    this.someOtherComponents.push({data: "insert_stuff_here"});
    this.someOtherComponents.push({data: "insert_stuff_here"})
    }
}

然后在您的ngFor中

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

希望这就是您要寻找的!