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

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.

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);
}

How do I make sure one Subcription finishes before another?

globleVariable: any;
ngOnInit() {
// This doesn't work. methodTwo throws error saying "cannot read someField from null. "
this.methodOne();
this.methodTwo();
}
methodOne() {
this.firstService.subscribe((res) => { this.globleVariable = res });
}
methodTwo() {
this.secondService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
As shown above, methodOne set the value of globleVariable and methodTwo uses it, therefore the former must finish running before the latter.
I am wondering how to achieve that.
Instead of subscribing in the methods, combine them into one stream and subscribe to that in ngInit(). You can use tap to perform the side effect of updating globaleVariable that you were previously performing in subscribe().
In the example below the "methods" are converted into fields since there is no reason for them to be methods anymore (you can keep them as methods if you want). Then the concat operator is used to create a single stream, where methodOne$ will execute and then when it's complete, methodTwo$ will execute.
Because concat executes in order, you are guaranteed that globaleVariable will be set by methodOne$ before methodTwo$ begins.
globleVariable: any;
methodOne$ = this.someService.pipe(tap((res) => this.globleVariable = res));
methodTwo$ = this.someService.pipe(tap((res) => console.log(this.globleVariable.someField));
ngOnInit() {
concat(this.methodOne$, this.methodTwo$).subscribe();
}
You can create a subject for which observable 2 will wait to subscribe like below :-
globalVariable: any;
subject: Subject = new Subject();
methodOne() {
this.someService.subscribe((res) => { this.globleVariable = res; this.subject.next(); });
}
methodTwo() {
this.subject.pipe(take(1), mergeMap(() => this.someService)).subscribe((res) => {
console.log(this.globleVariable.someField) });
}
The only way to guarantee a method call after a subscription yields is to use the subscription callbacks.
Subscriptions have two main callbacks a success and a failure.
So the way to implement a method call after the subscription yeilds is to chain it like this:
globleVariable: any;
ngOnInit() {
this.methodOne();
}
methodOne() {
this.someService.subscribe((res) => {
this.globleVariable = res
this.methodTwo(); // <-- here, in the callback
});
}
methodTwo() {
this.someService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
You might want to chain the calls with some other rxjs operators for a more standard usage.
ngOnInit() {
this.someService.method1.pipe(
take(1),
tap(res1 => this.globleVariable = res1)
switchmap(res1 => this.someService.method2), // <-- when first service call yelds success
catchError(err => { // <-- failure callback
console.log(err);
return throwError(err)
}),
).subscribe(res2 => { // <-- when second service call yelds success
console.log(this.globleVariable.someField) });
});
}
Please remember to complete any subscriptions when the component is destroyed to avoid the common memory leak.
my take,
so it's a bit confusing when you use same service that throws different results, so instead of someService I used firstService and secondService here.
this.firstService.pipe(
switchMap(globalVariable) =>
this.secondService.pipe(
map(fields => Object.assign({}, globalVariable, { someField: fields }))
)
)
).subscribe(result => {
this.globalVariable = result;
})
What I like about this approach is that you have the flexibility on how you want to use the final result as it is decoupled with any of the property in your class.

Not load data onClose dialog ? using Angular

I want to trigger load data ( which also load on init ) when user close dialog.
in AddUser-component.ts i have any logic and only important part
public onClose: Subject<any> = new Subject<any>();
this.addService.addNewUser(user).subscribe(res => {
this.onClose.next(res.body)
this.bsModalRef.hide();
}
and when trigger this in main component AllUser-components.ts i have
public addNewUser() {
this.bsModalRef = this.modalService.show(AddUserComponent);
(this.bsModalRef.content as AddUserComponent).onClose.subscribe(singleUserFromChildComponent)=>{
this.loadUser(); // HERE IS PROBLEM... when i console.log this.allUsers i don't see new added user .....
if (bill) {
this.lastAddedItem(this.allUsers, singleUserFromChildComponent);
}
this.bsModalRef.hide()
})
}
and above i have loadUser
private loadUser() {
this.service.getAllUsers(
).subscribe((res) => {
this.allUsers = res.body!;
})
}
this.allUsers var is not updated....not load new user.... why ? I load data and allUsers need to be updated with new data
this.allUsers = res.body!;
but not... i don't know why ?
Well its because the loadUser() method is asynchronous, and while you are loading it on modal close, it does not finish yet (also having nested subscriptions is a kind of bad practice, you can read more in that thread Why nested subscription is not good?)
What I can suggest you is to remove the subscribe invocation from loadUser method and return just an observable:
private loadUser() {
return this.service.getAllUsers().pipe(map((res: any) => res.body!),tap((res: any) => this.allUsers = res));
}
In your OnInit (or constructor method initialize allUsers in this way.
this.loadUser().pipe(first()).subscribe();
and then refactor your modal close part with the switchMap( it will wait untill your service.getAllUsers finished)
public addNewUser() {
this.bsModalRef = this.modalService.show(AddUserComponent);
(this.bsModalRef.content as AddUserComponent).onClose
.pipe(tap(singleUserFromChildComponent => this.singleUserFromChildComponent = singleUserFromChildComponent), switchMap(addedUser => (this.loadUser()))).subscribe(_ => {
if (bill) {
this.lastAddedItem(this.allUsers, this.singleUserFromChildComponent);
}
this.bsModalRef.hide()
});
}
also please add the lastAddedUser property to your component
public singleUserFromChildComponent: any;

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

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()
});
}
}

chrome storage: Unable to change component state from a callback function

I'm having issues switching state of a material desing component from an asynchronous callback function.
HTML markup
<mat-slide-toggle
class="example-margin"
[color]="color"
[checked]="checked">
</mat-slide-toggle>
DOESN'T WORK
I'm sure that the callback function gets context to everything it needs, including "this.checked" variable, but for some reason it's not propagated to the material design component.
checked:boolean;
ngOnInit() {
chrome.storage.local.get('isActive', (data) => {
console.log(data); // {isActive:true}
console.log(this); //context is visible
this.checked = data.isActive; //true
console.log(this.checked); //true
});
}
WORKS.
This callback function works OK.
checked:boolean;
ngOnInit() {
setTimeout(() => {
this.checked = true;
}, 5000)
}
NOTE: There's definitely an issue with rendering. When I click some button, which is totally irelevant to this, this component gets re-rendered correctly.
I think what you could do is check whether data.isActive is equal to 'checked' and if it isn't, toggle the component so they're consistent. Something like this:
ngOnInit() {
chrome.storage.local.get('isActive', (data) => {
if (data.isActive != checked){
this.toggle();
}
});
}
That should trigger a change event, so that the change is propagated to the view.
I wrapped the async call around a Promise and it started to work. Funny thing the Observables (I also tried them) didn't work
Here is a working code
COMPONENT:
checked:boolean;
constructor(private storageService: StorageService) {}
ngOnInit() {
this.getStorageData();
}
ASYNC important
async getStorageData(): Promise<any> {
let storageData = await this.storageService.getStorageData();
console.log(storageData); //{isActive:true};
this.checked = value.isActive; //this works!!!
//if you want you can return a value and access it with then() function in the ngOnInit
}
SERVICE
public getStorageData():Promise<any> {
return new Promise((resolve, reject) => {
chrome.storage.local.get('isActive', (data) => {
console.log(data); //data
resolve(data); {isActive:true}
});
});
}
Can anyone explain why Observables don't work while promises do?

Categories

Resources