This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 1 year ago.
I am writing some post and get requests to access an API in Angular.
In my post request, I create a new item and get an id for that item.
To then write a get request to get that item, i need to append the item id to the url.
How can I access the id from the post request in the get request?
I have created the variable id that gets overwritten in createItem() and can be accessed in HTML by simply writing {{id}}. But I am not able to access the overwritten content from createItem() inside of getItem(); the variable id remains empty.
My code so far:
import { HttpClient } from '#angular/common/http';
import { Component, OnInit } from '#angular/core';
import { HttpHeaders } from '#angular/common/http';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: '...',
}),
};
type CreatedItem = {id: string; inventory: []}
#Component({
selector: 'home-component',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
})
export class HomeComponent {
url = 'url here';
id="";
constructor(private httpClient: HttpClient) {}
ngOnInit(): void {
this.createItem();
this.getItem();
}
createItem() {
this.httpClient.post(this.url, null, httpOptions).subscribe((res) => {
const data = res;
this.id = (data as CreatedItem).id;
});
}
getItem() {
this.httpClient
.get<any>(
this.url + this.id,
httpOptions
)
.subscribe((res) => {
const data = res;
});
}
getItem()'s subscription has no idea whether or not createItem()'s subscription has completed or not which will result in the id being null when getItem() fires. The ugly fix would be to only call getItem() once createItem()'s subscription is complete and there is an id:
ngOnInit(): void {
this.createItem();
}
createItem() {
this.httpClient.post(this.url, null, httpOptions).subscribe((res) => {
const data = res;
this.id = (data as CreatedItem).id;
this.getItem(this.id)
});
}
getItem(id: string) {
this.httpClient
.get<any>(
this.url + id,
httpOptions
)
.subscribe((res) => {
const data = res;
});
}
A better way to do this would be to use a rxjs switchmap
Related
I am working on an app in Angular 14 that requires authentication/authorization, reason for witch I use Keycloak Angular
.
I need to get the currently logged in user's data from the application.
For this purpose, I have a service:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { User } from '../../../models/user';
#Injectable({
providedIn: 'root'
})
export class UserFormService {
httpOptions: object = {
headers: new HttpHeaders({
'Content-Type' : 'application/json'
})
}
apiURL: string = 'http://localhost:8080';
constructor(private http: HttpClient) { }
public currentUserEmail: any;
public currentUserData: any;
public async getUserEmail(){
let currentUser = await this.keycloakService.loadUserProfile();
this.currentUserEmail = currentUser.email;
}
public getUserByEmail(email: string): Observable<User>{
return this.http.get<User>(`${this.apiURL}/getUserByEmail/${email}`, this.httpOptions);
}
}
I use it in a component:
public getUserByEmail() {
this.supplierFormService.getUserByEmail(this.currentUserEmail).subscribe(response => {
this.currentUser = response;
console.log('currentUser: ', response);
});
}
In keycloak.init.ts I have:
import { KeycloakService } from 'keycloak-angular';
export function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.init({
config: {
url: 'http://localhost:8085',
realm: 'MyRealm',
clientId: 'my-app'
},
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html'
}
});
}
ngOnInit(): void {
// Get user's email
this.getUserEmail();
// Get user's data by email
this.getUserByEmail();
}
The problem
Instad of returning the user's data, the service throws a 500 (Internal Server Error) and the email is undefined, as can be seen below:
http://localhost:8080/getUserByEmail?email=undefined
How do I fix this problem?
You should sync those two calls, the getUserByEmail may be excecuted faster then currentUserEmail is set:
async ngOnInit(): void {
// Get user's email
await this.getUserEmail();
// Get user's data by email
this.getUserByEmail();
}
decode jwt token returned from keycloak. It contains current user data and Id
Then get user by this id
I'm calling a Service from an onSubmit(). The service then calls a REST API for data. However the ordering is not what I'd expect.
I think I have 2 issues here:
The log ### CUSTOMER BEFORE RETURN doesn't seem to contain the retrieved data despite initialising the variable at the start of the method. So at this log line, it's still undefined. However at ### UNPACKED DATA the data is visible.
Even if customer was not undefined at the return of loadCustomer, it looks like the line ### CUSTOMER IN onSubmit is executed before data is retrieved and not after, which will be a problem since I need to use the data afterwards!
onSubmit(customerData) {
let customer = this.customerService.loadCustomer(customerData)
console.log("### CUSTOMER IN onSubmit", customer)
// use customer Object to process orders...
}
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable()
export class CustomerService {
constructor(private http: HttpClient) { }
loadCustomer(customerId: number) {
console.log("### Customer ID to get: ", customerId)
var myStr: string;
var myObj: any;
var customer: Object
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
this.http.get<any[]>('https://REGION.amazonaws.com/dev/customer', httpOptions).subscribe(
data => {
myStr = JSON.stringify(data);
myObj = JSON.parse(myStr);
console.log("### UNPACKED DATA", myObj['data']['Items'][customerId-1])
if (typeof myObj['data']['Items'][customerId] != "undefined")
customer = myObj['data']['Items'][customerId-1]
},
error => console.log('Failed to get customer.')
);
console.log("### CUSTOMER BEFORE RETURN: ", customer)
return customer;
}
OUTPUT IN CONSOLE:
customer.service.ts:21 ### Customer ID to get: 2
customer.service.ts:51 ### CUSTOMER BEFORE RETURN: undefined
cart.component.ts:64 ### CUSTOMER IN onSubmit undefined
customer.service.ts:38 ### UNPACKED DATA {customer_id: 2, address: "32 Big avenue", row_id: "a97de132-89ac-4f6e-89cd-2005319d5fce", name: "Dave Lawinski", tel_number: "777888999"}
From what I've gathered this looks like something to do with Observable / some form of asynchronous operation, however I've not been able to make sense of where I'm going wrong here.
you are returning an object before the http call returns, you need to return an observable and then subscribe to it in the component:
onSubmit(customerData) {
this.customerService.loadCustomer(customerData)
.subscribe((res) => {
console.log("### CUSTOMER IN onSubmit", res)
// use res Object to process orders...
})
}
and in your loadCustomer function:
loadCustomer(customerId: number) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
return this.http.get<any[]>('https://REGION.amazonaws.com/dev/customer', httpOptions)
.pipe(map((result) => {
const myStr = JSON.stringify(data);
const myObj = JSON.parse(myStr);
let customer;
console.log("### UNPACKED DATA", myObj['data']['Items'][customerId-1])
if (typeof myObj['data']['Items'][customerId] != "undefined") {
customer = myObj['data']['Items'][customerId-1];
}
return customer;
}));
}
I have an issue in my Angular web store when i refresh the window, i create a service that takes the user data from the server and then inject to the 'products' section with BehaviorSubject, my goal is to make just one request to the server:
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable({
providedIn: 'root'
})
export class DataService {
private userId = new BehaviorSubject<any>('');
currentUserId = this.userId.asObservable();
constructor() { }
sendUserId(message: string){
this.userId.next(message)
}
}
This works fine but the problem is when i refresh the window in products section, in console i can see that the service takes the user data but when i getProducts() it throws an error, it seems like getProducts() makes the request before the service had the response, i need the user Id to make the products request. My question: Is there a way to await the response of BehaviorSubject and then make the getProducts() request?. This is the code in the products section:
ngOnInit(): void {
this._dataService.currentUserId.subscribe(userId => this.userId = userId);
if(this.userId.length === 0){
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else {
this.getProducts();
}
}
As you can see, i do a condition to check if userId exists, if not i have to make a new user request, this fix the bug but i think there's a better way to solve this. Thanks in advance.
How about placing all your logic within the observer's next function as below:
this._dataService.currentUserId.subscribe(userId => {
if (userId.length === 0)
{
this.userService.getUserProfile().subscribe(
res => {
this.userDetails = res['user'];
this.userId = this.userDetails._id;
this.getProducts();
},
err => {
console.log(err);
}
);
} else
{
this.getProducts();
}
});
I wrote PUT and DELETE methods inside their functions ("editForm" and "deleteForm" respectively).
I wanted to display setAlert() function after each request successfully completes. therefore, I used .then() method and it works perfectly inside editForm function as you can see it below.
but when I do the same for deleteForm, .then() method does not works, because
it says: " Property 'then' does not exist on type 'Subscription' ". So how can I solve this?
Here is my component.ts file:
import { Component, OnInit, OnDestroy } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { FormService } from './forms.service';
import { HttpClient } from '#angular/common/http';
import { alert } from './alert';
#Component({
selector: 'app-forms',
templateUrl: './forms.component.html',
styleUrls: ['./forms.component.css']
})
export class FormsComponent implements OnInit {
alert: alert;
id: any;
posts: any;
constructor(public formService: FormService ,private route: ActivatedRoute,
private router: Router, private http: HttpClient) { }
ngOnInit() {
this.id=this.route.snapshot.params['id'];
this.alert = new alert();
this.posts = this.formService.getForms(this.id).subscribe(
(forms: any) => {
this.formService.form = forms[0];
}
);
}
editPost() {
this.formService.editForm().then((res:any) => {
this.formService.alert.setAlert("Post has been successfully saved !");
})
}
deletePost() {
this.formService.deleteForm().subscribe(
data => {
console.log("DELETE Request is successful ", data);
},
error => {
console.log("Error", error);
}
).then(() => {
this.formService.alert.setAlert("Post has been successfully deleted !");
})
}
}
Here is my service.ts file:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { form } from './form-interface';
import { alert } from './alert';
#Injectable({
providedIn: 'root'
})
export class FormService {
formsUrl = "https://jsonplaceholder.typicode.com/posts";
form: form = {
id: 0,
userId: 0,
title: '',
body: ''
};
alert: alert;
constructor(private http: HttpClient) {
this.alert = new alert();
}
getForms(id) {
return this.http.get('https://jsonplaceholder.typicode.com/posts'
+ "?id=" + id)
}
editForm() {
return fetch(this.formsUrl + "/" + this.form.id, {
method: 'PUT',
body: JSON.stringify(this.form),
headers: {
"Content-type": "application/json; charset=UTF-8"
}
})
.then(response => response.json())
}
deleteForm() {
return this.http.delete(this.formsUrl + "/" + this.form.id);
}
}
The editform method uses JavaScript fetch api to call the service, which returns the promise so then method works there. In deleteForm method, you are making a service call using angular HttpClient which returns observable. instead of using then you should use subscribe method
deleteForm() {
return this.http.delete(this.formsUrl + "/" + this.form.id);
}
In your component.ts
deletePost() {
this.formService.deleteForm().subscribe(
data => {
console.log("DELETE Request is successful ", data);
this.formService.alert.setAlert("Post has been successfully deleted !");
},
error => {
console.log("Error", error);
}
)
}
because http returns observable not promise. Use .subscribe here.It will solve your problem
You can use message when you get a proper response while you get the response in subscribe method and call alert into it
Like below
deletePost() {
this.formService.deleteForm().subscribe(
data => {
console.log("DELETE Request is successful ", data);
this.formService.alert.setAlert("Post has been successfully deleted !");
},
error => {
console.log("Error", error);
}
))
}
I have a strange problem while using Angular 4 Observables.
I have created a ServiceProxy.ts that manages all my HTTPS calls for my app
#Injectable()
export class ServiceProxy
{
private base_url = 'https://localhost:8443';
constructor (private http:Http) {}
public Request(route:ServiceRegistry, data : any , protocol:HttpProtocol)
{
let url : string = this.FormURI(route);
let headers = new Headers();
this.createAuthorizationHeader(headers);
if(protocol==HttpProtocol.get)
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
else
{
return this.http.post(url , data , {headers: headers})
.map(this.extractData)
.catch(this.handleError);
}
}
}
Now I go ahead and INJECT this ServiceProxy class in every SERVICE which needs an HTTP calls
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p);
}
}
Once that is done. I call the authentication service from my component
this.authService.attemptLogin(payload).subscribe(response =>
{
alert("Subscription Received");
if(response.status==R.STATUS.OK)
{
this.HandleLogin(JSON.stringify(response.data));
}
else
{
this.HandleFailedLogin();
}
});
Problem is - The subscription function is being called two times instead of just once.
I understand, Promise would be a better fit here as this is just one HTTP call , however I want to standardize the interface with Observables and hence not considering Promises
Observable Chain is not proper, it's broken in AuthenticationService.
Modify AuthenticationService class
#Injectable()
export class AuthenticationService
{
constructor(private proxy:S.ServiceProxy){ }
attemptLogin(d:L.LoginAuth): Observable<any>
{
let r:S.ServiceRegistry =S.ServiceRegistry.STAFF_LOGIN;
let p: S.HttpProtocol = S.HttpProtocol.post;
return this.proxy.Request(r,d,p).map(
(data) => {
return data;
}
).catch(this.handleError);
}
}