I'm writing my first Angular2 application.
I have a BankAccountService that fetches data from my backend server and a bank-account.component which uses this service to simply display an unsorted list of the accountNumbers.
Debugging through chrome devtool i was able to see that the data is fetched correctly and stored in the bankAccounts data member, but for some reason the ul shows nothing.
BankAccountService:
import { Injectable } from '@angular/core';
import {client} from '../app.module';
import gql from 'graphql-tag';
import {ObservableQuery} from "apollo-client";
@Injectable()
export class BankAccountService{
bankAccounts: any[] = [];
constructor() {
this.queryBankAccounts();
}
queryBankAccounts(): any{
let queryObservable: ObservableQuery = client.watchQuery({
query: gql`
{
bankAccounts {
id
accountNumber
userOwners{
firstName
}
bankId
branchId
transactions{
amount
payerId
recipientId
}
}
}
`,
pollInterval: 50
});
let subscription = queryObservable.subscribe({
next: ({ data }) => {
this.bankAccounts = data;
},
error: (error) => {
console.log('there was an error sending the query', error);
}
});
}
getBankAccounts(){
return this.bankAccounts;
}
}
bank-account.component:
import {Component} from "@angular/core";
import {BankAccountService} from "../services/BankAccountService";
@Component({
selector: 'bank-account',
template: `<ul>
<li *ngFor="let account of bankAccounts">
{{account.accountNumber}}
</li>
</ul>`,
providers: [BankAccountService]
})
export class BankAccountComponent{
bankAccounts: any[] = [];
constructor(bankAccountService: BankAccountService){
this.bankAccounts = bankAccountService.getBankAccounts();
}
}
Would really appreciate if someone could shed some light, i can't seem to understand my mistake.
Thanks in advance
2 Answers
That's because async function is getting data, but you are trying to display it before it is fetched, therefore, you are iterating through an empty array. You can solve this problem easily by not initializing array (change bankAccounts: any[] = []; to bankAccounts: any[];) and surrounding your <ul> tags with <div *ngIf="bankAccounts"></div>. That way, bankAccounts array will be undefined until data is fetched and your *ngFor loop will be executed right after array is populated thanks to *ngIf directive.
Your code doesn't work because the service fills its bankAccounts data asynchronously (when it gets results from the server) while your component tries to read the data synchronously. At the time the component reads the data, the service is still waiting for that data to arrive from the back end.
The better design is to make the service's getBankAccounts() be an Observable that will emit the data when it is ready. Your component would then subscribe to this service and only initialize the view once the data arrives.
service
//make this observable into a class property
queryObservable:ObservableQuery = client.watchQuery({
...
});
//return the observable that will emit the data
getBankAccounts():Observable<any[]>{
return this.queryObservable;
}
component
//don't set it to anything, so the *ngIf will work in the template
bankAccounts: any[];
//better to do this here than in the constructor
ngOnInit(){
this.bankAccountService.getAccounts().subscribe(
data => this.bankAccounts = data
)
}
template
<!-- only show the list IF the data is available -->
<ul *ngIf="bankAccounts">
<li *ngFor="let account of bankAccounts">
{{account.accountNumber}}
</li>
</ul>
I don't go over it here, but an alternative (if you don't want to use *ngIf) is to leave the service as I described, then in the component set bankAccounts = service.getBankAccounts(), and in the template use the async pipe, which expects an Observable and will automatically display the data once the Observable emits.