How to pass observer variable as parameter? - javascript

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

Related

How to use one function for different BehaviorSubjects

I used to have one banner at the top of the page for all events in my app (like some Errors, Warnings, and Success) and used for that BehaviorSubject.
For example:
in the main app.component.html file I had:
<baner [alerts]="alerts$ | async"></baner>
and alerts get from bannerService:
ngOnInit(): void { this.alerts$ = this.bannerService.alerts$; }
the service looks next:
alertSub$ = new BehaviorSubject<string>('');
alerts$ = this.alertSub$.asObservable();
showWarning(message: string): void {
const newAlert = { message, type: 'Warning' };
this.alertSub$.next([...this.alertSub$.getValue(), newAlert]);
setTimeout(() => this.dismiss(newAlert), 500);
}
dismiss(alert): void {
const updatedAlerts = this.alertSub$.getValue().filter(alertSub => alertSub !== alert);
this.alertSub$.next(updatedAlerts);
}
...and so on...
So when I wanted to add some warning, I called this.bannerService.showWarning('some msg') and everything was fine.
But now I need to add a banner inside another component for its own warnings, and it should be independent. This means that global warnings would be still on the top of the app, but warnings of this component are only inside the component.
I understand, that I should create a new BehaviorSubject, but how to re-use all functions correctly?
For now, I've added to all functions a parameter, that pass proper BehaviorSubject, but in that case, I need to make changes in all places, where bannerService was used.
Service with my new changes:
alertSub$ = new BehaviorSubject<string>('');
alerts$ = this.alertSub$.asObservable();
componentSub$ = new BehaviorSubject<string>('');
componentAlerts$ = this.componentSub$.asObservable();
showWarning(message: string, banner: BehaviorSubject<string>): void {
const newAlert = { message, type: 'Warning' };
banner.next([...banner.getValue(), newAlert]);
setTimeout(() => this.dismiss(newAlert, banner), 500);
}
dismiss(alert, banner: BehaviorSubject<string>): void {
const updatedAlerts = banner.getValue().filter(alertSub => alertSub !== alert);
banner.next(updatedAlerts);
}
...and so on...
Would be really grateful for any idea, on how to use old functions for different BehaviorSubjects.
I think it can be a bit easier than that. Your baner component is responsible of rendering the messages, right? What if you modify this component to take in two instances of bannerService instead of just one? Let's suppose this is our BannerComponent:
export class BannerComponent implements OnInit {
bannerService: BannerService;
constructor(
#Host() #Optional() parentBannerService: BannerService,
#Inject() globalBannerService: BannerService
) {
this.bannerService = parentBannerService ?? globalBannerService;
}
This allows us to ask the injector for an (optional) instance of BannerService that is provided by the parent component (the component that renders the BannerComponent component).
In case we don't have such a thing, we still want the BannerService to be injected from somewhere, hence the second parameter, globalBannerService.
Now all that is left for us to do, is to provide a BannerService instance from our custom component that displays the banner:
#Component({
selector: 'app-component-with-its-own-banner',
// template, css, etc
providers: [BannerService]
})
export class ComponentWithItsOwnBanner {
// ...
}
The template of this component also includes the banner component selector:
<baner [alerts]="bannerService.alerts$ | async"></baner>
Everything else can stay exactly the same. You don't need to create any additional behavior subjects.

Get observable return value without subscribing in calling class

In TypeScript / Angular, you would usually call a function that returns an observable and subscribe to it in a component like this:
this.productsService.getProduct().subscribe((product) => { this.product = product });
This is fine when the code runs in a class that manages data, but in my opinion this should not be handled in the component. I may be wrong but i think the job of a component should be to ask for and display data without handling how the it is retrieved.
In the angular template you can do this to subscribe to and display the result of an observable:
<h1>{{ product.title | async }}</h1>
Is it possible to have something like this in the component class? My component displays a form and checks if a date is valid after input. Submitting the form is blocked until the value is valid and i want to keep all the logic behind it in the service which should subscribe to the AJAX call, the component only checks if it got a valid date.
class FormComponent {
datechangeCallback(date) {
this.dateIsValid$ = this.dateService.checkDate(date);
}
submit() {
if (this.dateIsValid$ === true) {
// handle form submission...
}
}
}
You can convert rxjs Observables to ES6 Promises and then use the async-await syntax to get the data without observable subscription.
Service:
export class DateService {
constructor(private http: HttpClient) {}
async isDateValid(date): Promise<boolean> {
let data = await this.http.post(url, date, httpOptions).toPromise();
let isValid: boolean;
// perform your validation and logic below and store the result in isValid variable
return isValid;
}
}
Component:
class FormComponent {
async datechangeCallback(date) {
this.dateIsValid = await this.dateService.isDateValid(date);
}
submit() {
if (this.dateIsValid) {
// handle form submission...
}
}
}
P.S:
If this is a simple HTTP request, which completes on receiving one value, then using Promises won't hurt. But if this obersvable produces some continuous stream of values, then using Promises isn't the best solution and you have to revert back to rxjs observables.
The cleanest way IMHO, using 7.4.0 < RxJS < 8
import { of, from, tap, firstValueFrom } from 'rxjs';
const asyncFoo = () => {
return from(
firstValueFrom(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
)
);
};
asyncFoo();
// Outputs "World" once
asyncFoo().subscribe((foo) => console.info(foo));
// Outputs "World" twice
The "more cleanest" way would be having a factory (in some service) to build these optionally subscribeable function returns...
Something like this:
const buildObs = (obs) => {
return from(firstValueFrom(obs));
};
const asyncFoo = () => {
return buildObs(
of('World').pipe(
tap((foo) => {
console.info(foo);
})
)
);
};

Which RxJS type to use when a method may or may not fetch data asynchronously?

Imagine we have the following factory:
#Injectable()
export class DataClassFactory {
constructor(
private dataService: DataService,
) { }
public createThing(initialData?: InitialData): AsyncSubject<DataClass> {
let dataClass: AsyncSubject<DataClass> = new AsyncSubject<DataClass>();
if (!!initialData) {
dataClass.next(new DataClass(initialData));
dataClass.complete();
} else {
this.dataService.getData().subscribe((dataResponse) => {
dataClass.next(new ReportRequest(dataResponse));
dataClass.complete();
});
}
}
return dataClass;
}
}
We inject this factory, invoke the createThing method, and subscribe to the response in some component. I originally tried to use a plain Subject, but then I realized that in the case where we already have initial data, next() is called before the response is returned, so the subscriber in the component never gets that value.
My question is: is this correct situation in which to use an AsyncSubject, or is there a different/better way to handle this sort of method that has potential synchronous and asynchronous timelines?
I would do something along these lines
public createThing(initialData?: InitialData): Observable<DataClass | ReportRequest> {
if (!!initialData) {
const data = new DataClass(initialData);
return of(data);
} else {
return this.dataService.getData()
.pipe(map(dataResponse => new ReportRequest(dataResponse));
}
}
Whoever calls createThing would get an Observable to which it would have to subscribe.
This Observable would emit an instance of DataClass if initialData is not null, otherwise it would return and instance of ReportRequest as soon as dataService responds.

How to use data from an observable in other methods?

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.

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