简体   繁体   English

如何将异步数据传递给对象中的子组件(Angular6)

[英]How to pass async data to child components in an object (Angular6)

I'm trying to display data retrieved from a server (using Angular 6, Rxjs and Chartjs), and render a chart using the data. 我正在尝试显示从服务器检索的数据(使用Angular 6,Rxjs和Chartjs),并使用数据呈现图表。 If I use local mock data, everything renders just fine. 如果我使用本地模拟数据,一切都很好。 But if I use get the data from the servers, the necessary data to render the graphs isn't available so the charts render as blank charts. 但是,如果我使用从服务器获取数据,则无法呈现图形所需的数据,因此图表将呈现为空白图表。

Summary: A component makes a service call, and prepares an object to pass down to a child component using the response from the service call. 简介:组件进行服务调用,并使用服务调用的响应准备对象以传递给子组件。 However, by the time the response is ready, the object is already sent without the necessary information. 但是,在响应准备就绪时,已经发送了没有必要信息的对象。

Service code snippet : 服务代码段

  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', []))
      );
  }

In client-info.component.ts (component to make the service call, and prepare and pass the object to child component) 在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
    };
  }

I'm looking for changes using ngOnChanges (in the child component), however the chart data is NOT updated in the child component after the "clientAccounts" array is filled with the response. 我在寻找使用ngOnChanges(在子组件)的变化,但是图表数据是不是在子组件更新“clientAccounts”阵列充满响应之后。

@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 ***');
  }
}

Can you point me to how I should continue my research on this? 你能指点我应该如何继续我的研究吗?

Sorry for the long question, and thanks! 抱歉,这个问题很长,谢谢!

EDIT : Upon request, the templates are below 编辑 :根据要求,模板如下

Parent (client-info) : 父(客户信息)

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

Chart: 图表:

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

I saw that, you are not assigning the data directly to this.barChart instead you are assigning it as this.barChart.data , which means you are modifying the property directly, which might not invoke the ngOnChanges of the child component. 我看到了,你没有直接将数据分配给this.barChart而是将它指定为this.barChart.data ,这意味着你要直接修改属性,这可能不会调用子组件的ngOnChanges This is due to the explanation that you have given in your comments. 这是由于您在评论中给出的解释。

I read that it may be because angular change detection checks the differences by looking at the object references 我读到这可能是因为角度变化检测通过查看对象引用来检查差异

And it will not get to know when the property of object gets changed 并且它不会知道对象的属性何时被改变

The variable that is bound to @Input() property is this.barChart and not this.barChart.data . 绑定到@Input()属性的变量是this.barChart而不是this.barChart.data

Instead of 代替

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

You try this 你试试这个

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

here you are directly modifying this.barChart which should trigger ngOnChanges() . 在这里你直接修改this.barChart应该触发ngOnChanges()

EDIT : 编辑:

You should be invoking this.updateChart(); 你应该调用this.updateChart(); inside subscribe block of 里面的subscribe

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

That is why you also have this.clientAccounts.length as 0 这就是为什么你也将this.clientAccounts.length设为0

Your component needs to have the data before rendering. 您的组件需要在呈现之前获取数据。 You may use resolve , a built in feature that Angular provides to handle use-cases like the ones you described. 您可以使用resolve ,这是Angular提供的内置功能,用于处理您所描述的用例。

Also look here . 还看这里 may be a useful resource in a tutorial form. 可能是教程表单中的有用资源。

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

ngOnChanges's argument value is type of SimpleChanges for each Input() prop: ngOnChanges的参数值是每个Input() prop的SimpleChanges的类型:

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

You should check you data by previousValue , currentValue . 您应该通过previousValuecurrentValue检查数据。 Something like: 就像是:

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

StackBlitz Demo StackBlitz演示

You need to interact with child components from parent this you nned to use input binding. 您需要与来自父级的子组件进行交互,以便使用输入绑定。

Refer: 参考:

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

My issue is solved and I'd like to share the solution in case anyone needs it in the future. 我的问题已经解决,我想分享解决方案以防将来有人需要它。 As Amit Chigadani suggested (in the comments), invoking my chart updating functions in the subscribe block worked. 正如Amit Chigadani建议的那样(在评论中),在订阅块中调用我的图表更新功能。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM