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));
}
}
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 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();
}
});
}
}
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));
}
Calling init function from another class, console.log is never called. Later, calling broadcast gives the following error:
Uncaught (in promise) TypeError: Cannot read property 'next' of
undefined
File with observable code:
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/share';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import Deferred from './Deferred';
import * as m from '../Models/models';
let sharedServiceInstance = null;
export default class SharedService {
observable: Observable<any>;
observer: Observer<any>;
constructor() {
if(!sharedServiceInstance){
sharedServiceInstance = this;
}
return sharedServiceInstance;
}
init = () =>{
var deferred = new Deferred<any>();
if(this.observable != undefined){
deferred.resolve();
}
else{
this.observable = Observable.create((observer: Observer<any>) => {
this.observer = observer;
console.log("Observer: " + JSON.stringify(this.observer,null,4));
deferred.resolve();
}).share();
}
return deferred.promise;
}
broadcast(event: m.SharedEventModel) {
this.observer.next(event);
}
on(eventName, callback) {
return this.observable.filter((event) => {
return event.Name === eventName;
}).subscribe(callback);
}
}
File where observable is initiated and called:
import { Subject } from 'rxjs/Subject';
import SharedService from '../Services/sharedService';
import * as m from '../Models/models';
let initializeServiceInstance;
export default class InitializeService {
private sharedService = new SharedService();
public constructor(){
if(!initializeServiceInstance){
this.initialize();
initializeServiceInstance = this;
}
return initializeServiceInstance;
}
initialize =() =>{
var promise1 = this.sharedService.init()
.then(()=>{
//Debugger never reaches here
})
.catch((response)=>{
//Debugger never reaches here
var event = new m.SharedEventModel({
Name: m.EventSubjectEnum.AfterLogout
})
this.sharedService.broadcast(event);
})
}
}
NOTE: Using rxjs without angular.
In this block of code:
this.observable = Observable.create((observer: Observer<any>) => {
this.observer = observer;
console.log("Observer: " + JSON.stringify(this.observer,null,4));
}).share();
That inner function is not executed until there's a subscription. The only place subscriptions are happening is in .on, and .on is never called. So if there are no subscriptions yet, then this.observer will be undefined. Since it can be undefined, this.observer.next(event); can throw an error.
Also, if there are ever two subscriptions, then the second subscription will overwrite this.observer, thus making it so the first subscription will not get any notifications.
I think for what you're trying to do, you'll want to use a subject.
import { Subject } from 'rxjs/Subject';
// and other imports
export default class SharedService {
subject: Subject<any>
constructor() {
if(!sharedServiceInstance){
sharedServiceInstance = this;
}
return sharedServiceInstance;
}
init = () => {
this.subject = new Subject();
}
broadcast(event: m.SharedEventModel) {
this.subject.next(event);
}
// I don't recommend mixing callbacks and observables in this way
on(eventName, callback) {
return this.subject.filter((event) => {
return event.Name === eventName;
}).subscribe(callback);
}
// My recomendation would be to just return the observable
// That way the caller can decide whether they want to subscribe
// Or whether they want to do additional manipulation of the stream
//on(eventName) {
// return this.subject.filter((event) => {
// return event.Name === eventName;
// });
//}
}
How can I implement this promise scenario in ng2?
export class MySpecializedClass
{
myObject;
constructor(
private myService: MyService
)
{
this.myObject = new MyObject();
}
buildMyObject()
{
this.builderMethod1();
this.builderMethod2();
}
builderMethod1()
{
this.myService.getData1()
.then(response => this.myObject.Prop1 = response.Prop1 )
}
builderMethod2()
{
this.myService.getData2()
.then(response => this.myObject.Prop2 = response.Prop2 )
}
}
export class MyConsumerClass
{
myObect;
getMyObject()
{
this.myObject = new MySpecializedClass().buildMyObject().myObject;
}
}
The problem is that in the following line of code, myObject should not be referenced until builderMethod1() and builderMethod2() are guaranteed complete.
MyConsumerClass.getMyObject().myObject
I want builderMethod1() and builderMethod2() to run at the same time which is why they aren't chained in a then(). How could I implement this scenario with Promises? Or would Observables or a different approach provide a better solution?
Personally I find the async-await syntax a lot more readable than then chaining. Maybe you will too, this is how the same thing can be done using async-await:
export class MySpecializedClass
{
myObject : MyObject;
constructor(
private myService: MyService
)
{
this.myObject = new MyObject();
}
async buildMyObject()
{
const first = this.builderMethod1();
await this.builderMethod2();
await first;
return this.myObject;
}
async builderMethod1()
{
const response = await this.myService.getData1();
this.myObject.Prop1 = response.Prop1;
}
async builderMethod2()
{
const response = await this.myService.getData2();
this.myObject.Prop2 = response.Prop2;
}
}
export class MyConsumerClass
{
myObject;
async getMyObject()
{
this.myObject = await new MySpecializedClass(new MyService()).buildMyObject()
}
}
You can just return the promise:
builderMethod2()
{
return this.myService.getData2()
.then(response => {
this.myObject.Prop2 = response.Prop2;
return response.Prop2;
})
}
You can do this in both promise and Observable. I would prefer observable as it have more options over promise
promise
In case of promise, you need to return promise/differ. But in your case you are not returning anything and also when you say .then it resolves the promise
export class MySpecializedClass
{
myObject;
constructor(
private myService: MyService
)
{
this.myObject = new MyObject();
}
buildMyObject()
{
var diff = differed // find out exact differed object
return diff.all(this.builderMethod1(), this.builderMethod2());
}
builderMethod1()
{
return this.myService.getData1()
.then(response => this.myObject.Prop1 = response.Prop1 )
}
builderMethod2()
{
return this.myService.getData2()
.then(response => this.myObject.Prop2 = response.Prop2 )
}
}
export class MyConsumerClass
{
myObect;
getMyObject()
{
this.myObject = new MySpecializedClass().buildMyObject().then (() => {
myObject;
}
}
}
Observable
In case of observable you can you Observable.merger, Observable.formJoin etc to join 2 of your observable and subscribe them