This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 6 years ago.
I'm writing project using Angular and RxJS. I implemented injectable class that retrieves data from JSON like this:
import {Injectable, Inject} from '#angular/core';
import {Http} from '#angular/http';
import 'rxjs/add/operator/map';
import {Student} from './student.data';
#Injectable()
export class RosterService {
private students : Student[];
constructor(#Inject(Http) private http:Http){}
private getStudents(){
this.http.get('/JSON/students.json')
.map(data => data.json().students)
.subscribe(data => this.students = data);
}
public getRoster() {
this.getStudents();
return this.students;
}
}
After I inject RosterService into constructor of AppComponent (including into #Component as provider):
export class AppComponent {
public students : Student[];
constructor(private _rosterService : RosterService) {
this.students = _rosterService.getRoster();
}
}
But when I call getRoaster() method, it doesn't wait until getStudents (async get call) is executed. In the result I get undefined value.
How can I deal with it? Thanks for responce.
I would use call back function, for example if it is a promise then i would use something like below.
var data;
asyncCall().then((result:any) => {
data = reuslt;
return data;
});
Not sure if that's what your looking for.
Updated:
#Injectable()
export class RosterService {
private students : Student[];
constructor(#Inject(Http) private http:Http){}
private getStudents(){
return this.http.get('/JSON/students.json')
.map(data => data.json().students)
.subscribe(data => this.students = data);
}
public getRoster() {
return this.getStudents().then (() =>{
return this.students;
});
}
}
And inside your AppComponent
export class AppComponent {
public students : Student[];
constructor(private _rosterService : RosterService) {
this._rosterService.getRoster().then ((data:<T>)=>{
this.students =data;
});
}
}
It seems to me that you've fundamentally missed a key design pattern in Javascript, which is the callback.
Instead of returning a value from the function, you pass in some code to execute when the function is good and ready.
function doSomething(success) {
var data = 1;
// blah blah blah, something happens.
success(data);
}
function myFunction(data) {
console.log(data);
}
doSomething(myFunction);
That way, you get to keep the asynchronous nature of the call, which doesn't block the single thread that Javascript has available.
I'm assuming your asynchronous functions return promises.
First, the logic is kind of weird there.. data is scoped to the function, func can't access data.
Here's what you probably want to do:
function() {
return func().then(data => data)
}
Except that makes that the same as this:
function() {
return func()
}
..and in that case you might as well just replace that function with func!
func
(or if func is a property of some other object, () => func().)
EDIT: This new function is an asynchronous function so of course it will return a promise. You'll need to use .then on it later to get the data from it.
Related
I have a method in an Angular component that pulls data via HttpClient subscription, and assigns it to an attributes this.allData, then instantiates an empty dictionary of parameters based on this, to pass to a second function:
export class TestComponent implements OnInit {
allData: object[] = []
activeData: object = {}
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.getData()
this.makeRequestBasedOnData()
}
getData() {
this.http.get(this.url).subscribe(res => {
for (let datum of res["data"]) {
this.allData.push({
"key": Object.keys(datum)[0],
"values": Object.values(datum)[0]
})
this.activeData[Object.keys(datum)[0]] = ""
}
})
}
makeRequestBasedOnData() {
let testParams = this.activeData
console.log(testParam)
}
}
I need these steps to happen sequentially. At the moment, logging the testParams in makeRequestBasedOnData() simply shows an empty object {}. When I try to return arbitrarily in the first method, I get a TypeScript error that you cannot assign a promise to type void.
How do I enforce synchronicity here even though neither method actually returns anything?
You can return Observable from getData method and proceed with any other methods within subscribe:
ngOnInit(): void {
this.getData().subscribe(() => this.makeRequestBasedOnData());
}
getData() {
return this.http.get(this.url).pipe(
tap(res => {
// update allData
})
);
}
where:
pipe method allows us to provide any kind of transformation with the data returned from http.get(...) call.
tap is a rxjs operator for side-effects, meaning that we can do everything we want with the response without modifying Observable flow.
I have a service that connects with api
export class ConsolidadoApi {
constructor(private http: HttpClient) { }
getInvestiments(search?: any): Observable<any> {
return this.http.get<any>(`${environment.basePosicaoConsolidada}`);
}
}
Response this api:
https://demo5095413.mockable.io/consolidado
This one is responsible for the logic before reaching the component
#Injectable({
providedIn: 'root'
})
export class CoreService {
public test;
constructor(private api: ConsolidadoApi, private state: StateService) { }
public createMenu() {
this.api.getInvestiments()
.subscribe(response => {
console.log(response.carteiras[0])
this.products = response.carteiras[0]
return this.products;
})
}
In my component
export class MenuComponent implements OnInit {
constructor( private coreService : CoreService ) {}
ngOnInit(): void {
console.log(this.coreService.createMenu())
}
}
But when createMenu is called in menu.component.ts it comes undefined.
The raw response is an object. forEach works only on an array. If you are aiming for forEach in 'categorias', you should try
this.test.categorias.forEach()
When you return Observable<any>, that means the argument of the lambda you create when you do subscribe (which you named response) is type any. This doesn't necessary have the function forEach defined (unless the API returns an object with that prototype). That's generally why using any is not good practice; you can't have any expectations on what the object can contain. In fact, it's possible that it's not on object (it could be an array since any is not exclusively an object). If you do want to use forEach, you will want to make sure that response is type array. You can inspect the object's type before using it (e.g. using typeof) and make a judgement on what to call or even just check if the function you're trying to use is defined first, e.g. if (response.forEach !== undefined). You don't actually need to compare to undefined though, so if (response.forEach) suffices. In the examples, I used response, but you can use this.test since they are the same object after the first line in the lambda.
Based on the link you shared, the response is an object. You can log it to the console to confirm.
You can only call for each on an array, so for example, based on the response api, you can call forEach on the property ‘categorias’ and on that array’s children property ‘produtus’
Edit: this answer was based on the op original api and question
https://demo5095413.mockable.io/carteira-investimentos
public createMenu() {
return this.api.getInvestiments()
}
ngOnit() {
this.coreService.createMenu().subscribe(x => console.log(x.categorias))};
{
"codigo":1,
"categorias":[
{
"nome":"Referenciado",
"valorTotal":23000.0,
"codigo":"2",
"produtos":[
{
"nome":"CDB Fácil Bradesco",
"valor":2000.0,
"codigo":1,
"quantidade":0.0,
"porcentagem":0.5500,
"aplicacaoAdicional":500.0,
"codigoInvest":1,
"salaInvestimento":"CDB",
"permiteAplicar":true,
"permiteResgatar":true,
"movimentacaoAutomatica":false,
"ordemApresentacao":37,
"horarioAbertura":"08:30",
"horarioFechamento":"23:59",
"codigoGrupo":0,
"codigoMF":"001
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I've been rewriting an Angular app which previously omitted the use of http requests. For this reason, lots of logic is contained in a service component and the http request was simulated by working with an array of the following format:
[{"name": "somename1", "subset1": [
{"name": "somesubname1", "subset2": [
{"somemetric1": 0, "somemetric2: 1, "somemetric3": 2}]}]}]
This is exactly the format which is also displayed on some web api (https://localhost:5001/api/values e.g.).
My service looks like this:
import { Injectable } from '#angular/core'
import { Subject, Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class StrategyService{
data:any
constructor(private httpClient: HttpClient) {
}
getData() {
this.data = this.httpClient.get("https://localhost:5001/api/values")
}
If I console.log(this.data) in e.g. getData() and call this function when running my app, "undefined" is returned in the console. When I would replace the body of getData() with
this.httpClient.get("https://localhost:5001/api/values").subscribe(data => {data = console.log(data)})
the console does return the array in the format as desired (and displayed in the first block). Console logging is in fact not what I need since I need to be able to access the data in other functions inside my service component which would need array-specific methods such as .find etc.
I feel like this is a simple thing to do, but I've had no succes so far. I've tried
import { Injectable } from '#angular/core'
import { Subject, Observable } from 'rxjs';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class StrategyService{
dat:any
constructor(private httpClient: HttpClient) {
}
getData() {
this.httpClient.get("https://localhost:5001/api/values").subscribe(data => {this.dat = data})
}
console.log(this.dat) still returns "undefined".
This is a classic starting problem everyone has with async code. Have a research of rxjs + how async code works in Typescript.
The solution to your problem is to check the data within the subscribe block as that happens after the response from your api is back.
this.httpClient.get("https://localhost:5001/api/values").subscribe(data => console.log(data))
public myFunction() {
this.httpClient.get("https://localhost:5001/api/values").subscribe(data => {
this.data = data;
console.log(this.data); // data is defined here
})
console.log(this.data); // data will be undefined here
}
Links to useful resources:
https://www.learnrxjs.io/
https://angular.io/guide/http
I am new to Angular, JS, and observables. I have a typescript class called DataService. I want it to load a list of URLs from a JSON formatted local file, and then have some way to call those URLs (to a handful of REST APIs) and return observables. The problem I am having is my code is not waiting for the config file to be loaded before the REST API functions get called.
I thought I could have the DataService constructor load the configuration file, and then have unique functions for each REST API call, but that isn't working
my code:
export class DataService {
configFile
constructor(private http: HttpClient) {
this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.configFile = config;
});
}
getUrlFromConfigFile(name: string): string {
...
this returns the URL from the config file
...
}
getUrlAData(): Observable {
return this.http.get( getUrlFromConfigFile('A') )
}
}
My other components have code like this:
export class SomeComponent implements OnInit {
someComponentAData
constructor(private data: DataService) { }
ngOnInit() {
this.data.getUrlAData().subscribe(
data => {
this.someComponentAData = data
}
)
}
I am getting an error that the observable returned from the dataservice is undefined. Which I believe is because the constructor hasn't finished loading the config file, which I think is why the function getUrlAData isn't returning anything.
I feel like I'm not correctly handling these async calls, but I'm at a loss for how to tell my code to :
create the data service object
load the data file before anything else can be done
allow the other functions to be called asyncronously AFTER the config file is loaded
Angular CLI: 6.2.3
Node: 8.12.0
OS: win32 x64
Angular: 6.1.8
Edit 1: attempting to implement suggested solution
My DataService
configFile
configObservable: Observable<any>;
someSubscribeObj
constructor(private http: HttpClient) {
this.someSubscribeObj = this.http.get('/assets/restApiUrlListConfig.json').subscribe(config => {
this.someSubscribeObj = undefined;
this.configFile = config;
});
}
getObsFromConfigFile(name: string): Observable<any> {
//...
if (this.configFile != undefined) {
console.log('this.restApiUrlListConfig[name]',this.configFile[name])
return of(this.configFile[name])
}
else
return of(this.someSubscribeObj.pipe(map(c => c[name])))
//this.configObservable
//...
}
getUrlAData(): Observable<any> {
return this.getObsFromConfigFile('A').pipe(mergeMap(url => this.http.get(url)))
}
My other component:
constructor( private data: DataService ) { }
ngOnInit() {
//this.data.loggedIn.pipe((p) => p);
this.data.getUrlAData().subscribe(
data => {
this.urlAData = data
}
)
}
I was unable to store the "subscribe" into the observable, so I created a generic Any type varable, but at runtime I get a problem with the pipe command:
TypeError: this.someSubscribeObj.pipe is not a function
at DataService.push../src/app/services/data.service.ts.DataService.getObsFromConfigFile
(data.service.ts:67)
at DataService.push../src/app/services/data.service.ts.DataService.getUrlAData
(data.service.ts:74)
Edit 2: the unfortunate workaround
I am currently using two nested subscriptions to get the job done basically
http.get(config_file_url).subscribe(
config => {
http.get( config['A'] ).subscribe( adata => { do things };
http.get config['B'].subscribe( bdata => {do things };
}
)
I feel like I should be able to use a mergeMap of some sort, but I couldn't get them to work as I thought they would.
You need to wait on that async call, I would use a flatmap to get the value out of an observable.
export class DataService {
configFile
configObservable: Observable<any>;
constructor(private http: HttpClient) {
this.configObservable = this.http.get('/assets/restApiUrlListConfig.json').pipe(
map(config => {
this.configObservable = undefined;
this.configFile = config;
return configFile;
})
);
}
getUrlFromConfigFile(name: string): Observable<string> {
...
return of(configFile[name]) if configFile is set else return configObservable.pipe(map(c => c[name]));
...
}
getUrlAData(): Observable<string> {
return this.getUrlFromConfigFile('A').pipe(map(url => this.http.get(url)))
}
}
Basically you want to store the observable and keep using it till it completes, after it completes you can just wrap the config in an observable. The reason for wrapping it is to make the interface consistent, otherwise you have to have an if before every get.
So in normal javascript if I wanted to assign a value to a variable and then use that value outside of a function it would be done by declaring the variable first and then define it's value in the function. I'm brand new to typescript and angular so I am missing how to do this.
In the code below I am trying to get the value from a method in a service and then pass that value into my return. (I hope that makes sense). However I keep getting undefined on console.log(url) with no other errors.
emailsAPI() {
let url: any
this.apiUrlsService.urlsAPI().subscribe(
data => {
this.results = data
url = this.results.emails
}
);
console.log(url)
return this.http.get('assets/api/email_list.json')
}
api-urls service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
#Injectable()
export class ApiUrlsService {
constructor(
private http: HttpClient
) { }
urlsAPI () {
return this.http.get('assets/api/api_urls.json')
}
}
That's because you're calling async method subscribe and then trying to log the coming value before subscription is resolved. Put last two statements (console.log and return) inside the curly braces just after assigning this.results.emails to the url variable
emailsAPI(): Observable<any> {
let url: any
return this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
url = this.results.emails
// you can now access url variable
return this.http.get('assets/api/email_list.json')
});
}
As per reactive programming, this is the expected behaviour you are getting. As subscribe method is async due to which you are getting result later on when data is received. But your console log is called in sync thread so it called as soon as you are defining subscribe method. If you want the console to get printed. Put it inside your subscribe data block.
UPDATE:
As per your requirement, you should return Subject instead of Observable as Subject being data consumer as well as data producer. So it will consume data from httpget request from email and act as a producer in the method from where you called emailsAPI method.
emailsAPI(): Subject<any> {
let emailSubject:Subject = new Subject();
this.apiUrlsService.urlsAPI()
.flatMap(data => {
this.results = data
return this.results.emails;
}).
subscribe(url=> {
this.http.get(your_email_url_from_url_received).subscribe(emailSubject);
});
return emailSubject;
}
The subject can be subscribed same as you will be doing with Observable in your calee method.