I'm new in Nest JS. I want to serialize custom response - javascript

I want to return this object on every response:
class Response<T> {
success: boolean
message: string
data: T
}
but built in serializer can't process it because it waits for object which is under serialization. This is my solution. I created a custom response serializer and inherit built in one and map response. It works excellent for now! Offered me better way please.
export interface PlainLiteralObject {
[key: string]: any;
}
#Injectable()
export class ResponseSerializerInterceptor extends ClassSerializerInterceptor {
public intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
const contextOptions = super.getContextOptions(context);
const options = {
...super.defaultOptions,
...contextOptions,
};
return next.handle().pipe(map((res: Response<PlainLiteralObject> | Response<Array<PlainLiteralObject>>) => {// data property holds object which is under serialization
const data = super.serialize(res.data, options);
res.data = data;
return res;
}));
}
}

Related

How to extract only certain fields from Http response object

I'm fairly new to Angular and I'm trying to get only certain values from the Http response object.
In my service, I'm doing a get request to fetch weather data for a given city like so:
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string){
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`);
}
}
Now, in the component, I'm logging out the response like so:
ngOnInit(): void {
this.cityService.getCity('lucija').subscribe(data => {
console.log(data)
});
}
The response itself it's just one object with many fields (also nested ones), which most of them I do not need.
I've also set up an interface where I would like to "save" in those certain response fields:
export interface City {
name: string;
description: string;
icon: string;
main: object;
search: string;
}
How can I do that? Cheers!
EDIT:
Based on the answer below I got 2 errors, which I resolved like so:
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res: any) => <City>{
name: res.name,
description: res.weather[0].description,
icon: `http://openweathermap.org/img/w/${res.weather[0].icon}.png`,
main: res.main,
search: name
}));
One solution could be to map the object in your service. Then your service will return the City Object.
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res) => { return { name: res.cityName }; }); // only added name as example. In the final code map all the values to the correct City field.
}
}
If you do not want your service to always return the City object you can do this mapping in your component.

Typescript : Create custom type axios instance

I want to create a complex type name api with an axios instance so that I can use the basic instance like this:
api.get("url")...
But also to be able to have new dynamic bodies such as:
api.myApi.get("url")
And finally, be able to read the list of instances like this:
api.list
Must be return
myApi
When I want to extend AxiosInstance or inject properties to the main instance, I always get a type error.
Any idea?
My test :
type apiInstances = Record<string, AxiosInstance>
type apiList = Record<'list', string[]>
let api: apiInstances | apiList
const instance = (baseURL: string) =>
axios.create({
baseURL
})
const instances = { instance('myApi') }
api = { ...instances, ...instance(''), list }
Error when I write api.myApi.get("...")
Property 'myApi' does not exist on type 'apiInstances | apiList'
I think you're on the good way.
The best thing you can do is to abstract axios client as you would do with another http client and hide axios implementation.
For this, you can make a class for instead
export class HttpClient extends HttpMethods {
_http: AxiosInstance;
constructor() {
super();
this._http = axios.create({
...this._options,
validateStatus: status => status >= 200 && status < 400,
});
}
setAdditionnalHeaders(headers: object, override?: boolean): void {
this._options = _.merge({}, override ? {} : this._options, { headers });
}
public async get<T>(path: string, params?: any, headers?: object): Promise<Result<T>> {
if (headers) {
this.setAdditionnalHeaders(headers, true);
}
const result = await this._http({
method: 'GET',
url: path,
params,
headers: this._options,
...this.hydrateConfig(this._config),
});
return result;
}
}
export abstract class HttpMethods {
public _http: any;
protected _options: object;
public abstract get<T>(path: string, params?: any, headers?: object): Promise<Result<T>>;
}
And then play with chainable functions where you will inject your class which hide that axios is use in this.
export function httpBuilder<I extends HttpMethods>(client: I): IHttpBuilder {
return {
...httpRequestMethods(client),
};
}
function httpRequestMethods(instance: HttpMethods): BuilderMethod {
const { config } = instance;
return {
get<T>(path: string, params?: any, headers?: object): ChainableHttp & HttpExecutableCommand<T> {
return {
...executableCommand<T>(path, instance, 'GET', requests, null, params, headers),
};
},
}
function executableCommand<T>(
path: string,
instance: HttpMethods,
commandType: CommandType,
requests: RequestType[],
data?: any,
params?: any,
headers?: object,
): HttpExecutableCommand<T> {
return {
async execute(): Promise<Result<T>> {
const result = await getResolvedResponse<T>(path, instance, commandType, data, params, headers);
return result;
},
};
}
async function getResolvedResponse<T>(
path: string,
instance: HttpMethods,
commandType: CommandType,
data?: any,
params?: any,
headers?: object,
): Promise<Result<T>> {
let result: Result<T>;
if (commandType === 'GET') {
result = await instance.get<T>(path, params, headers);
}
return result;
}
This is an example for help if you want to perform extra feature on your http client whether is axios, fetch or whatever you want :-)
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype
type ApiKeys = "myApi" | "yourApi";
type Api = Partial<Record<ApiKeys, AxiosInstance>>;
I have found solution :
type Api = {
[key: string]: AxiosInstance
list: never
}
let api: Api
That work like a charm

Subclass override return type of the base class

I am trying to override a method that is defined in the super class, however I am getting an error that says:
Property 'get' in type 'Model' is not assignable to the same property in base type 'DB'.
What can I do to override this? It works with the generated JavaScript, and I would like to not have to make DB.get() return Promise<any> which also works.
type NonAbstractModel<T extends Model> = (new () => T) & typeof Model
type RowDataPacket = {}
export class Collection<T> {
protected _items: T[] = []
public add(item:T){ this._items.push(item) }
}
export class DB {
public async get(): Promise<RowDataPacket[]> {
// Query a database, this is just dummy data
return [{}, {}, {}]
}
}
export abstract class Model extends DB {
public static convert<T extends Model>(model: NonAbstractModel<T>, data: RowDataPacket[]): Collection<T> {
let c = new Collection<T>()
for(let i of data) {
let m = new model()
c.add(m)
}
return c
}
public async get<T extends Model>():Promise<Collection<T>>{
return Model.convert(<NonAbstractModel<T>>this.constructor, await super.get())
}
}
How the two work:
DB
When the DB class is used, we have no idea the type of Model because a model is based on a database table, but we do know that the results will be an array of database items.
Model
When a Model is used we do know what the type of Model is, so we have the ability to convert the packet data into a collection of models (One Model class for each row item).
TypeScript Playground

Type 'Object' is not assignable to type 'any[]' in Angular 4.x app

I get the following error in my typescript code but unsure why any ideas - it is the this.items variable?
Type 'Object' is not assignable to type 'any[]'. The 'Object' type
is assignable to very few other types. Did you mean to use the 'any'
type instead?
export class GifpickerComponent implements OnInit {
public searchTerm : FormControl = new FormControl();
public items = [];
constructor(private gifpickerService: GifpickerService){
this.searchTerm.valueChanges
.subscribe(data => {
this.gifpickerService.search(data).subscribe(response =>{
console.log('subscribe', response);
this.items = response; /* line picked up in error */
})
})
}
/* service as requested */
import { Injectable } from '#angular/core';
import { ApiService } from "../../services/api.service";
import { HttpClient, HttpParams, HttpResponse} from '#angular/common/http';
#Injectable()
export class GifpickerService {
constructor(
private http: HttpClient,
private apiService: ApiService
) {}
search(term){
console.log('term', term);
let url = this.apiService.getApiUrl('gif/search');
let params = new HttpParams()
.set('q', String(term))
.set('results_only', '1');
return this.http.get(url, {params: params})
.map(response => {
return response;
});
}
}
do this change
public items = {}
then thing is, you are making items as array of any type and apparently response is of object type
This is an array of any items any[].
public items = []
Response is probably an object.
You are setting items to be equal to an object.
You should either make items of type any OR
You should push the response object to the items array like this:
this.items.push(response)
OR
this.items = [...this.items, response]
Depending on what you want.

Angular 2 Array printed on console but can't print object property on screen

I have the following method in a service I've created:
getPost(nid: string): Observable<Post[]>{
let url = "http://test.co.uk/api/v1/basic/" + nid;
return this.http.get(url, {headers: this.headers}).map(res => res.json() as Post).catch(err => {
return Observable.throw(err);
});
}
And this is the class of my component:
export class PostDetailComponent implements OnInit {
posts: Post[] = [];
post: Post = new Post();
constructor(
private route: ActivatedRoute,
private postService: PostService
) { }
ngOnInit() {
this.route.params.switchMap((params: Params) => {
let nid = params ['nid'];
return this.postService.getPost(nid); }).subscribe(res => {
console.log(res)
this.post = res as Post;
}, err =>{
console.log(err);
});
}
}
The JSON feed looks like this(yes one object in the array):
[
{
"nid":"3",
"title":"When Unity meets Vuforia",
"body":"<p>Unless you have been living under a rock in the past 7 - ...",
"uid":"admin",
"path":"\/node\/3",
"field_article_image":"http:\/\/test.co.uk\/sites\/default\/files\/when-unity-meets-vuforia_0.jpg?itok=BGYaotay"
}
]
So in my template, if I print {{post}} I get [object Object] on the screen.
If I print {{post | json}} I get the row JSON feed.
And finally, if I print {{post.title}} or {{post?.title}} I don't get anything.
I also have a class Post that is looking like this:
export class Post{
constructor(
public nid?: string,
public title?: string,
public body?: string
public image?: string
){
}
}
Any hints?
You are assigning an array into what should be a single object. Copy the first element of the array into the post variable
this.post = res[0] as Post
Side note: It's incorrect to assign a raw object to a class instance. In this case, your this.post.constructor won't exist and this.post instanceof Post == false.
You could do Object.assign(this.post, res[0]) but you may need to clear existing properties if not all properties are always present.
I prefer to define object shapes as interfaces instead, then you would not have that problem because all the interface information is removed at runtime, whereas a class does emit some code instead of just doing static type checks at compilation time

Categories

Resources