如何将异步数据传递给 object 中的 child 组件(Angular6)
How to pass async data to child components in an object (Angular6)
我正在尝试显示从服务器检索的数据(使用 Angular 6、Rxjs 和 Chartjs),并使用这些数据呈现图表。
如果我使用本地模拟数据,一切都呈现得很好。但是,如果我使用从服务器获取数据,则无法获得呈现图表所需的数据,因此图表呈现为空白图表。
总结:
组件进行服务调用,并准备 object 以使用服务调用的响应向下传递到 child 组件。但是,当响应准备就绪时,object 已经发送但没有必要的信息。
服务代码片段:
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(组件调用服务,准备并传递object到child组件)
@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(在 child 组件中)查找更改,但是图表数据 NOT 在 child 组件中更新后"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 ***');
}
}
你能告诉我应该如何继续我的研究吗?
抱歉问题太长了,谢谢!
编辑:根据要求,模板如下
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>
图表:
<div class='bar-chart-canvas-container' *ngIf='chart'>
<canvas id='{{canvasID}}' #{{canvasID}}></canvas>
</div>
您需要与来自父组件的子组件进行交互,您需要使用输入绑定。
参考:
https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding
您的组件需要在渲染前拥有数据。您可以使用 resolve,这是 Angular 提供的一项内置功能,用于处理您描述的用例。
也看看here。可能是教程形式的有用资源。
我看到了,你没有直接将数据分配给 this.barChart
而是将其分配为 this.barChart.data
,这意味着你正在直接修改 属性,这可能不会调用子组件的 ngOnChanges
。这是由于您在评论中给出的解释。
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
绑定到 @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
我的问题已解决,我想分享解决方案以备日后有人需要。正如 Amit Chigadani 所建议的(在评论中),在订阅块中调用我的图表更新功能有效。
getAccountsOfClientId(clientID: string): void {
this.clientAccountService.getAccountsOfClientId(this.client.id)
.subscribe(accounts => {
this.clientAccounts = accounts;
this.updateChart();
this.updateBarChart();
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes['accountBalanceStatus'] || changes['chart']) {
this.renderChart();
}
}
ngOnChanges 的参数值是每个 Input()
的 SimpleChanges 类型
道具:
class SimpleChange {
constructor(previousValue: any, currentValue: any, firstChange: boolean)
previousValue: any
currentValue: any
firstChange: boolean
isFirstChange(): boolean
}
您应该通过previousValue
、currentValue
检查您的数据。
类似于:
if(changes.accountBalanceStatus.previousValue != changes.accountBalanceStatus.currentValue
|| changes.chart.previousValue != changes.chart.currentValue){
this.renderChart();
}
我正在尝试显示从服务器检索的数据(使用 Angular 6、Rxjs 和 Chartjs),并使用这些数据呈现图表。 如果我使用本地模拟数据,一切都呈现得很好。但是,如果我使用从服务器获取数据,则无法获得呈现图表所需的数据,因此图表呈现为空白图表。
总结: 组件进行服务调用,并准备 object 以使用服务调用的响应向下传递到 child 组件。但是,当响应准备就绪时,object 已经发送但没有必要的信息。
服务代码片段:
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(组件调用服务,准备并传递object到child组件)
@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(在 child 组件中)查找更改,但是图表数据 NOT 在 child 组件中更新后"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 ***');
}
}
你能告诉我应该如何继续我的研究吗?
抱歉问题太长了,谢谢!
编辑:根据要求,模板如下
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>
图表:
<div class='bar-chart-canvas-container' *ngIf='chart'>
<canvas id='{{canvasID}}' #{{canvasID}}></canvas>
</div>
您需要与来自父组件的子组件进行交互,您需要使用输入绑定。
参考:
https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding
您的组件需要在渲染前拥有数据。您可以使用 resolve,这是 Angular 提供的一项内置功能,用于处理您描述的用例。
也看看here。可能是教程形式的有用资源。
我看到了,你没有直接将数据分配给 this.barChart
而是将其分配为 this.barChart.data
,这意味着你正在直接修改 属性,这可能不会调用子组件的 ngOnChanges
。这是由于您在评论中给出的解释。
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
绑定到 @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
我的问题已解决,我想分享解决方案以备日后有人需要。正如 Amit Chigadani 所建议的(在评论中),在订阅块中调用我的图表更新功能有效。
getAccountsOfClientId(clientID: string): void {
this.clientAccountService.getAccountsOfClientId(this.client.id)
.subscribe(accounts => {
this.clientAccounts = accounts;
this.updateChart();
this.updateBarChart();
});
}
ngOnChanges(changes: SimpleChanges) { if (changes['accountBalanceStatus'] || changes['chart']) { this.renderChart(); } }
ngOnChanges 的参数值是每个 Input()
的 SimpleChanges 类型
道具:
class SimpleChange {
constructor(previousValue: any, currentValue: any, firstChange: boolean)
previousValue: any
currentValue: any
firstChange: boolean
isFirstChange(): boolean
}
您应该通过previousValue
、currentValue
检查您的数据。
类似于:
if(changes.accountBalanceStatus.previousValue != changes.accountBalanceStatus.currentValue
|| changes.chart.previousValue != changes.chart.currentValue){
this.renderChart();
}