Angular: normal array from observable [duplicate] - javascript

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

Related

observable undefined in component - Angular

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

how to subscribe the Id of the created object after POST operation

I have an component where i am adding a new object called customer by calling the api like this:
public onAdd(): void {
this.myCustomer = this.customerForm.value;
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
this.callSuccessMethod();
},
(error) => { // If POST is failed
this.callFailureMethod();
},
);
}
Service file:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import {ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer);
}
}
As shown in component code, i have already subscribed the api call like this:
this.myService.addCustomer(this.myCustome).subscribe(
() => { // If POST is success
.....
},
(error) => { // If POST is failed
...
},
);
But,I want to subscribe the results in another component, I have tried like this:
public getAddedCustomer() {
this.myService.addCustomer().subscribe(
(data:ICustomer) => {
this.addedCustomer.id = data.id; <======
}
);
}
I am getting this lint error: Expected 1 arguments, but got 0 since i am not passing any parameter.
What is the right approach to subscribe the api call in other components? after POST operation.
Because i want to get added object id for other functionality.
Well it totally depends on the design of your application and the relation between components. You can use Subjects for multicasting the data to multiple subscribers.
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs';
import { ICustomer } from 'src/app/models/app.models';
#Injectable({
providedIn: 'root',
})
export class MyService {
private baseUrl : string = '....URL....';
private latestAddedCustomer = new Subject();
public latestAddedCustomer$ = this.latestAddedCustomer.asObservable()
constructor(private http: HttpClient) {}
public addCustomer(customer: ICustomer): Observable<object> {
const apiUrl: string = `${this.baseUrl}/customers`;
return this.http.post(apiUrl, customer).pipe(map((data) => this.latestAddedCustomer.next(data)));
}
}
and subscribing to the subject as follows
this.latestAddedCustomer$.subscribe()
should get you the latest added customer details. Even though i would not do this the way its written. I would basically write a seperate service to share the data between the components or would write a cache service if its used across the application. But the idea here is to use the concept of Subjects. You can read more about it Here

typescript/angular pass value out of this?

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.

Angular HttpClient get, wrong this object

in my Angular App i make a simple call to a node.js server. the HttpClient "get"
function returns the right answer. This answer I want to store in a variable of my component "interfaces". But in the "subscribe" function of the get request my "this" pointer doesn't point to my component. Instead it tells me that it is of type "SafeSubscriber". Any call to my member "interfaces" lead to the following error:
TypeError: Cannot read property 'interfaces' of undefined
export class SettingsComponent implements OnInit {
public interfaces : string[];
constructor(private http: HttpClient) {
this.interfaces = [];
this.interfaces.push("huhu");
}
ngOnInit() : void {
this.http.get('http://localhost:3000/settings/interfaces').subscribe((data) => {
// Read the result field from the JSON response.
console.log(data);
this.interfaces.push("xxx");
Object.keys(data).forEach(function(k) {
console.log(k);
this.interfaces.push("xxx");
});
}),
err => {
console.log("error " + err);
};
}
}
As you can see I also tried to enter some values manually into the array just to make sure, that not the server response is causing the problem.
Any help is appreciated.
I used this code as a blueprint which is from:
https://angular.io/guide/http
#Component(...)
export class MyComponent implements OnInit {
results: string[];
// Inject HttpClient into your component or service.
constructor(private http: HttpClient) {}
ngOnInit(): void {
// Make the HTTP request:
this.http.get('/api/items').subscribe(data => {
// Read the result field from the JSON response.
this.results = data['results'];
});
}
}
You're losing reference to the correct this in this statement:
Object.keys(data).forEach(function(k) {..})
Inside the function block code this refers to the calling context , which is the subscribe method itself, that's why interfaces is undefined, since it's not a property of the subscribe method.
You can change the function for a lambda en it should be fine:
Object.keys(data).forEach((k) => {..})

Return after async function in Angular [duplicate]

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.

Categories