I want to unsubscribe an observable in an angular service once a certain state is present. The unsubscribe should be executed within the subscription. Unfortunately the code below does not work.
#Injectable()
export class CartManagementUsecase {
private unsubscribe = new Subject();
public streamSession(): void {
this.adapter
.streamSession()
.pipe(takeUntil(this.unsubscribe))
.subscribe((session) => {
if(session.session_state === SessionState.CLOSED) {
this.unsubscribe.unsubscribe();
}
});
}
}
I would recommend you to take events untill closed is come. no subject would be required
public streamSession(): void {
this.adapter
.streamSession()
.pipe(takeWhile(session => session.session_state != SessionState.CLOSED))
.subscribe((session) => {
// do something that is required
});
}
You need to call complete on your subject for takeUntil to unsubscribe to the observable.
#Injectable()
export class CartManagementUsecase {
private unsubscribe = new Subject();
public streamSession(): void {
this.adapter
.streamSession()
.pipe(takeUntil(this.unsubscribe))
.subscribe((session) => {
if(session.session_state === SessionState.CLOSED) {
this.unsubscribe.complete();
}
});
}
}
Related
I have the following MobX class that maintain async operation:
import { makeObservable, observable, action } from "mobx";
class AsyncAction<T, P = void> {
public isLoading = false;
public error?: unknown;
public response?: T;
constructor(
private asyncAction: (payload: P) => Promise<T>,
) {
makeObservable(this, {
error: observable,
isLoading: observable,
response: observable,
setLoading: action,
setError: action,
setResponse: action,
});
}
setLoading(isLoading: boolean) {
this.isLoading = isLoading;
}
setError(error: unknown) {
this.error = error;
}
setResponse(response: T | undefined) {
this.response = response;
}
async run(payload: P) {
try {
this.setLoading(true);
const response = await this.asyncAction(payload);
this.setResponse(response);
} catch (error) {
this.setError(error);
} finally {
this.setLoading(false);
}
}
}
export { AsyncAction };
I also have the following store that extends AsyncAction:
import api from '../api';
import { AsyncAction } from "./AsyncAction";
class StatusesStore extends AsyncAction<Record<string, Status>> {
constructor() {
super(async () => {
const { statusesMap } = await api.fetchStatusesMap();
return statusesMap;
});
makeObservable(this, {
setStatus: action,
});
}
public setStatus(name: string, status: Status) {
if (this.response) {
this.response[name] = status;
}
}
}
When I trigger from my component rootStore.statusesStore.setStatus('name', 'DONE'), the component doesn't get updated.
When open devools, I see the following:
Instead of being wrapped with observable, the object keys are plain strings. This might be a reason why changing the status string triggers nothing.
How can I fix that? What am I missing?
Since async processes are resolved in the next tick of the event loop, mobx can't track the changes after the tick. One of the solutions is to use
runInAction function after every await keyword.
Like this:
import {runInAction} from 'mobx'
async run(payload: P) {
try {
this.setLoading(true);// this is okay
const response = await this.asyncAction(payload);
// everything after "await" must be wrapped
runInAction(()=>{
this.setResponse(response);
})
} catch (error) {
this.setError(error); // wrap in runInAction
} finally {
this.setLoading(false); // wrap in runInAction
}
}
There are also a few other alternatives how you can deal with promises in combination with Mobx. For me, runInAction is the most straightforward way.
For other examples check out official documentation:
Asynchronous actions
I have a problem with a function that returns wrong return instead of proper return.
Model:
export module AbsModule {
export class AbsModul{
abs: string;
state: boolean;
}
}
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { environment } from '../../environments/environment';
import { httpFactory } from '#angular/http/src/http_module';
import { AbsModule } from './abs-module';
#Injectable()
export class AbsService {
private apiUrl: string = environment.apiUrl;
constructor(private http: HttpClient) { }
getAbs(): object {
return this.http.get(this.apiUrl + "/abs/download").subscribe(data => {
const returnFromFunction = this.assigneAbs(data);
console.log(returnFromFunction);
console.log(data);
return returnFromFunction;
})
}
private assigneAbs(data: object) {
return Object.assign(new AbsModule.AbsModul(), data);
}
}
Calling function:
#Component({})
export class TestClass {
constructor(
private tests: AbsService
) {}
test(): void {
console.log(this.tests.getAbs())
}}
After that I am receiving return from subscribe instead of returnFromFunction. When I am calling this function first I see the return from Subscriber from the Test function, then I see the console log from returnFromFunction and data. Return returnFromFunction does not work - it does not return this result.
Right now you just return the observable that's returned by the http.get method. You could fix this by awaiting for the response to come and then returning the resolved and processed value.
async getAbs(): Promise<object> {
const response = await this.http.get(this.apiUrl + "/abs/download").toPromise();
return this.assigneAbs(response);
}
getAbs is an asynchronous method now so you will have to treat it appropriately in your test suite.
async test(): Promise<void> {
console.log(await this.tests.getAbs());
}
The http.get(...) method returns an observable, and the subscribe method returns an object of type Subscription.
Now, the argument being passed to the subscribe method is actually a callback, a function that returns void. Hence, having such callback return an actual value, 'returnFromFunction' in your case, is meaningless.
An observable serves as a publisher notifying its observers of some new data, so a better approach would be to have the service expose an observable and have the component (as well as a tester) subscribe to it:
export class AbsService {
private apiUrl: string = environment.apiUrl;
constructor(private http: HttpClient) { }
getAbs(): Observable<any> {
return this.http.get(this.apiUrl + "/abs/download")
.map(data => this.assigneAbs(data));
}
private assigneAbs(data: object) {
return Object.assign(new AbsModule.AbsModul(), data);
}
}
export class TestClass {
constructor(
private tests: AbsService
) {}
test(): void {
this.tests.getAbs().subscribe(data => console.log(data));
}
}
I have a method in the component that calls a service which returns an observable
Component Method code
public upload(file) {
this.Service.ToBase64(files[0])
.subscribe(data => (this.convertedFile = data));
}
This works fine but when I chain unsubscribe to it, it stops working.
With Unsubscribe - This does not work
public upload(file) {
this.Service.ToBase64(files[0])
.subscribe(data => (this.convertedFile = data)).Unsubscribe();
}
Service Code method
convertedFile$: Subject<string> = new Subject<string>();
ToBase64(file: any) {
const myReader = new FileReader();
myReader.onloadend = e => {
this.convertedFile$.next(myReader.result.toString().split(',')[1]);
};
myReader.readAsDataURL(file);
return this.convertedFile$.asObservable();
}
As this a subject, I would like to unsubscribe. How can I do that correctly?
You must declare a Subscription property
First in your component
import { Subscription } from 'rxjs';
Then
fileSubscription: Subscription;
And in your method
public upload(file) {
this.fileSubscription = this.Service.ToBase64(files[0])
.subscribe(data => (this.convertedFile = data));
}
In ngOnDestroy method
ngOnDestroy() {
if (this.fileSubscription) {
this.fileSubscription.unsubscribe();
}
}
Regards
The method is unsubscribe() not Unsubscribe(). But a more elegant way to get a value of observable and destroy the subscription is use the first operator like that:
import { first } from 'rxjs/operators';
public upload(file) {
this.Service.ToBase64(files[0]).pipe(first())
.subscribe(data => (this.convertedFile = data));
}
On my component, I get some translations from my service.
The service makes a call to an MVC API controller.
The code on the component:
private getTranslations(): void {
this._translationService.getTranslations('Foo');
}
The code on the service:
public translations: Translation[] = new Array<Translation>();
public getTranslations(action: string) {
this._http.get<Translation[]>(this.baseUrl + action).subscribe(
(result: Translation[]) => {
result.forEach(element => {
this.translations.push(element);
});
},
(error: any) => this._loggerService.logError(error)
);
}
In the service, it sets a value on the variable this.translations in a subscription.
How can I wait in my component for this to "complete" meaning that the public variable this.translations is set?
I saw a thread to wrap it in a new Promise() but I wasn't able to figure it out in my example. Any method can be used.
You can return an observable from your service:
public translations: Translation[] = new Array<Translation>();
public getTranslations(action: string) {
return new Observable((observer) => { // <-- return an observable
this._http.get<Translation[]>(this.baseUrl + action).subscribe(
(result: Translation[]) => {
result.forEach(element => {
this.translations.push(element);
});
observer.complete(); // <-- indicate success
},
(error: any) => {
this._loggerService.logError(error);
observer.error(error); // <-- indicate error
}
);
});
}
and subscribe in the component:
private getTranslations(): void {
this._translationService.getTranslations('Foo').subscribe({
error: () => { /* handle error */ },
complete: () => { /* handle complete */ },
});
}
What I trying to do is to make the save method to wait for this.collection.create() before executing the save because otherwise it might crash.
class UserRepository extends BaseRepository<User>
{
constructor()
{
super();
this.collection = this.db.collection('users');
this.collection.create().then(res => {
if (res.code === 200)
{
// collection successfully created
}
}).catch(err => {
if (err.code === 409)
{
// collection already exists
}
});
}
}
class BaseRepository<T>
{
protected db: Database = Container.get('db');
protected collection: DocumentCollection;
public save(model: T): void
{
this.collection.save(model);
}
}
And then I can use it like this:
const userRepository = new UserRepository();
userRepository.save(new User('username', 'password'));
I can think of two solutions
Run this.collection.create() synchronously
Create a property called isCollectionReady and make a small loop in save method that wait for isCollectionReady value to change to true.
Is there any better way to do that?
Definitely don't use a loop; JavaScript is single-threaded, and the async event will never complete.
You can store off the Promise for the initialization instead and simply chain onto it:
class UserRepository extends BaseRepository<User>
{
constructor()
{
super();
this.collection = this.db.collection('users');
this._initPromise = this.collection.create().then(res => {
if (res.code === 200)
{
// collection successfully created
}
}).catch(err => {
if (err.code === 409)
{
// collection already exists
}
});
}
ensureInitialized() {
return this._initPromise;
}
}
class BaseRepository<T>
{
protected db: Database = Container.get('db');
protected collection: DocumentCollection;
public ensureInitialized() {
return Promise.resolve();
}
public save(model: T): Promise<void>
{
return this.ensureInitialized()
.then(() => this.collection.save(model));
}
}