如何正确使用 Angular 中的 Observable 将数组数据从父级发送到子级?

Posted

技术标签:

【中文标题】如何正确使用 Angular 中的 Observable 将数组数据从父级发送到子级?【英文标题】:How to send array data from Parent to Child using Observable in Angular properly? 【发布时间】:2021-09-04 20:18:02 【问题描述】:

首先,我需要从父组件向子组件发送一些 json 数据,但是我在 ngOnInit 事件中从 http 请求加载数据,当我以正常方式提交数据时,数据不会发送到我的子组件,这里这是我的第一次尝试:

data: TransSalesOrder = new TransSalesOrder();

   ngOnInit(): void 

    if (this.dataId > 0) 
      const data: SelectItem = new SelectItem();
      data.id = this.dataId;
      const sb = this.transSalesOrderService.view(data).subscribe(response => 
        this.transSalesOrderForm.patchValue(response);
        this.data = response;

        if (this.transSalesOrderForm.get('isDeleted').value) 
          this.editButton = false;
          this.deleteButton = false;
          this.isDeleted = true;
        
      , err => 
        this.openSnackBar(err);
      );
      this.subscriptions.push(sb);
    


然后在我的父 html 中,我发送了这样的数据:

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail"></app-trans-sales-order-detail-list>

然后当我 console.log 进入子 component.ts 时:

@Input() salesOrderDetailsInput: any;

  constructor()  

  ngOnInit(): void 
    console.log(this.salesOrderDetailsInput);
  

打印出'未定义'

那我觉得parent来不及加载数据了,这里是我第二次尝试,我需要ngOnChanges事件:

  ngOnChanges(changes: SimpleChanges) 
    console.log(changes.salesOrderDetailsInput.currentValue);
  

然后我可以获取从父级发送的数据,但是我在 *** 的某个地方读到,有人说需要从父级值添加异步函数,并且子组件中的 @Input 应该检索为 Observable,然后我假设如果我可以做到这一点,我将不再需要 ngOnChanges,这是我的尝试:

在我的父代码中:

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail | async"></app-trans-sales-order-detail-list>

但是在我的 Intellij Idea ide 中编译时它给了我错误 ERROR Error: InvalidPipeArgument: '' for pipe 'AsyncPipe' 和错误 Argument type TransSalesOrderDetail[] is not assignable to parameter type Observable&lt;unknown&gt; | Promise&lt;unknown&gt;

这是我的子组件.ts:

@Input() salesOrderDetailsInput: Observable<any>;

我错过了什么?

【问题讨论】:

transSalesOrderDetail 的类型吗?您应该将其用作子组件输入的类型 - 如下所示:@Input() salesOrderDetailsInput: TransSalesOrderDetail; 【参考方案1】:

成员变量this.data 被异步赋值并在绑定[salesOrderDetailsInput]="data.transSalesOrderDetail" 执行时持有一个空对象。所以它会发送transSalesOrderDetail 的当前状态,即undefined

IMO 不需要在这里使用async。你可以试试下面的

选项 1:*ngIf

使用*ngIf有条件地渲染子组件标签

<app-trans-sales-order-detail-list 
  *ngIf="!!data && !!data.transSalesOrderDetail"
  [salesOrderDetailsInput]="data.transSalesOrderDetail"
></app-trans-sales-order-detail-list>

这里没有定义transSalesOrderDetail 属性时子组件不会被渲染。有关双 bang !! 运算符的更多信息,请参阅 here。

选项2:安全导航操作员?.

访问属性时使用安全导航运算符?.。它会在尝试访问之前检查该属性是否在对象中定义。

<app-trans-sales-order-detail-list 
  [salesOrderDetailsInput]="data?.transSalesOrderDetail"
></app-trans-sales-order-detail-list>

这里可能会渲染子组件,但没有绑定到transSalesOrderDetail


话虽如此,我可以提供一些额外的建议

当使用ngOnChanges 挂钩读取@Input 属性的更改时,最好在尝试访问之前检查该属性是否已定义。在具有单个 @Input 属性的组件中,这可能不是问题。但是当有多个@Inputs 时,可能会发生一些已定义而有些未定义的情况。

@Input() salesOrderDetailsInput: any;

ngOnChanges(changes: SimpleChanges) 
  if (!!changes && 
      !!changes.salesOrderDetailsInput && 
      !!changes.salesOrderDetailsInput.currentValue
  ) 
    console.log(changes.salesOrderDetailsInput.currentValue);
  

【讨论】:

【参考方案2】:

您必须使 transSalesOrderDetail 在您的父母中成为可观察的。然后在你的孩子中订阅这个 observable。

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data.transSalesOrderDetail"></app-trans-sales-order-detail-list> 

那么在你的孩子身上,

@Input() salesOrderDetailsInput: any;
...
...

ngOnInit(): void 
  salesOrderDetailsInput.subscribe(data => console.log(data));

您可以选择使用async 管道,这将消除订阅的必要性 - 但您必须使用 onChange 事件来获取最新数据。,

【讨论】:

【参考方案3】:

当您调用 API 或其他东西以异步获取数据时,异步管道实际上就像订阅 observable 一样。当您使用async 管道时,| 的左侧应该是observable。示例:

parent.component.html

<ChildComponent [inputData] = "_data | async"></ChildComponent>

parent.component.ts

public data = new BehaviorSubject(null);
public _data = this.selectedSubmission.asObservable();


ngOnInit(): 
  this.http.get(url).subscribe((respData:any)=>
    this.data.next(respData);
  )

child.component.ts

@Input() inputData: any;

ngOnChanges(changes: SimpleChanges) 
  console.log(this.inputData);

注意:您应该使用服务文件来编写所有 API 调用。上面的例子演示了应该如何做。

Live Demo

【讨论】:

【参考方案4】:

我认为 Input 无法正常工作的原因是,data.transSalesOrderDetail 会导致它的第一次评估出错,所以尝试添加Elvis operator。一旦ngOnInit() 中的服务执行完毕,数据将按预期正确加载

另一种方法是,定义变量并将其添加到输入绑定..

component.ts

transSalesOrderDetail: TransSalesOrderDetail;

...
this.transSalesOrderForm.patchValue(response);
this.data = response;
this.transSalesOrderDetail = response.transSalesOrderDetail;
...

HTML 中:

<app-trans-sales-order-detail-list [salesOrderDetailsInput]="data?.transSalesOrderDetail"></app-trans-sales-order-detail-list>


 // or with new variable <app-trans-sales-order-detail-list [salesOrderDetailsInput]="transSalesOrderDetail"></app-trans-sales-order-detail-list>

最好也为 Input 添加类型

@Input() salesOrderDetailsInput: TransSalesOrderDetail;

【讨论】:

以上是关于如何正确使用 Angular 中的 Observable 将数组数据从父级发送到子级?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确地将数据绑定到 Angular2 中的单选按钮?

在 RxJS Observable 中“展平”数组的最佳方法

如何正确验证 HTML 输入中的数字范围? (角度 2)

如何在 Angular 组件中正确使用 Cloud Functions?

Angular使用总结 --- 如何正确的操作DOM

RxJS Observables 的 Promise.all 行为?