*** - Hi guys, I've been having a problem for days. I am trying to populate
an object with the result of a query to a JSON API. I need to fill in
a model because through it I need to nail a key to make another query
in another api and return the data on the screen. But so far all I
can get is undefined
To better understand I need to fill the generation Object so that through it I can fill the data of another object and get a url to query another endpoint api and return other data from the screen.
export class PokeApp implements OnInit {
gen1: Generation[];
gen2: Generation[];
generation : Generation;
constructor(private http: HttpClient, private gService:GenerationService) {
}
ngOnInit(){
this.getGeneration1();
this.getGeneration2();
// Only for test, this is not the data ho i need - but this object generation returns null, i do not now how.
this.gService.getPokemon_Species(this.generation.name);
}
// this request return a gen1 object to my screen, but a need this object in JS code
// to do another query.
getGeneration1(): void{
this.gService.getGeneration1().subscribe(gen =>{
this.gen1 = gen
this.generation = gen[0];
});
}
getGeneration2(): void{
this.gService.getGeneration2().subscribe(gen => this.gen2 = gen);
console.log("Still Returned Undefined___>>>>" + this.generation);
}
// this do the request to a Poke API
export class GenerationService {
private GetGenerationURL1 = "https://pokeapi.co/api/v2/generation/1";
private GetGenerationURL2 = "https://pokeapi.co/api/v2/generation/2";
httpOptions = { headers: new HttpHeaders({ "Content-Type": "application/json" }) };
constructor(private http: HttpClient) { }
getGeneration1(): Observable<Generation[]> {
return this.http.get<Generation[]>(this.GetGenerationURL1)
.pipe(
tap(_ => console.log('fetched generations')),
catchError(this.handleError<Generation[]>('getGeneration1', []))
);
// Subscribe to begin listening for async result
}
getGeneration2(): Observable<Generation[]> {
return this.http.get<Generation[]>(this.GetGenerationURL2)
.pipe(
tap(_ => console.log('fetched generations')),
catchError(this.handleError<Generation[]>('getGeneration2', []))
);
}
getPokemon_Species(url: string): Observable<Pokemon[]> {
console.log("___>>>>${generation}" + url);
return this.http.get<Pokemon[]>(url)
.pipe(
tap(_ => console.log('fetched Species')),
catchError(this.handleError<Pokemon[]>('getPokemon_Species', []))
);
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
console.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}
Update
So the issue actually is with the typings. You don't need to add the [] after the the Generation anywhere. As there isn't any place that the API will respond with an Array of Generations.
So remove the [] from the returning type of getGeneration1 and in the typed response of the HTTP in the service.
Please note that the typings in Typescript are only for compiling time, it doesn't affect anything in the runtime, just to make sure you are using the right references and detect errors before runtime.
I'm adding the getGeneration functions here:
getGeneration1(): Observable<Generation> {
return this.http.get<Generation>(this.GetGenerationURL1)
.pipe(
tap(_ => console.log('fetched generations')),
catchError(this.handleError<Generation>('getGeneration1', []))
);
}
getGeneration2(): Observable<Generation> {
return this.http.get<Generation>(this.GetGenerationURL2)
.pipe(
tap(_ => console.log('fetched generations')),
catchError(this.handleError<Generation>('getGeneration2', []))
);
}
In the component, you will need to refactor it like this:
export class PokeApp implements OnInit {
gen1: Generation;
gen2: Generation;
generation : Generation;
constructor(private http: HttpClient, private gService:GenerationService) {
}
ngOnInit(){
this.getGeneration1();
this.getGeneration2();
this.gService.getPokemon_Species(this.generation.name);
}
getGeneration1(): void{
this.gService.getGeneration1().subscribe(gen =>{
this.gen1 = gen
this.generation = gen;
});
}
getGeneration2(): void{
this.gService.getGeneration2().subscribe(gen => this.gen2 = gen);
}
This is in case you still need your code in the component to work without chaining the responses as I provided in the old answer, but I suggest to refactor your code same as this:
getGenerations() {
this.gService.getGeneration1()
.pipe(mergeMap(gen => {
this.generation = gen;
return this.gService.getGeneration2();
}))
.pipe(mergeMap(gen => {
return this.gService.getPokemon_Species(this.generation.name);
}))
.subscribe(response => console.log(response));
}
Old Answer
You well need to use mergeMap. It should be something like this:
getGenerations() {
this.gService.getGeneration1()
.pipe(mergeMap(gen => {
this.gen1 = gen;
this.generation = gen[0];
return this.gService.getGeneration2();
}))
.pipe(mergeMap(gen => {
this.gen2 = gen;
return this.gService.getPokemon_Species(this.generation.name);
}))
.subscribe(response => console.log(response));
}
Related
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 am working on a project which is based on the nest.js framework
The following is a snippet of my function:
#Post('beneficiaries/:beneficiaryId/bankDetails')
#HttpCode(HttpStatus.OK)
async addBankDetails(#Param('beneficiaryId', new ValidationPipe()) beneficiaryHash: BeneficiaryHashIdDto, #Body() body, #Headers() headers) {
const beneficiary = await this.beneficiaryService.getBeneficiaryIdFromHash(beneficiaryHash, ['beneficiaryId', 'currencyCode', 'countryCode']);
let routingOptions = await this.beneficiaryService.getBeneficiaryRoutingConfig(beneficiary.beneficiaryId, pick(headers, GET_HEADERS_LIST));
routingOptions = lmap(routingOptions, partialRight(pick, ['bankDetail', 'beneficiaryRoutingConfigId']));
const [routingConfig] = routingOptions.filter(item => item.beneficiaryRoutingConfigId === body.beneficiaryRoutingConfigId);
if (!routingConfig) {
throw new BadRequestException('Invalid beneficiaryRoutingConfigId');
}
const { error } = this.beneficiaryService.bankDetailsSchema(routingConfig.bankDetail).validate(body, { abortEarly: false });
if (error) {
throw new BadRequestException(error);
}
// write here logic to validate routing codes
await this.beneficiaryService.validateBeneficiaryBankDetails(routingConfig, body, pick(headers, GET_HEADERS_LIST), beneficiary);
// write here logic to insert bank details of bene
return this.beneficiaryService.updateBankDetails(body, headers, beneficiary.beneficiaryId);
}
Nest allows us to extract the params, headers, body, etc of a request.
https://docs.nestjs.com/controllers
I want to extract a particular key from my params
For example my params contain:
1.clientId
2.customerId
3.beneficiaryId
I am able to take out the beneficiaryId and store it in beneficiaryHash but I am not able to perform a validation at the same time.Is there any work around?
You can reach it by custom pipes. as a example like ParseIntPipe
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '#nestjs/common';
#Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
#Get(':id')
async findOne(#Param('id', new ParseIntPipe()) id) {
return this.catsService.findOne(id);
}
for more knowledge please read https://docs.nestjs.com/pipes#transformation-use-case
I'm working on the test driven angular app. (Don't ask why, That is how client wants)
Below is the spec which I can't modify or edit.
it('should get results', fakeAsync(
inject(
[XHRBackend, NewsService ],
(mockBackend: MockBackend, newsService: NewsService) => {
const expectedUrl = 'https://api.nytimes.com/svc/topstories/v2/home.json?api-key=315a5a51483b469a918246dc2753b339';
mockBackend.connections.subscribe((connection : MockConnection) => {
expect(connection.request.method).toBe(RequestMethod.Get);
expect(connection.request.url).toBe(expectedUrl);
connection.mockRespond(new Response(
new ResponseOptions({ body: mockResponse })
));
});
newsService.getSectionNews('home')
.subscribe( (res: any) => {
expect(res).toEqual(mockResponse);
});
})
));
So based on the spec, I need to write my front end code.
So this is what I've written,
import { Http } from '#angular/http';
constructor(private http: Http) {}
getSectionNews(sectionName: string): any {
// fetch news of that sectionName
// return this.mockResponse;
const expectedUrl = 'https://api.nytimes.com/svc/topstories/v2/home.json?api-key=315a5a51483b469a918246dc2753b339';
return this.http.get(expectedUrl).subscribe(res => res);
}
But while running the test case, I'm getting this error:
TypeError: newsService.getSectionNews(...).subscribe is not a function
please tell me what I'm doing wrong here.
I wanted to pass the test case.
UPDATE
After updating my service spec.
getSectionNews(sectionName: string): Observable<any> {
const expectedUrl = `https://api.nytimes.com/svc/topstories/v2/${sectionName}.json?api-key=315a5a51483b469a918246dc2753b339`;
return this.http.get(expectedUrl);
}
Now I'm getting this below error,
Expected Response with status: null null for URL: null to equal
Objectt({ status: 'OK', copyright: 'C ...
I have a method on an service to handle all backend requests. Instead of writing a whole bunch of different calls using the HttpClient, I thought I might write one single function that could connect to my backend and pass it arguments to handle different types of data.
Consider this function
public postRequest(token: string, url: string, body: any, headers: Object = {}) : Observable<any> {
//create new header object
const httpOptions = {
headers: new HttpHeaders()
.set('Authorization', token)
};
//add the headers if any
for(let index in headers){
httpOptions.headers.set(index, headers[index]);
}
//connect to the backend and return the repsonse
return this.http.post( this.config.BASE_SERVER_URL + url, body , httpOptions)
.pipe(
map((res) => {
return res;
}),
catchError(this.handleError)
);
}
It works well except I wanted to be able to set the response type dynamically. Thus I could set the method to use one of my model types.
Here's what I'm trying to accomplish. Hopefully this makes sense.
map(res: "Attendee") => {}
//or
map(res: typeof(typeInput)) => {}
Is it possible to pas a "dynamic" type to the http map method so I can map the different responses to a model of my choice?
I can achieve this by using generic methods.
you can use this approach.
my-own.service.ts
userAuthentication<T>(userName: string, password: string): Observable<T> {
const url = `http://my-own.url`;
const targetData = {
'emailId': userName,
'password': password
};
return this.http.post<CommonResponse<T>>(url, targetData, httpOptions).pipe(
retry(3),
map((data: CommonResponse<T>) => {
if (data.status) {
if (!data.result[0]) {
this.showMessage('You are not authorized for login');
return null;
}
return data.result[0] as T;
}
this.showMessage(data.message);
return null;
}),
tap((userProfile: T) => {
console.log('UserLogin ');
}),
catchError(this.handleError<T>('unable to logged in')));
}
CommonResponse model
export class CommonResponse<T> {
autherizationExpires: string;
autherizationKey: string;
message: string;
result: T | T[];
status: boolean;
}
So, when you call this method like myOwnService.userAuthentication < LoginModel >(...params).subscribe(/ * your codes * /);
It will inherited to the map as well.
let me know if I am not get your question.
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);
}
}