【问题标题】:does angular content projection inside *ngFor share same data? How to access project content of child component from parent?*ngFor 内的角度内容投影是否共享相同的数据?如何从父组件访问子组件的项目内容?
【发布时间】:2021-04-20 19:49:01
【问题描述】:

目前我试图将第三个组件投影到一个子组件中,该子组件被投影在ngFor 循环内(在子组件内),但是每当我使用查询列表的索引更改或设置投影内容中的某些属性时在父组件中 (ViewChildren('#thirdComponent')) 在父级中所有子级的投影内容都显示相同的变化。有什么合适的方法吗?

是不是因为子组件的内容投影位置重复了select属性绑定。子组件的投影是在一次打开一个或多个面板的手风琴内完成的。

@Component({
  selector: "my-app",
  template: `
    <child-comp #child>
      <ng-container selected>
        <some-other-comp #someOtherComp></some-other-comp>
      </ng-container>
    </child-comp>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
  h = 0;
  i = 1;
  j = 2;
  k = 3;
  @ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;

  ngAfterViewInit(): void {
    this.otherCompList.toArray()[this.h].prop = this.h;
    // below will result in undefined due to QueryList of size 1 
    // this.otherCompList.toArray()[this.i].prop = this.i;
    // this.otherCompList.toArray()[this.j].prop = this.j;
    // this.otherCompList.toArray()[this.k].prop = this.k;
  }
}

@Component({
  selector: "child-comp",
  template: `
    <div *ngFor="let value of [1, 2, 3]; let i = index">
      <!-- if ngIf is removed than only the last projection is diplayed -->
      <div *ngIf="i === 0">
        <ng-content select="[selected]"> </ng-content>
      </div>
    </div>
  `,
  styleUrls: ["./app.component.css"]
})
export class ChildComponent {}

@Component({
  selector: "some-other-comp",
  template: `
    <p>{{ prop }}</p>
  `,
  styleUrls: ["./app.component.css"]
})
export class SomeOtherComponent {
  prop: any;
}

Stackblitz

【问题讨论】:

    标签: javascript angular


    【解决方案1】:

    利用 *ngTemplateOutlet 和 let-variables

    我们可以将模板传递给我们的子组件,并利用@Input() 装饰器和*ngTemplateOutlet 直接访问父组件中HTML 模板的属性。

    示例

    首先,我在我的父组件中定义了一个数组,我想将它用作我的外部子组件中循环的基础。

    父组件

    @Component({
      selector: 'parent',
      templateUrl: 'parent.component.html',
      styleUrls: ['parent.component.scss']
    })
    export class ParentComponent implements OnInit {
    
      dataItems: { title: string, description: string }[] = [{
        title: 'First Element',
        description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nihil!'
      }...] // remaining items truncated for brevity.
    
      constructor() {
      }
    
      ngOnInit(): void {
      }
    
    }
    

    这个父组件然后有一个子组件,它接受整个项目列表的输入

    <child [items]="dataItems"></child>
    

    子组件(第一级)

    @Component({
      selector: 'child',
      templateUrl: 'child.component.html',
      styleUrls: ['child.component.scss']
    })
    
    export class ChildComponent implements OnInit {
      @Input() items!: any[];
      
      constructor() {
      }
    
      ngOnInit(): void {
      }
    }
    
    <ng-container *ngFor="let childItem of items">
      <projected [item]="childItem">
        <ng-template let-item>
          <h4>{{item.title}}</h4>
          <p>{{item.description}}</p>
        </ng-template>
      </projected>
    </ng-container>
    

    投影组件(子子)

    @Component({
      selector: 'projected',
      templateUrl: 'projected.component.html',
      styleUrls: ['projected.component.scss']
    })
    
    export class ProjectedComponent implements OnInit {
      @Input() item: any;
      @ContentChild(TemplateRef) templateOutlet!: TemplateRef<any>
      constructor() {
      }
    
      ngOnInit(): void {
      }
    
    }
    
    <ng-container *ngTemplateOutlet="templateOutlet; context: {$implicit: item}"></ng-container>
    <ng-content></ng-content>
    

    它是如何工作的

    父组件在这种关系中并不是绝对必要的,因为我们不会将内容直接从父组件投影到ProjectedComponent,我只是选择在此处定义一个项目列表以保持与您的问题类似的层次结构.

    子组件
    子组件做了两件事:

    • 定义一个 *ngFor 循环以遍历某些元素集合。
    • 定义了如何在ProjectedComponent 的模板中使用这些元素的模板。

    ProjectedComponent 中,我们使用@ContentChild 装饰器来选择我们希望通过&lt;ng-content&gt; 给出的TemplateRef

    然后使用*ngTemplateOutlet 将这个模板放入容器中,这也允许我们为局部变量创建数据绑定上下文。

    context: {$implicit: item} 告诉 Angular,模板上定义的任何 let-* 变量没有任何显式绑定都应该绑定到我们组件中的 item 属性。

    因此,我们可以在父组件级别的模板中引用此属性。

    编辑

    从技术上讲,如果您想直接在子组件内部定义模板,则不需要上下文绑定,因为您可以直接引用*ngFor 模板,但是如果您想将模板取出,则必须这样做到ParentComponent 级别以使解决方案更具可重用性。

    【讨论】:

      【解决方案2】:

      您是正确的错误的原因(仅更改最后一个元素)是因为在渲染时您有多个元素具有相同的 select 值。

      一种可能的解决方案是使用模板引用将所需的子组件从顶层传递到您希望它被投影的位置。

      这是一个有效的StackBlitz

      import {
        AfterViewInit,
        Component,
        Input,
        QueryList,
        ViewChildren
      } from "@angular/core";
      
      @Component({
        selector: "my-app",
        template: `
          <child-comp #child [templateRef]="templateRef"> </child-comp>
          <ng-template #templateRef>
            <some-other-comp #someOtherComp></some-other-comp>
          </ng-template>
        `,
        styleUrls: ["./app.component.css"]
      })
      export class AppComponent implements AfterViewInit {
        h = 0;
        i = 1;
        j = 2;
        k = 3;
        @ViewChildren("someOtherComp") otherCompList: QueryList<SomeOtherComponent>;
      
        ngAfterViewInit(): void {
          this.otherCompList.toArray()[this.h].prop = this.h;
          this.otherCompList.toArray()[this.i].prop = this.i;
          this.otherCompList.toArray()[this.j].prop = this.j;
          this.otherCompList.toArray()[this.k].prop = this.k;
        }
      }
      
      @Component({
        selector: "child-comp",
        template: `
          <div *ngFor="let value of [1, 2, 3, 4]; let i = index">
            <!-- if ngIf is removed than only the last projection is diplayed -->
            <ng-container *ngTemplateOutlet="templateRef"></ng-container>
          </div>
        `,
        styleUrls: ["./app.component.css"]
      })
      export class ChildComponent {
        @Input() templateRef;
      }
      
      @Component({
        selector: "some-other-comp",
        template: `
          <p>{{ prop }}</p>
        `,
        styleUrls: ["./app.component.css"]
      })
      export class SomeOtherComponent {
        prop: any;
      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-19
        • 2018-07-09
        • 2018-05-07
        • 1970-01-01
        • 1970-01-01
        • 2019-04-19
        相关资源
        最近更新 更多