Typescript Resolve Observable to Entity - javascript

I want to resolve an observable to it's underlying type. Yes, I am aware I should NOT be doing so, but at the moment I would like to test something without Fixing My architecture, and I would like to know if its possible.
This is a function in the base provider to call a GET request
getByRoute(url: string): Observable<T> {
return this.http.get(url).map(res => <T>res.json());
}
I had an only class returning a concrete object with just an Id on it.
//public currentUser = { id: 1 };
But now I'm trying to implement my provider to lazy load the current user, I don't have time right now to switch everything to use an observable, but I would like to know my other code works.
private _user: User;
get currentUser(): User {
if (this._user == null) {
this._user = this.getLoggedInUser();
}
return this._user;
}
set currentUser(user: User) {
this._user = user;
}
constructor(public usersProvider: Users) {
}
getLoggedInUser(): User {
return this.usersProvider.getByRoute(URL).single();
};
How can I Resolve my Observable to get the entity?

You will need to subscribe to the observable and then assign it to the property on your class.
this.usersProvider.getByRoute(URL).subscribe(next => {
this.currentUser = next;
});
Note that you will need to handle cleanup of this subscription on your own! (this thread will help with that: Angular/RxJs When should I unsubscribe from `Subscription`)
Also, you can't really "resolve" an Observable -- it is not a Promise! Check out this thread for more on that: Angular - Promise vs Observable

Related

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

Handling parallel nested observables in RxJS

I'm working on cleaning up nested parallel observables in my project and I am having some issues wrapping my head around handling the observable events. Here's one situation in my music application where I load the Playlist page and need to load the playlist data, information about the user, and if the user is following the playlist:
Route Observable (returns parameters from URL, I need to extract the playlistId)
Playlist Observable (needs playlistId and returns PlaylistObject and resolves)
User Observable (returns UserObject)
IsUserFollowing Observable (needs playlistId and UserObject.id and returns T/F if user is following the playlist)
In summary, I receive the route parameter from Angular's Route observable, I need to fire two other observables in parallel, the Playlist Observable and User Observable. The Playlist Observable can resolve once the playlist data is returned, but the User Observable needs to continue on to another observable, IsFollowingUser, until it can resolve.
Here is my current (bad) implementation with nested observables:
this.route.params.pipe(
first()
).subscribe((params: Params) => {
this.playlistId = parseInt(params["playlistId"]);
if (this.playlistId) {
this.playlistGQL.watch({
id: this.playlistId,
}).valueChanges.pipe(
takeUntil(this.ngUnsubscribe)
).subscribe((response) => {
this.playlist = response; // use this to display playlist information on HTML page
this.playlistTracks = response.tracks; //loads tracks into child component
});
this.auth.user$.pipe(
filter(stream => stream != null)
).subscribe((user) => {
this.user = user;
this.userService.isFollowingPlaylist(this.user.id, this.playlistId).subscribe((response) => {
this.following = response; //returns a T/F value that I use to display a follow button
})
})
}
This works but obviously does not follow RxJS best practices. What is the best way to clean up nested parallel observables like this?
This is something I would do although this may not be the most optimal but could be better than before.
I will assume the previous code block is in an ngOnInit.
async ngOnInit() {
// get the params as if it is a promise
const params: Params = await this.route.params.pipe(
first()
).toPromise();
this.playlistId = parseInt(params["playlistId"]);
if (this.playlistId) {
// can name these functions to whatever you like (better names)
this.reactToPlaylistGQL();
this.reactToUser();
}
}
private reactToPlaylistGQL() {
this.playListGQL.watch({
id: this.playListId,
}).valueChanges.pipe(
takeUntil(this.ngUnsubscribe)
).subscribe(response => {
this.playlist = response;
this.playlistTracks = response.tracks;
});
}
private reactToUser() {
this.auth.user$.pipe(
filter(stream => stream != null),
// switch over to the isFollowingPlaylist observable
switchMap(user => {
this.user = user;
return this.userService.isFollowingPlaylist(this.user.id, this.playListId);
}),
// consider putting a takeUntil or something of that like to unsubscribe from this stream
).subscribe(response => this.following = response);
}
I find naming the functions in the if (this.playlistId) block to human friendly names makes it unnecessary to write comments (the descriptive function name says what it should do) and looks cleaner than putting a blob of an RxJS stream.
I wrote this all freehand so there might be some typos.

How to return a response from a request(post | get)?

There is a variable in the service that checks if the user is authorized:
get IsAuthorized():any{
return this.apiService.SendComand("GetFirstKassir");
}
SendCommand(within the usual post request orget):
ApiService.SendComand(Command: string, Params?: any): Observable<any>
And I have to process the component as follows:
this.authorizeService.IsAuthorized.subscribe(
res => {
if (res.Name.length === 0) this.router.navigate(['/login'])
}
);
How do I make get IsAuthorized () returned string Name(not Observable<any>) (which is inside thesubscribe. And the component I could write console.log(this.authorizeService.IsAuthorized);
Here the thing you are handling is asynchrony. So the IsAuthorized attribute can either be a promise/observable. I believe all you are looking for is a simpler syntax. So for that we can use async/await.
get IsAuthorized():any{
return this.apiService.SendComand("GetFirstKassir").toPromise();
}
and to use in the component lets say in ngOnInit we can use it as below:
async ngOnInit() {
const isAuthorized = await this.authorizeService.IsAuthorized;
if(isAuthorized) { // works like normal boolean variable
}
}
Hope it helps!!! Cheers

(Angular) How to return different Observable type request in my case?

I am having trouble to return an observable. It seems like the codes inside the mergeMap is not running at all.
Codes:
book.service.ts
import {HttpClient, HttpHeaders} from '#angular/common/http';
export class bookService {
constructor(
private http: HttpClient,
...others
) {}
addNewBook(book): Observable<Book>{
##### Tried to use mergeMap otherwise the return type won't match
return this.tokenService.getToken().mergeMap((token: string) => {
console.log("Fire this...") <===== This is never output.
const myUrl = "www.testurl.com";
const parameters = {
bookTitle: book.name,
};
return this.http.post<Book>(myUrl, book);
})
}
token.service.ts
public token$: Subject<string>;
..others
public getToken(): Observable<string> {
return this.token$; <= return Observable<string> not Observable<Book>
}
book.component.ts that calls the addNewBook method.
...others
return Promise.resolve()
.then(() => {
return bookService.addNewBook(book);
}).then((result) => {
console.log(result);
})
I can't really change the token service because it's used on other place, I am not sure why the codes inside the mergeMap is not running. Can someone help me about it? Thanks a lot!
It won't work unless you subscribe to the results of bookService.addNewBook(book). Just returning it from the then callback won't subscribe. You need to at least add toPromise.
...others
return Promise.resolve()
.then(() => {
return bookService.addNewBook(book).toPromise();
}).then((result) => {
console.log(result);
})
In order for the mergeMap() to be be triggered, the token$ subject inside token.service.ts needs to emit a value (via .next()) after addNewBook() is subscribed to by a consumer.
One of the things to keep in mind with Subjects is that 'late subscribers' won't receive a value off of them until the next time .next([value]) is called on that Subject. If each subscriber, no matter how late, needs to immediately receive the last value generated by that source (Subject) then you can use BehaviorSubject instead.
From your short code example it is hard to see where the Observable generated by addNewBook() is being subscribed to though. Remember, a Observable won't execute until it has a subscriber.

Get instant access to Observable data

I'm using Angular, ngrx/store and rxjs
So I have a Cart reducer and an Items reducer.
When I'm fetching Items from the API I need to know if they are already in the Cart.
So since I have pagination implemented the best way I thought was to check that in the actual service that fetches Items and creating an InCart field inside the Items object.
I do not want to do it in subscribe because when I'm fetching Items I'm not changing the Cart state so the subscribe will never be called. And if I use map
I can't also access the data in the service.
So I'm really not sure on how to do this in a 'Best practice way'.
Probably I could also save the Cart object inside the Cart service in an old fashion way but I don't think that would be the best practice here.
This code below would be the ideal case scenario for me. (But ofc this doesn't work because I can't access the cart object using pluck or map.)
getItems(): Observable<Item[]> {
return this.http.get(url)
.map(this.extractData)
.map(this.extractItemlist)
.catch(this.handleError)
}
private extractItemlist(res: ApiResponse) {
switch (res.response.type) {
case ApiResponse.TYPE.ERROR:
throw res.response.data.message
case ApiResponse.TYPE.SUCCESS:
this.checkIfItemsAreInCart(res.response.data.list)
return res.response.data.list
}
}
private checkIfItemsAreInCart(items: Item[]) {
const cart = this.store.select<CartState>('cart').pluck('cart')
for (const item of items) {
for (const cartItem of cart.items) {
if (item.details.item_id === +cartItem.item_id) {
item.inCart = true
} else {
item.inCart = false
}
}
}
}
Does anyone have idea on how to achieve this? I'm a new to this observables stuff so probably that's why I still didn't came up with a good solution.
I would really appreciate some help.
I do not want to do it in subscribe because when I'm fetching Items I'm not changing the Cart state so the subscribe will never be called
Subscribe of Observable will be called in first time you call so it does not need "change".
private checkIfItemsAreInCart(items: Item[]) {
this.store.select<CartState>('cart')
.take(1)
.subscribe(cart => {
for (const item of items) {
for (const cartItem of cart.items) {
if (item.details.item_id === +cartItem.item_id) {
item.inCart = true
} else {
item.inCart = false
}
}
}
});
}

Categories

Resources