Angular HttpClient, setting map type dynamically - javascript

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.

Related

Message (user) attribute 'tracestate' must contain a non-empty value of type 'String'

Getting Message (user) attribute 'tracestate' must contain a non-empty value of type 'String' while trying to push the data in AWS SQS.
Using below body and function to push the data
Function used
import aws, { SQS } from 'aws-sdk';
import { ServiceConfigurationOptions } from 'aws-sdk/lib/service';
export default abstract class AbstractSQSPublisherService {
protected sqs: SQS;
private options: Parameters<typeof aws.config.update>[0];
constructor(protected queueUrl: string, options: ServiceConfigurationOptions) {
this.options = options;
this.sqs = new SQS({ ...options, apiVersion: '2012-11-05' });
}
protected async publishToSqs(body: Record<string, unknown>) {
await this.sqs
.sendMessage({
QueueUrl: this.queueUrl,
MessageBody: JSON.stringify(body),
})
.promise();
}
}
Unable to find out why this issue is happening? For normal body like {notificationId: 2345} works well but sometimes it throws error shown above.

Injected function into a class constructor throws undefined

I am learning about using typescript to build API's, I have come across two issues right now. First, I have a somewhat generic PostController Class that can accept a use-case that implements the PostMethod Interface, e.g
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
That's the interface, and the generic controller looks like this.
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
let { ...incomingHttpBody } = req.body
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
const newItem = await this.postMethod({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
And then, I can use this PostController class like this
...
const postMethod = new AddUser(UsersDb).addUser
export const postUser = new PostController(postMethod)
...
The AddUser class looks like this,
export class AddUser {
constructor(public usersDb: UserDatabase) {}
async addUser(userInfo: IUser) {
console.log({...userInfo})
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
When I do a console.log of req.body, I get the incoming body, but I keep getting TypeError: Cannot read property 'postMethod' of undefined. I am unsure how to annotate the constructor function also. I do not know what I could be doing wrong, when I console.log postUser, I do see the function passed as argument logged to the console, but when I try sending requests, it fails.
Please help, thank you.
I think there are some misconceptions here.
You're defining an interface PostMethod, which itself IS NOT a method. It is an interface. So if you try to pass an instance of this, please don't just pass a FUNCTION (or method). But pass an instance of PostMethod.
It could look like this (look at the changes I made):
export interface PostMethod {
add: (req: Request, res: Response) => Promise<any> // not sure if it should be returning any
}
export class PostController implements PostMethod {
constructor(public postMethod: any) {}
async add(req: Request, res: Response) {
// cloning? if so, do it like this
let incomingHttpBody = { ...req.body }
console.log('body', incomingHttpBody)
console.log(this.postMethod)
type Source = {
ip: string
browser: string | string[] | undefined
referrer: string | string[]
}
let source = {} as Source
source.ip = req.ip
source.browser = req.headers['User-Agent']
if (req.headers.Referer) {
source.referrer = req.headers.Referer
}
// change: I made a change here, you have to call postMethod.add!
const newItem = await this.postMethod.add({ source, ...incomingHttpBody })
return apiResponse({
status: true,
statusCode: 201,
message: 'Resource created successfully',
data: [newItem]
})
}
}
// change: instantiate AddUser which in turn implements PostMethod that can be passed...
const postMethod = new AddUser(UsersDb)
export const postUser = new PostController(postMethod)
// change: implementing PostMethod!
export class AddUser implements PostMethod {
constructor(public usersDb: UserDatabase) {}
// change: new method! this is a must, because we're implementing PostMethod!
async add(req: Request, res: Response) {
// this gets called, not addUser!
}
async addUser(userInfo: IUser) {
const exists = await this.usersDb.findByEmail(userInfo.email)
if (exists) {
throw new UniqueConstraintError('Email address')
}
const user = new UserFactory(userInfo)
user.makeUser()
const { email, ...details } = user.user
const newUser = await this.usersDb.insert({ email, ...details })
const id = newUser.user._id
await createWallet(id)
// await publisher(id.toString(), 'newuser.verify')
// await consumer('verify_queue', verifyUser, '*.verify')
return newUser
}
}
You need to instantiate the class first before you can use the method...
...
const postMethod = new AddUser(UsersDb)
//then use it
postMethod.addUser()
export const postUser = new PostController(postMethod)
...
However you could make it static so you do not need to instantiate it:
static async add(req: Request, res: Response) { ....
An function is usually written like this in an interface:
export interface PostMethod {
constructor(): void,
add(req: Request, res: Response): Promise<any>
}
I fixed the issue, after much digging and reading up I realized it was a problem with this losing its context. Hence why I was getting undefined when I do something like this.
const postMethod = new PostController(usecase)
The fix was to bind this in the constructor like so,
this.add = this.add.bind(this)
that fixed the issue as when the class was instantiated it doesn't just point to the methods of the class but also the associations of the class.
The postMethod parameter which is being passed into the constructor of PostMethod is not being stored anywhere. Store it ins a private class variable and then use the private class variable.
like -
const _postMethod = postMethod;
and then while calling call
const newItem = await this._postMethod({ source, ...incomingHttpBody })

Extraction and Validation

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

Populating an Object Model in Angular 7

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

Error while subscribing from http request angular

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 ...

Categories