Extracting data from subscribe() method - javascript

I don't really know how to extract the values from the subscribe() method.
getMessages(): any {
this.gatewayMessagesState$.subscribe(data => data.gatewayMessages
.get(this.gatewayId)
?.list
.map(Message => Message.message));
}
gatewayMessagesState is an initial state that contains some data. gatewayMessages is a map with gatewayIds as keys and arrays of Message objects as values. Message has message field that's just a string. I would like to extract an array of messages for a given id. How can I do that?

What you probably want to do is to populate another Observable with the data so that you can access it elsewhere in your project without the need for calling the API more than once.
To do this, you create what is known as a Subject (in this case a BehaviorSubject) and you can populate that with data when your API call returns a response.
Then, in order to access this data elsewhere, you can create a "get" function to return the Subject (which is itself an Observable) whenever you need the data.
Here is an example:
my - data.service.ts
myData: BehaviorSubject < number > = new BehaviorSubject < number > (0);
callApi() {
this.dbService.get('apiUrl').subscribe(
(data) = > this.myData.next(data) // Assuming data is a 'number'
);
}
getMyData() {
return this.myData.asObservable();
}
Now to use this in a component:
this.myService.getMyData().subscribe(
(data) = > {
/* Use the value from myData observable freely */
}
);
Or you could rely on the Angular async pipe (which is a very convenient method for dealing with observables in your code).

You are not specifying if getMessages is in a service, component... in any case, I suggest returning the Observable without subscribing to it in the getMessages function
// this function could be anywhere
getMessages(): Observable<string[]> {
return this.gatewayMessagesState$.pipe(
map((data) => data.gatewayMessages.get(this.gatewayId)),
map((yourMap) => yourMap?.list.map((theMessage) => theMessage.message))
);
}
Now, if you need to extract this value, either from a component, a service, etc... then, just call this function and then subscribe to get the result
Let's say getMessages is in a service file for example
Your component
constructor(private theService: YourService) {}
anotherFunction() {
this.theService.getMessages().subscribe((myMessages) => console.log(myMessages));
}
Or let the async pipe subscribe to this observable
Your component
messages$!: Observable<string[]>
constructor(private theService: YourService) {}
anotherFunction() {
this.messages$ = this.theService.getMessages()
}
Your component html
<ng-container *ngIf="messages$ | async as messages">
<div *ngFor="let message of messages">
<p>{{ message }}</p>
</div>
</ng-container>

I this you want to retrieve the data as an observable of messages as string, you can define the function return as this and using pipe and map operatoes from rxjs,this is code below is my proposition
getMessages(): observable<string[]>{
return this.gatewayMessagesState$.pipe(map((data) =>
data.filter((f) => f.gatewayMessages.id ===this.gatewayId)),
map(item => item.message));
}

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

Why are my input variable properties undefined (Angular)

I'm fetching data from my service in the app component, and then passing it down to a child component via #Input. when I console.log the data in ngOnInit, it DOES show up in the child component, but when I try to pass it to a variable and use it in the child template, it's coming back undefined, and I'm not sure why.
Here's where I call my service in app.component.ts, the data that is having issues is this.colorcounts. Console logging here inside the ngOnInit DOES show the correct data. colorCounts has 3 properties: red, yellow & green:
ngOnInit() {
this.pciService.getPciInfo()
.subscribe((pciInfo) => {
this.spinner.hide();
this.pciData = pciInfo.sorted,
this.colorCounts = pciInfo.counts;
});
Here's the app.component.html template where I'm passing the data down:
<app-navbar *ngIf="colorCounts" [colorCounts] = "colorCounts"></app-navbar>
<app-system-status [pciData]="pciData"></app-system-status>
Here's the child component where I'm grabbing the data, doing a console.log in ngOnInit does work, but trying to use "red" in the template or save it in the class comes back undefined:
constructor(private pciService: PciService,
public dialog: MatDialog,
private decimalPipe: DecimalPipe) { }
AMVersion: any;
#Input() colorCounts: Colors;
openDialog(): void {
let dialogRef = this.dialog.open(AmVersionDialogComponent, {
panelClass: 'custom-dialog-container',
data: {}
});
(<AmVersionDialogComponent>dialogRef.componentInstance).AMVersion = this.AMVersion;
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
});
}
ngOnInit() {
this.pciService.getAMVersion()
.subscribe((AMInfo) => {
return this.AMVersion = AMInfo;
});
var red = this.colorCounts.red;
console.log(red)
console.log(this.colorCounts);
}
}
I know I'm probably missing something simple regarding the life cycle. Can someone point me in the right direction here?
All Observables are async so in template *ngIf condition will check variable and if it is null will return null but if You pipe variable as | async it will be checking this variable all time and when variable will apear not null it will show content ngIf.
*ngIf works only once !!! He not waiting for anny http calls and Observables are making one usualy. If You want to *ngIf wait for calls You need to use | async pipe inside.
Same as You subscribe to it in ngOnInit() and assign to variables in template. Subscription will assign those values later after template is allredy on screen. Read about what async means.
You need to know that this is a boilercode:
ngOnInit() {
this.pciService.getPciInfo()
.subscribe((pciInfo) => {
this.spinner.hide();
this.pciData = pciInfo.sorted,
this.colorCounts = pciInfo.counts;
});
It is better to do it like this:
ngOnInit() {
this.pciInfo$ = this.pciService.getPciInfo()
}
in template:
<ng-container *ngIf="pciInfo$ | async as pciInfo">
<app-navbar [colorCounts]="picInfo.counts"></app-navbar>
</ng-container>
Pipe Async will subscribe for you to an Observable.

How to make asynchronous function in angular?

In componentOne.ts i am sending the data through sharedService as,
this.sharedService.sendData(this.getCheckedProduct);
In componentTwo.ts i am subscribing the data like,
productList: any = [];
getAllProducts: any = [];
ngOnInit() {
this.sharedService.getData().subscribe(data => {
this.productList = data;
this.getProduct();
});
}
I am getting the data here in productlist then i need to call the function this.getProduct() which has the following,
getProduct() {
let tempArray = [];
this.productList.forEach(element => {
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).subscribe(res => {
tempArray.push(res.data);
});
});
this.getAllProducts = tempArray;
}
I need to pass the id element.product_obj_id to get the necessary data of that id..
I have tried changing the above like this,
ngOnInit() {
this.sharedService.getData().subscribe(data => {
data.forEach(element => {
this.getProduct(element);
})
});
}
async getProduct(element) {
let asyncResult = await this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).toPromise();
this.getAllProducts.push(asyncResult['data']);
}
Inside async getProduct(element) function i am, getting the data of this.getAllProducts but in html i am not getting the data.
HTML:
<div *ngFor="let product of getAllProducts">
Getting data
</div>
If i changed the above with async
<div *ngFor="let product of getAllProducts | async">
Getting data
</div>
I am getting error as,
ERROR Error: InvalidPipeArgument: '' for pipe 'AsyncPipe'
at invalidPipeArgumentError
To explain in detail, i need to send a data through sharedservice in componentone and recieve it in componenttwo then need to get the product_obj_id from that shared service data.
The on passing each id, i will get the data of that particular product and i need to store the final data recieved from this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).toPromise(); to getAllProducts..
AppConfig.settings.product.getProducts
is the url ..
How to achieve it in a async way.. UPto this i am getting data
this.getAllProducts.push(asyncResult['data']);
but outside the function i am not getting the value, Also the getAllProducts no more working on any scenario in html..
In scenario 1 explained at top of this question i have given the following
this.getAllProducts = tempArray;
This one gives empty array as value and so only i am trying with async function.
In simple words i need to get the final data from this.getAllProducts which will be recieved from the service with get method for which i need to pass an id in the url..
Simply assign your data inside the async call like this -
getProduct() {
let tempArray = [];
this.productList.forEach(element => {
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).subscribe(res => {
tempArray.push(res.data);
this.getAllProducts = tempArray; // Here
});
});
}
PS: In your use case no need to use pipe async.
AsyncPipe is to be applied on an Observable. In Your case, you are applying it on getAllProducts which is an Array, I believe. Hence you are getting that error.
Possible Solution:
remove async from your for loop of getAllProducts
Make getAllProducts return an Observable of your Products
Note: since you are looping through productList and calling the webapi to get the products, you might have to use mergeMap.
Solution 2 Sample:
I believe Solution 2 is best for you, since you productList contains all the products for which you need to hit the webapi to get the details of the products.
Learn MergeMap here
Sample code(untested & you need to add code for error handling with catchError):
import { from } from 'rxjs';
import { mergeMap, map, combineAll } from 'rxjs/operators';
this.getAllProducts = from(this.productList)
.pipe(
mergeMap(element =>
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).pipe(
map(res => res['data'])
)
),
combineAll()
)
<div *ngFor="let product of getAllProducts | async">
Getting data
</div
Explanation:
since you productList is an Array and you have to loop through it to and fire a request for each product object in that Array
from(this.productList) makes each object in productList into an observable. We add a pipe and using mergeMap we can fire request for each of the product, hence you see this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id) this inside mergeMap. We then map the response we get from the webapi and only return data using res['data']. Finally we combineAll the response which returns an Observable<[]>, which we set into getAllProducts, hence getAllProducts is an Observable and we can use AsyncPipe on it.
Edit:
Add the following in your constructor instead of in ngOnit
const getAllProducts = this.sharedService.getData().pipe(
mergeMap(data=> from(data))
).pipe(
mergeMap(element =>
this.appService.getRest(AppConfig.settings.product.getProducts + '/' + element.product_obj_id).pipe(
map(res => res['data'])
)
),
combineAll()
)

angularfirebase2 list query returning undefined

I must be seriously misunderstanding something with RxJs and AngularFirebase2 because I can't figure out this error.
I have a firebase list that I am querying with a function in a service like so:
returnAuthor(id) {
this.db.list('/flamelink/users', ref => ref.orderByChild('id').equalTo(id)).valueChanges().subscribe(data => { console.log(data); return data })
}
The console.log(data) in returnAuthor produces the correct data, but {{ returnAuthor(id) }} in my template returns undefined. Running returnAuthor in my component also returns undefined.
Can somebody please point me in the right direction here? Do I need to subscribe in the component and not the service?
Your method returns undefined because you are not returning a result from the function.
To return a result, your function will look like this
returnAuthor(id) {
return this.db.list('/flamelink/users', ref => ref.orderByChild('id').equalTo(id)).valueChanges().subscribe(data => { console.log(data); return data })
}
But returning like that will return a subscription not the data. If you want the data from the subscription, you can declare a variable and set the data returned from the subscription to it. Something like this
in your class
dataVariable;
returnAuthor(id) {
this.db.list('/flamelink/users', ref => ref.orderByChild('id').equalTo(id)).valueChanges().subscribe(data => { console.log(data); this.dataVariable = data })
}
Now the data available from the subscription will be passed onto the dataVariable that you can use.
In case you want to pass the data to another method when it arrives, you can call the method in the subscription. Something like this
returnAuthor(id) {
this.db.list('/flamelink/users', ref => ref.orderByChild('id').equalTo(id)).valueChanges().subscribe(data => { console.log(data); anotherMethod(data); })
}

Angular2 observable with JSON root element

This is my first time ever working with angular observables and I'm a bit confused on how this works. I was given a mostly functioning angular CLI app that I just need to wire up to my already existing API.
I have a service with this function
public getApps(): Observable<ApplicationInterface[]> {
return this.http.get(url);
}
Then in my component, I have
public data: ApplicationInterface[];
ngOnInit() {
this.route.params
.subscribe(params => {
this.fetchData();
});
}
fetchData() {
this.service.getApps()
.subscribe(data => {
this.data = data;
});
}
My api endpoint returns a JSON structure of {"applications": []}
I can't seem to figure out how to access the array in that JSON hash.
If I console.log(data) in the subscribe block, it is the API response with the applications key that I expect, but if I change the data assignment to this.data = data.applications, ng build fails with Property 'applications' does not exist on type 'ApplicationInterface[]'
You should design the interface to be aligned with the response. If the response is object, than you need to have it like this also in the interface.
Try something like this (using the new HttpClient):
interface ApplicationInterfaceResponse {
applications: ApplicationInterface[];
}
public getApps(): Observable<ApplicationInterface[]> {
return this.httpClient
.get<ApplicationInterfaceResponse>(url)
.map(response => {
console.log(response.applications);
return data.applications;
});
}
If your return is of type ApplicationInterface[], then it's an array of ApplicationInterfaces, thus does not have a property called applications on it. This has nothing to do with your observable; it's fine. Rather, you've mistyped your variable.
If you don't need any other properties of data, you can map the value:
public getApps(): Observable<ApplicationInterface[]> {
return this.http.get(url).map(data => data.applications);
}
However, I recommend against this in most situations. If your object changes in the future, then you have to change this function and all attached subscriptions. Instead, you should create an interface for your response (your response right now does not match the type you're giving it), and use values of it as necessary.
The simplest fix is to indicate the correct form of the data that is returned by your service method since it doesn't actually return an array:
public getApps(): Observable<{applications:ApplicationInterface[]}> {
return this.http.get(url);
}
Now in your subscribe, you can get at the array as you would expect
.subscribe(e => this.data = e.applications)

Categories

Resources