Reputation: 3252
I'm new to observables in angular. I have a problem where I want to return a value inside a subscribe method. I have
the following method (getFirebaseData(idForm:string):observable <any[]>
):
getTotalQuestions(idForm:string){
let totalQuestions:number;
this.getFirebaseData(idForm+"/Metadatos")
.subscribe(items =>
{
items.map(item => {
totalQuestions=item.Total;
console.log(totalQuestions);
});
}
);
console.log(totalQuestions);
return totalQuestions;
}
the first console.log(totalQuestions)
prints 4 but the second console.log(totalQuestions)
prints undefined.
I understand that subscribe is an asynchronous operation and for that reason the second console.log(totalQuestions)
("In order to write the code") prints undefined, but I can not find the way to return the variable after the subscribe method has been completed. Now, if I change the subscribe to map:
getTotalQuestions(idForm:string){
let totalQuestions:number;
this.getFirebaseData(idForm+"/Metadatos")
.subscribe(items =>
{
items.map(item => {
totalQuestions=item.Total;
console.log(totalQuestions);
});
}
);
console.log(totalQuestions);
return totalQuestions;
}
the first console.log(totalQuestions)
does not print anything and the second console.log(totalQuestions)
prints undefined. It's something that I do not understand why it happens.
I hope you can help me clarify the concept that I do not understand. Thanks!
Upvotes: 59
Views: 120821
Reputation: 15098
To solve this I would create a property totalQuestions$
an observable
In the TS file
totalQuestions$ = this.getFirebaseData(idForm + "/Metadatos").pipe(
map(items => items.reduce((prev, {Total}) => prev + Total), 0)))
)
And Now in your template you can use async
pipe
<span>{{ totalQuestions$ | async }}</span>
If you would like to use the variable in your TS file, then you can use combination operators like forkJoin
or combineLatest to access this value
newVariable$ = combineLatest([totalQuestions$, ...]).pipe(
map(([totalQuestions, ... ]) => {
// Perform an operation here and return a value
})
)
Upvotes: 4
Reputation: 11345
Observable runs when you subscribe to it and what return from subscription is always a subscription object like setInterval. first sample: because this is a async call, second console.log won't wait for subscribe to finish before it executes.
getTotalQuestions(idForm: string) {
let totalQuestions: number;
return this.getFirebaseData(idForm + "/Metadatos").pipe(
map(items =>
items.map(item => {
totalQuestions = item.Total;
console.log(totalQuestions);
});
));
}
getTotalQuestions('231').subscribe(console.log)
Upvotes: 2
Reputation: 1465
You can't directly return totalQuestions like that, you need to use a subject to achieve that.
getTotalQuestions(idForm:string): Observable<string> {
let totalQuestions:number;
var subject = new Subject<string>();
this.getFirebaseData(idForm+"/Metadatos")
.subscribe(items => {
items.map(item => {
totalQuestions=item.Total;
console.log(totalQuestions);
subject.next(totalQuestions);
});
}
);
return subject.asObservable();
}
Usage: getTotalQuestion(idForm).subscribe((r)=>console.log(r))
Upvotes: 84
Reputation: 6842
I made a live example. You do not need to fire the subscription to make transformations to the array or reduce some object to a single value, here is my approached. Your service
getTotalQuestions(idForm: string): Observable<any> {
return this.getFirebaseData(`${idForm}/Metadatos`)
.pipe(map(items => items
.map(item => item.Total)
.reduce((a, b) => a+b), 0));
}
Your component
this.service.getTotalQuestions('id')
.subscribe(totalQuestions => console.log(totalQuestions));
Upvotes: 1