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

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?

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.

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.

Communicating between Angular components and non-Angular components: Returning data from an observable coming from a callback

I have an application that requires that I use non-Angular JavaScript for certain things. To trigger an action in the Angular component, I'm passing a down a callback to the non-Angular component. When the callback is triggered, an observable runs on the Angular component (doing an http call). This works but the only piece of the puzzle I'm having trouble with is getting the data returned from this observable passed back down to the non-Angular component somehow. My actual application is fairly complex so I've created a Stackblitz for a much more simplified version to make it easier to see what I'm doing.
This is tricky for me as the GET call in doStuff is async, so I can't just return the results. I'd have some ideas on how to work around this in a pure Angular app... but I'm not sure how to accomplish this when sharing data between an Angular component and a Non-Angular one.
app.component.ts:
export class AppComponent {
constructor(private http: HttpClient){}
doStuff() {
let randomNum = this.getRandomInt(2); // Simulate different http responses
this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
});
}
ngOnInit() {
var x = new NonAngularComponent(this.doStuff.bind(this));
}
private getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max)) + 1;
}
}
NonAngularComponent.ts:
export class NonAngularComponent {
constructor(private onSave: () => void) {
this.init()
}
init() {
const newElement = document.createElement('button');
newElement.innerHTML = 'Click';
newElement.addEventListener('click', () => {
this.onSave(); // Works, but now I need to do something with the results of doStuff()
});
document.getElementById('foo').append(newElement);
}
}
Any suggestions would be much appreciated.
It would be better to return Observable from your doStuff() method and use tap operator if you want to have some side effect in Angular component:
doStuff() {
let randomNum = this.getRandomInt(2);
return this.http.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`).pipe(tap(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
} else {
// Here is where I want to share data with the non-Angular component
console.log(x.id);
}
}));
}
non-angular.component.ts
newElement.addEventListener('click', () => {
this.onSave().subscribe(res => {
// do whatever you want
});
});
Forked Stackblitz
I think that the easiest solution would be to simply have an instance of your NonAngularComponent inside the AppComponent
this.nonAngularComponent = new NonAngularComponent(this.doStuff.bind(this));
And in the callback simply call the method you want from the NonAngularComponent like so:
doStuff() {
let randomNum = this.getRandomInt(2);
this.http
.get<any>(`https://jsonplaceholder.typicode.com/todos/${randomNum}`)
.subscribe(x => {
if (x === 1) {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
} else {
// Here is where I want to share data with the non-Angular component
// console.log(x.id);
this.nonAngularComponent.doSomething(x);
}
});
}
doSomething method:
public doSomething(result) {
console.log("Non-Angular component received result", result);
}
And console output:
Stackblitz: https://stackblitz.com/edit/angular-tddc7q?file=src%2Fapp%2FNonAngularComponent.ts

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

Categories

Resources