【问题标题】:How to pass async data to child components in an object (Angular6)如何将异步数据传递给对象中的子组件(Angular6)
【发布时间】:2018-11-30 15:14:45
【问题描述】:

我正在尝试显示从服务器检索到的数据(使用 Angular 6、Rxjs 和 Chartjs),并使用这些数据呈现图表。 如果我使用本地模拟数据,一切都会很好。但是,如果我使用从服务器获取数据,则呈现图表所需的数据不可用,因此图表呈现为空白图表。

总结: 组件进行服务调用,并准备一个对象以使用来自服务调用的响应向下传递给子组件。但是,当响应准备好时,对象已经在没有必要信息的情况下发送。

服务代码sn-p

  getAccountsOfClientId(clientID: string): Observable<Account[]> {
    return this.http.get<Account[]>(`${this.BASE_URL}/accounts?client=${clientID}`)
      .pipe(
        tap(accounts => console.log('fetched client\'s accounts')),
        catchError(this.handleError('getAccountsOfClientId', []))
      );
  }

在client-info.component.ts中(进行服务调用的组件,并准备对象并将对象传递给子组件)

@Input() client; // received from another component, data is filled

  constructor(private clientAccountService: ClientAccountService) { }

  ngOnInit() {
    this.getAccountsOfClientId(this.client.id);
  }

  ngAfterViewInit() {
    this.updateChart(); // render for pie chart
    this.updateBarChart(); // render for bar chart
  }

  getAccountsOfClientId(clientID: string): void {
    this.clientAccountService.getAccountsOfClientId(this.client.id)
      .subscribe(accounts => this.clientAccounts = accounts);
  }

  updateBarChart(updatedOption?: any): void {
    /* unrelated operations above ... */

    // Create new base bar chart object
    this.barChart = {};
    this.barChart.type = 'bar';
    this.setBarChartData();
    this.setBarChartOptions('Account', 'Balance');
  }

  setBarChartData(): void {

    // field declarations..

    console.log('clientAccounts has length: ' + this.clientAccounts.length); // prints 0
    this.clientAccounts.map((account, index) => {
        // do stuff
    });

    dataset = {
      label: 'Balance',
      data: data,
      ...
    };

    datasets.push(dataset);

    // since clientAccounts was empty at the time this function ran, the "dataset" object doesn't contain
    // the necessary information for the chart to render
    this.barChart.data = {
      labels: labels,
      datasets: datasets
    };
  }

我正在使用 ngOnChanges(在子组件中)查找更改,但是在“clientAccounts”数组被响应填充后,图表数据在子组件中更新。 p>

@Input() chart: Chart;
@Input() canvasID: string;
@Input() accountBalanceStatus: string;

ngOnChanges(changes: SimpleChanges) {
  if (changes['accountBalanceStatus'] || changes['chart']) {
    this.renderChart();
  }
}


renderChart(): void {
  const element = this.el.nativeElement.querySelector(`#${this.canvasID}`);

  if (element) {
    const context = element.getContext('2d');

    if (this.activeChart !== null) {
      this.activeChart.destroy();
    }

    this.activeChart = new Chart(context, {
      type: this.chart.type,
      data: this.chart.data,
      options: this.chart.options
    });
  } else {
    console.log('*** Not rendering bar chart yet ***');
  }
}

你能指出我应该如何继续我的研究吗?

抱歉问题太长了,谢谢!

编辑:根据要求,模板如下

父母(客户信息)

<div class='client-info-container'>
  <div class='info-container'>
    <li>Date of Birth: {{ client.birthday | date: 'dd/MM/yyyy'  }}</li>
    <li>Name: {{ client.name }}</li>
    <li>First Name: {{ client.firstname }}</li>
  </div>

  <div class='more-button'>
    <button (click)='openModal()'>More</button>
  </div>

  <div class='chart-container'>
    <div *ngIf='pieChart && client'>
      <app-balance-pie-chart
      [chart]='pieChart' 
      [canvasID]='accountBalancePieChartCanvasID'
      (updateChart)='handlePieChartOnClick($event)'>
      </app-balance-pie-chart>
    </div>

    <div class='bar-chart-container'>
      <div class='checkbox-container'>
        <div *ngFor='let option of cardTypeCheckboxOptions' class='checkbox-item'>
          <input
          type='checkbox' 
          name='cardTypeCheckboxOptions' 
          value='{{option.value}}' 
          [checked]='option.checked'
          [(ngModel)]='option.checked'
          (change)="updateCardTypeCheckboxSelection(option, $event)"/>

          <p>{{ option.name }} {{ option.checked }}</p>
        </div>
      </div>

      <div *ngIf='barChart && client'>
        <!--  *ngIf='client.accounts.length === 0' -->
        <div class="warning-text">This client does not have any accounts.</div>
        <!--  *ngIf='client.accounts.length > 0' -->
        <div>
            <app-balance-account-bar-chart
            [chart]='barChart'
            [canvasID]='accountBarChartCanvasID'
            [accountBalanceStatus]='accountBalanceStatus'>
            </app-balance-account-bar-chart>
        </div>
      </div>

    </div>
  </div> 
</div>

图表:

<div class='bar-chart-canvas-container' *ngIf='chart'>
  <canvas id='{{canvasID}}' #{{canvasID}}></canvas>
</div>

【问题讨论】:

  • 也分享你的组件的 HTML。
  • @FarhanTahir 是图表组件,还是父组件?
  • 父母和图表的sn-p会有所帮助。
  • @FarhanTahir 添加了
  • 登录 if (changes['accountBalanceStatus'] || changes['chart']) { } 并检查在异步调用解决后您是否获得了正确的数据。 ngOnChanges 通常会在每次数据更改时触发多次。

标签: javascript angular rxjs observable angular6


【解决方案1】:

我看到了,您没有将数据直接分配给this.barChart,而是将其分配为this.barChart.data,这意味着您正在直接修改属性,这可能不会调用子组件的ngOnChanges。这是由于您在 cmets 中给出的解释。

我读到这可能是因为角度变化检测检查了 通过查看对象引用的差异

并且它不会知道对象的属性何时发生变化

绑定到@Input() 属性的变量是this.barChart 而不是this.barChart.data

代替

this.barChart.data = {
      labels: labels,
      datasets: datasets
};

你试试这个

this.barChart = {
    data : {
      labels: labels,
      datasets: datasets
 }};

这里你直接修改this.barChart 应该触发ngOnChanges()

编辑:

你应该在subscribe块内调用this.updateChart();

this.clientAccountService.getAccountsOfClientId(this.client.id) 
.subscribe((accounts) => {
   this.clientAccounts = accounts;
   this.updateChart();
}) 

这就是为什么你也有 this.clientAccounts.length 为 0

【讨论】:

  • 感谢您的回答。由于 this.barChart 包含另外 2 个属性,我相信使用 this.barChart = { newproperties: ...} 会消除之前在 barChart 对象中添加的字段。我将尝试使用扩展运算符(例如 this.barChart = {...this.barChart, {newproperties}})并回复您。
  • 是的,请确保您不会丢失其他属性。您还可以使用tempObj 设置某些属性的值,然后将整个tempObj 重新分配给this.barChart
  • 您应该在this.clientAccountService.getAccountsOfClientId(this.client.id) .subscribe(accounts =&gt; this.clientAccounts = accounts) 的订阅块内调用this.updateChart();。这就是为什么你也有 this.clientAccounts.length 作为 0
【解决方案2】:
ngOnChanges(changes: SimpleChanges) {
  if (changes['accountBalanceStatus'] || changes['chart']) {
    this.renderChart();
  }
}

ngOnChanges 的参数值是 SimpleChanges 的类型,每个 Input() 道具:

class SimpleChange {
  constructor(previousValue: any, currentValue: any, firstChange: boolean)
  previousValue: any
  currentValue: any
  firstChange: boolean
  isFirstChange(): boolean
}

您应该通过previousValuecurrentValue 检查您的数据。 比如:

if(changes.accountBalanceStatus.previousValue != changes.accountBalanceStatus.currentValue 
   || changes.chart.previousValue  != changes.chart.currentValue){
 this.renderChart();
}

StackBlitz Demo

【讨论】:

    【解决方案3】:

    我的问题已解决,我想分享解决方案,以防将来有人需要。正如 Amit Chigadani 建议的那样(在 cmets 中),在订阅块中调用我的图表更新函数是可行的。

    getAccountsOfClientId(clientID: string): void {
        this.clientAccountService.getAccountsOfClientId(this.client.id)
          .subscribe(accounts => {
            this.clientAccounts = accounts;
            this.updateChart();
            this.updateBarChart();
          });
    }
    

    【讨论】:

      【解决方案4】:

      您的组件需要在渲染之前拥有数据。您可以使用 resolve,这是 Angular 提供的一个内置功能,用于处理您所描述的用例。

      也看here。可能是教程形式的有用资源。

      【讨论】:

        【解决方案5】:

        您需要与父组件中的子组件交互才能使用输入绑定。

        参考:

        https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-07-14
          • 2019-01-13
          • 2017-12-17
          • 2021-07-12
          • 1970-01-01
          • 2019-11-16
          • 2017-12-22
          • 1970-01-01
          相关资源
          最近更新 更多