Angular 2+ wait for subscribe to finish to update/access variable - javascript

I am having an issue with my variables being undefined. I am certain this is because the observable hasn't finished. Here is the part of my code in my .ts file that is causing the issue. (I'm placing the minimum code required to understand the issue. Also myFunction gets called from a click event in the HTML).
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
});
console.log(myVariable) --> undefined
}
}
So this piece of code calls a function in my service that returns some data from an API. The issue is that when I try to access the variable myVariable right outside of the subscribe function it returns undefined. I'm sure this is because the subscribe hasn't finished before I try to access myVariable
Is there a way to wait for the subscribe to finish before I try to access myVariable?

why not create a separate function and call it inside the subscription.
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
this.update()
});
this.update()
}
update(){
console.log(this.myVariable);
}
}

As you know subscriptions are executed when server return data but the out side of subscription code executed synchronously. That is why console.log outside of it executed. The above answer can do your job but you can also use .map and return observable as shown below.
let say you are calling it from s service
export class myClass {
myVariable: any;
// calling and subscribing the method.
callingFunction() {
// the console log will be executed when there are data back from server
this.myClass.MyFunction().subscribe(data => {
console.log(data);
});
}
}
export class myClass {
myVariable: any;
// this will return an observable as we did not subscribe rather used .map method
myFunction() {
// use .pipe in case of rxjs 6
return this.myService.getApi().map(data => {
this.myVariable = data;
this.update()
});
}
}

Related

API call returns empty object when called from inside Angular service

I'm getting this only when I subscribe to the function that makes api call from inside the Angular service, so the object that subscribes to the function is empty. Here's my code snippet from my service:
getSchedules(): Observable<Schedule[]> {
this.http.get<TempSchedules[]>(this.apiUrl).subscribe(x => this.temp = x);
this.temp.forEach((e, i) => {
// Do something, this loop is never executed because this.temp is empty
});
// Some processing here
return something; }
Here is my http.get function somewhere inside the service:
getTempSchedules(): Observable<TempSchedules[]> {
return this.http.get<TempSchedules[]>(this.apiUrl);
}
From the above, this.temp is empty. Why is that?
I called the above function in the service constructor as
constructor(private http:HttpClient) {
this.getTempSchedules().subscribe(x => this.temp = x);
}
Here is a code snippet from a component that calls that function in the service:
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}
The component works fine, so when I use the value this.tempSchedules in the html it is displayed correctly. What am I missing here?
It is not working because you are not getting the way observable works. It is async process and you need to be in subscribe block to get it. In case you want to do some funky stuff with the response before returning it to the component, then you should use map
getTempSchedules(): Observable<Schedule[]> {
return this.http.get<TempSchedules[]>(this.apiUrl)
.pipe(map(res => {
return res.forEach(() => {
// Do something, this loop will be executed
})
})) }
use it in component as :
ngOnInit(): void {
this.scheduleService.getTempSchedules().subscribe(x => this.tempSchedules = x);
}

Why my code works with arrow function and broken with bind?

I have thought that bind(this) is an equivalent of the arrow function however I have met an issue(
This is a store which purpose to track delayed requests. I am using the orbitjs library to save everything into IndexedDB. It provides api which allows to subscribe to db changes, so here my store:
export class DelayedRequestsStore implements IDelayedRequests {
add = addHttpRequest;
list = queryHttpRequests;
remove = removeHttpRequest;
#observable private _delayedRequestsCount = 0;
#computed get any(): boolean {
return this._delayedRequestsCount > 0;
}
#computed get empty(): boolean {
return !this.any;
}
constructor(db: Sources) {
db.source.on('update', () => {
this._updateDelayedRequestsCount();
});
this._updateDelayedRequestsCount();
}
private async _updateDelayedRequestsCount(): Promise<void> {
const delayedRequests = await this.list();
runInAction(() => {
this._delayedRequestsCount = delayedRequests.length;
});
}
}
See that code on constructor
constructor(db: Sources) {
db.source.on('update', () => {
this._updateDelayedRequestsCount();
});
this._updateDelayedRequestsCount();
}
And some code on react:
<button onClick={async () => {
await notifyServer();
await clearRequestsFromIndexedDb();
goToAnotherPage();
})>Cancel</button>
Everything works well until I have not changed the constructor code to
constructor(db: Sources) {
db.source.on('update', this._updateDelayedRequestsCount.bind(this));
this._updateDelayedRequestsCount();
}
With that change I have not seeing any errors in console but Cancel button is not working. I have debugged and found that notifyServer has been called, then clearRequestsFromIndexedDb has been called but goToAnotherPage not called, like if error occured in clearRequestsFromIndexedDb, but no errors. SO I rollbacked back to arrow function everything works again. Does it can affect anything? Or the problem actually in some other place I am missing?
I see that you bound this only to db.source.on('update', ... ). But the call for this._updateDelayedRequestsCount() in your constructor has no binding. This could be a problem.
You can explicitly bind this to every call of you method like this:
constructor(db: Sources) {
this._updateDelayedRequestsCount = this._updateDelayedRequestsCount.bind(this);
db.source.on('update', this._updateDelayedRequestsCount);
this._updateDelayedRequestsCount();
}
Maybe it will fix your problem.

Which RxJS type to use when a method may or may not fetch data asynchronously?

Imagine we have the following factory:
#Injectable()
export class DataClassFactory {
constructor(
private dataService: DataService,
) { }
public createThing(initialData?: InitialData): AsyncSubject<DataClass> {
let dataClass: AsyncSubject<DataClass> = new AsyncSubject<DataClass>();
if (!!initialData) {
dataClass.next(new DataClass(initialData));
dataClass.complete();
} else {
this.dataService.getData().subscribe((dataResponse) => {
dataClass.next(new ReportRequest(dataResponse));
dataClass.complete();
});
}
}
return dataClass;
}
}
We inject this factory, invoke the createThing method, and subscribe to the response in some component. I originally tried to use a plain Subject, but then I realized that in the case where we already have initial data, next() is called before the response is returned, so the subscriber in the component never gets that value.
My question is: is this correct situation in which to use an AsyncSubject, or is there a different/better way to handle this sort of method that has potential synchronous and asynchronous timelines?
I would do something along these lines
public createThing(initialData?: InitialData): Observable<DataClass | ReportRequest> {
if (!!initialData) {
const data = new DataClass(initialData);
return of(data);
} else {
return this.dataService.getData()
.pipe(map(dataResponse => new ReportRequest(dataResponse));
}
}
Whoever calls createThing would get an Observable to which it would have to subscribe.
This Observable would emit an instance of DataClass if initialData is not null, otherwise it would return and instance of ReportRequest as soon as dataService responds.

unable to assign data from response data in Angular

I have a function that should return an array of objects when called. The function looks like this
loadTodo(): Todo[]{
var data
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
data = res.json()
}, error => {
console.log(error)
})
return data}
This results in unexpected behavior where data variable gets assigned correctly inside the success response block but is undefined when accessed outside the response block.
The function is assigned to a variable with type Todo[] and is invoked immediately when the variable is declared. I am quite new to TypeScript and Angular but not to JavaScript. Am I missing something with scope/closure of the function or is this issue related to TypeScript/Angular?
Whole class looks like this:
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
})
console.log('outside function')
console.log(this.parcedTodos)
return this.parcedTodos
}
}
http.get() is asynchronous, which means that when you try to print parcedTodos outside the then callback, it will still be undefined.
Asynchronous programming in JS
It is happening because http calls are asynchronous.
You need to make sure you are accessing data only after call is completed.
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo(): Todo[]{
this.http.get(`${this.API_URL}todos`).toPromise().then(res => {
this.parcedTodos = res.json()
console.log('inside function')
console.log(this.parcedTodos)
}, error => {
console.log(error)
},
{
console.log(this.parcedTodos);
// This is where your call gets completed. Here you can access assigned data or call another function where you can access data.
})
console.log('outside function')
console.log(this.parcedTodos) // This is called before asynchronous call is completed. Thats why it is undefined yet.
return this.parcedTodos
}
}
Hope this helps.
this.http.get(whatever) is an async call.
Your data is undefined because you're accessing it before it is actually initialized. i.e. you're initializing it inside the success handler (the first argument to then), and probably are accessing it before initialization.
All you need to do is make sure you're doing so after the success or error handler. use Observable
I think that using res.json() not is neccesary because angular pipes already doing this works. Do you try to assign to variable res directly?
As others friends says, you are doing bad some things.
First: you must read about asynchronous methods
Second: use Observables importing rxjs/Observable; and follow its callbacks flow
Example
export class TodoDataService {
API_URL: String = 'http://localhost:3000/'
todos: Todo[] = this.loadTodo();
constructor(private http: Http) {
}
loadTodo() : Observable<Todo[]>{
return this.http.get(`${this.API_URL}todos`);
}
}
And other class consummes this method
todoDataService.loadTodo().subscribe(
(response) => {
console.log("Future response", response);
}
);

Angular2 component's "this" is undefined when executing callback function

I have a component which calls a service to fetch data from a RESTful endpoint. This service needs to be given a callback function to execute after fetching said data.
The issue is when I try use the callback function to append the data to the existing data in a component's variable, I get a EXCEPTION: TypeError: Cannot read property 'messages' of undefined. Why is this undefined?
TypeScript version: Version 1.8.10
Controller code:
import {Component} from '#angular/core'
import {ApiService} from '...'
#Component({
...
})
export class MainComponent {
private messages: Array<any>;
constructor(private apiService: ApiService){}
getMessages(){
this.apiService.getMessages(gotMessages);
}
gotMessages(messagesFromApi){
messagesFromApi.forEach((m) => {
this.messages.push(m) // EXCEPTION: TypeError: Cannot read property 'messages' of undefined
})
}
}
Use the Function.prototype.bind function:
getMessages() {
this.apiService.getMessages(this.gotMessages.bind(this));
}
What happens here is that you pass the gotMessages as a callback, when that is being executed the scope is different and so the this is not what you expected.
The bind function returns a new function that is bound to the this you defined.
You can, of course, use an arrow function there as well:
getMessages() {
this.apiService.getMessages(messages => this.gotMessages(messages));
}
I prefer the bind syntax, but it's up to you.
A third option so to bind the method to begin with:
export class MainComponent {
getMessages = () => {
...
}
}
Or
export class MainComponent {
...
constructor(private apiService: ApiService) {
this.getMessages = this.getMessages.bind(this);
}
getMessages(){
this.apiService.getMessages(gotMessages);
}
}
Or you can do it like this
gotMessages(messagesFromApi){
let that = this // somebody uses self
messagesFromApi.forEach((m) => {
that.messages.push(m) // or self.messages.push(m) - if you used self
})
}
Because you're just passing the function reference in getMessages you don't have the right this context.
You can easily fix that by using a lambda which automatically binds the right this context for the use inside that anonymous function:
getMessages(){
this.apiService.getMessages((data) => this.gotMessages(data));
}
I have same issue, resolved by using () => { } instead function()
Please define function
gotMessages = (messagesFromApi) => {
messagesFromApi.forEach((m) => {
this.messages.push(m)
})
}

Categories

Resources