I'm fairly new to Angular and I got stuck while following a tutorial on fetching data from AFS with RxJS
I successfully grabbed the required and it is logged it into the console but when the same data is passed into a class property for use in my Component view, everything breaks. I've been on this for 2 hours, I've tried implementing solutions to similar problems I found here on SO, but all failed.
model.ts
export interface Expenses {
title: string,
type: boolean,
amount: number
}
ExpenselistService.ts
import { Expenses } from '../models/expenses';
loadItems() {
return this.afs.collection('expdata').snapshotChanges().pipe(
map(dump => {
return dump.map(a => {
const id = a.payload.doc.id;
const data = a.payload.doc.data();
return {id, data};
})
})
);
}
component.ts
expenseData: Array<object>; //Always produce an error (not initialized by constructor and returns an empty array if muted or initialized manually)
constructor(private exService: ExpenselistService) { }
ngOnInit(): void {
this.exService.loadItems('expdata').subscribe(values => {
this.expenseData = values; // Successfully stores to property but still unavailable in component.html
});
}
component.html
<tr *ngFor="let exp of expenseData; let i = index">
<td>{{ i }}</td>
<td>{{ exp.data.title }}</td>
<td>{{ exp.data.type }}</td>
<td>{{ exp.data.amount | currency }}</td>
<td class="dropdown">
<button class="dropdown-toggle" data-toggle="dropdown">Action</button>
<div class="dropdown-menu dropdown-menu-right">
<a href="#" class="dropdown-item">Edit</a>
<a href="#" class="dropdown-item">Delete</a>
</div>
</td>
</tr>
The idea is to display all data transfer all data from service.ts through component.ts to component.html, but component.ts always return an undefined object to component.html and component.html plots an error saying 'exp' does not have a property of 'data'
Any help will be greatly appreciated, since I have no clue what seems to be the problem
Your application is not working because you are not initializing the array here or in the constructor like this:
expenseData: Array<object> = [];
Another way to approach this is to remove the subscription
and the array initialization, this will help maintain your application better, as subscriptions do not automatically unsubscribe which can cause memory leaks.
Let's start by refactoring the expenseData
variable and just make it an Observable (adding the $
at the end is a convention that just lets us/others know that this is an observable), this simplifies the typescript as we no longer need the ngOnInit()
:
expenseData$ = this.exService.loadItems('expdata');
constructor(private exService: ExpenselistService) { }
Next in the html
we just use an async
pipe which will subscribe
on component creation, and unsubscribe
on component destroy (fixing potential memory leaks). The data then gets saved to the the exp
variable just like it did before.
<tr *ngFor="let exp of expenseData$ | async; let i = index">
<!-- The rest of the html -->
</tr>
After these changes, we will have a simpler component, and we remove the potential of having memory leaks due to us maybe forgetting to manually unsubscribe.
If we want to update the data that goes to the async pipe, all we need to do is reassign and make the call again. This will automatically unsubscribe and resubscribe to the new observable and then update the template data as needed.
update() {
this.expenseData = this.exService.loadItems('expdata');
}