How to use data from an observable in other methods? - javascript

I am working on an Angular4 app.
Here is a service I am using to get the data-
export class BookingService {
constructor(private http: HttpClient) {}
getMemberBookings(memberID: number): Observable<any> {
return this.http.get('http://myapi.com/bookings/member/'+memberID).map(response => response['bookings']);
}
}
And then in my component-
export class PrintButtonComponent implements OnInit {
bookings: any;
constructor(private service: BookingService) {}
ngOnInit() {}
downloadPDF() {
this.getBookings(memberID);
//pdf creation logic
}
getBookings(memberID: number) {
this.service.getMemberBookings(memberID).subscribe(data => this.bookings = data);
}
}
The problem is I want to use the data from the service in the downloadPDF method as there is other data in it that will be needed to create the PDF.
But when I return the data from the subscribe or set it to a property, it is giving undefined. I understand that this is due to asynchronous nature, but I dont want to put my pdf creation logic inside the subscribe method.
So how do I solve this problem? I am quite new to Angular and observables.
Thank you.

Since the code above doesn't involve multiple values per observable and doesn't require to stick to them, this can be done with async..await with no extra nesting:
async downloadPDF() {
await this.getBookings(memberID);
//pdf creation logic
}
async getBookings(memberID: number) {
this.bookings = await this.service.getMemberBookings(memberID).toPromise();
}
As any other RxJS operator, toPromise has to be imported.

Related

How to make subscribe method to run first & then proceed further

I am new to Angular and struck with subscribe method. I need to trigger subscribe first in which initialization of property is done. Please help me with this.
Here is my code..
json.service.ts
#Injectable({providedIn: 'root'})
export class JsonService {
constructor(private http: HttpClient) {}
//This fetch the json present in assets folder
url: string = `${window.location.origin}/assets/urls-list.json`;
getRestJson() {
this.http.get(url);
}
url.setting.ts
#Injectable({providedIn: 'root'})
export class UrlService {
fetchedJson: Array<any>;
constructor(private service: JsonService) {
this.fetchingJsonFromService();
}
fetchingJsonFromService() {
this.service.getRestJson().subscribe(
response => {
this.fetchedJson = response;
});
}
private static getValue(key: string) {
// use the key do some stuff with "this.fetchedJson"
and return only that value back to getServiceUrl()
return something;
}
public static getServiceUrl(key:string) {
const result = UrlService.getValue(key);
return result;
}
}
From debugging this code realized that "getValue()" is called first and the "this.fetchedJson" is undefined. Then the call goes to "fetchingJsonFromService()" where fetchedJson gets initialized which is too late. Kindly help me to solve this issue. It would be of great help.
"getServiceUrl(key)" - is called by multiple services throughout the application
Thanks a lot!! 😊
As getRestJson() is async you'll need to execute getValue() when it's response has arrived, which You can simply do by calling getValue() inside your fethcingJsomFromService()
fetchingJsonFromService() {
this.service.getRestJson().subscribe(
response => {
this.fetchedJson = response;
this.getValue();
});
}
private getValue() {
// do some stuff with "this.fetchedJson" and return only that value to other method
}

How to pass observer variable as parameter?

The component has variable applicationObs as observer:
export class OrderDetailsComponent implements OnInit {
public applicationObs: Observable<ApplicationResponse>;
public getSections(): SidebarMenuItem[] {
const fabricApplicationSidebar = fabricApplicationSidebarMenu(this.application); // Here
}
}
How to pass applicationObs to fabricApplicationSidebarMenu() when this.application is not observer, but ApplicationResponse.
How to pass async data as variable in function?
I'd suggest you think about this a little differently, what you actually want to do is add a new function onto your observable pipe. so I'd do something like this:
export class OrderDetailsComponent implements OnInit {
public applicationObs: Observable<ApplicationResponse>;
public getSections(): Observable<SidebarMenuItem[]> {
return applicationObs.pipe(
map(m => fabricApplicationSidebarMenu(m))
);
}
}
Here were adding a new pipe onto the existing observable and returning that. this is still async so in itself doesn't do anything. To get the sections you'll still need to subscribe to the resulting observable:
this.getSections().subscribe((s: SidebarMenuItem[]) => {
});
You could also potentially use an async pipe if you wished. See here
Note that the above will trigger the applicationObs and whatever fabricApplicationSidebarMenu does on subscribe. This may or may not be desirable depending on what your planning on doing and what these functions do.
what do you want to do?
something like this?
this.applicationObs.subscribe(
(applicationResponse: ApplicationResponse) => {
const fabricApplicationSidebar = fabricApplicationSidebarMenu(applicationResponse); // Here
});

Angular - Using a service property set by callback in another component

I am trying to use a DataService property myData that is waiting for callback. But it is undefined when I call in DataComponent. How can I access and use it there?
export class DataService {
public myData;
constructor(private http: HttpClient) {
this.load().then((data) => {
this.myData = data
})
}
load() {
return new Promise((resolve) => {
this.http.get('https://reqres.in/api/users').subscribe(
(res: any) => {
console.log(res.data)
resolve(res.data)
},
(error) => {
console.log(error);
}
)
})
}
}
export class DataComponent implements OnInit {
constructor(private dataService: DataService) {
this.prepareData();
}
prepareData() {
console.log(this.dataService.myData)
}
ngOnInit(): void {
}
}
Here is the source code: https://stackblitz.com/edit/angular-ivy-kbpdpo
You are running into a race condition since this is an asynchronous function.
This change works: https://stackblitz.com/edit/angular-ivy-vf3llg
Consider reading up on https://angular.io/guide/http
Personally, I just have services return raw data and manipulate it elsewhere, but if needed you can tap into the response as I have shown i the updated example.
This question and answer are probably really a duplicate of this question...
What are pipe and tap methods in Angular tutorial?
your load() method is asynchronous, that means that it can return the response after 2 hours, so it will execute your callback after 2 hours, and you are asking myData synchronously which means that you are asking it right now, so it won't work.
you have to wait until the answer is returned, in your code there is no chance to accomplish this, so either remove yourData field and just subscribe it into the component, or create BehaviorSubject and emit value to the component

RxJS & Angular HttpClient: How to transform value asynchronously?

I want to apply an asynchronous transformation function on the value emitted by an observable.
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url);
}
}
In the code above, I want to apply a transformation function myFunc, which returns a promise, on the value emitted by this.http.get(url).
Normally I would use the map operator of RxJS, but since the transformation function returns a promise, I could not find a way to handle it.
For example, let my function be:
function myFunc(value){
return new Promise((resolve, reject) => {
// modify the value async
resolve(modifiedValue);
// ...
});
}
Is there an appropriate way to handle this task? I think the following is not suitable, am I right?
return this.http.get(url).map(myFunc);
Any help will be much appreciated.
Note: I'm using RxJS 5.5.2
Use the mergeMap operator to take in the response value, perform some modification asynchronously via another Observable operation, and then return the modified value. This operator will merge the values emitted by HttpClient and your modifier Observable and return a single Observable that emits the mutated values.
EDIT: Including the Observable.fromPromise bit from #bygrace's comment for a more complete answer.
i.e.
EDIT: For RxJs v5.5+
import { pipe } from 'rxjs/util/pipe';
import { mergeMap } from 'rxjs/operators';
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url).pipe(
mergeMap(myFunc)
);
}
private myFunc(x): Observable<any> {
// do some asynchronous modification that returns an Observable
return Observable.fromPromise(x);
}
}
Pre RxJs v5.5
#Injectable
export class ApiService{
constructor(private http: HttpClient){}
getSomething(url): Observable<any>{
return this.http.get(url)
.mergeMap(data => {
// do some asynchronous modification that returns an Observable
return Observable.fromPromise(data);
});
}
}
See: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-mergeMap

Call component logic when state changes in ngrx

I'm currently developing an application with Angular using redux principle with ngrx.
I'm looking for a best practice for reacting to state changes and call some component logic depending on this state. I'll give you an (simplified) example to make clear what I mean:
reducers.ts
import {createSelector} from 'reselect';
export const getViewTypeOrFilterChanged = createSelector(isLoading, getActiveViewType, getActiveFilter, (isLoading, activeViewType, activeFilter) => {
// ensure that data is loaded
if (!isLoading) {
return {
activeViewType: activeViewType,
activeFilter: activeFilter
};
}
});
example-component.ts
#Component({ ... })
export class ExampleComponent implements OnInit {
// properties ...
constructor(private store: Store<fromRoot.AppState>) {
}
ngOnInit() {
this.subscriptions.push(
this.store.select(fromRoot.getViewTypeOrFilterChanged).subscribe((result) => {
if (result) {
this.property1 = result.activeType;
this.dependentHelperClass.method1(result.activeFilter);
this.method1();
this.method2(result.activeFilter);
this.method3();
}
})
);
}
ngOnDestroy() {
this.subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
}
// methods ...
}
As you can see I'm also using reselct to combine three different slices of state within a selector (getViewTypeOrFilterChanged). In the subscription to this selector I then want to take some actions according to the combined state.
The thing is, I'm feeling like using ngrx store and subscriptions more in a way of publish/subscribe pattern here and it feels not quite correct. Also the subscriptions (I have multiple ones) in ngOnInit and unsubscriptions in ngOnDestroy bother me, but I can't think of a way achieving the same results using e.g. async pipe.
Is there maybe a more elegant way of reacting to (combined) state changes?
Thanks!
With RxJS you should think of everything as a stream - the following code is just as an example, because I don't really know any of your UI-logic so just look at the structure and not at the logic of the code, since it's more like a very wild guess of mine:
#Component({ ... })
export class ExampleComponent implements OnInit {
private destroyed$ = new Subject<boolean>();
// the following streams can be used in the controller
// as well as in the template via | async
// the .share() is just so the | async pipe won't cause unneccessary stream-creations (the result should be the same regardless of the .share(), it's just a minor performance-enhancement when using multiple | async)
isLoading$ = this.store.select(fromRoot.isLoading).share();
activeViewType$ = this.store.select(fromRoot.getActiveViewType).share();
activeFilter$ = this.store.select(fromRoot.getActiveFilter).share();
activeViewTypeAndFilter$ = Observable.combineLatest(this.activeViewType$, this.activeFilter$).share();
constructor(private store: Store<fromRoot.AppState>) {
}
ngOnInit() {
this.isLoading$
.filter(isLoading => !isLoading) // the initial stream will not emit anything until "loading" was false once
.switchMapTo(this.activeViewTypeAndFilter$)
.do([viewType, filter] => {
this.dependentHelperClass.method1(activeFilter);
this.method1();
this.method2(activeFilter);
this.method3();
})
.takeUntil(this.destroyed$) //this stream will automatically be unsubscribed when the destroyed$-subject "triggers"
.subscribe();
}
ngOnDestroy() {
this.destroyed$.next(true);
this.destroyed$.complete();
}
// methods ...
}
As I said: logic-wise I cannot say if this is what you need, but that's just a question of using different operators and/or a different order to arrange your "main-stream" differntly.

Categories

Resources