NestJS Http Module - javascript

I took this example from the nestJS docs (https://docs.nestjs.com/techniques/http-module#http-module), which is a minimal example of my problem:
#Injectable()
export class CatsService {
constructor(private httpService: HttpService) {}
findAll(): Observable<AxiosResponse<Cat[]>> {
return this.httpService.get('http://localhost:3000/cats');
}
}
How can I extract the actual Array of Cats from the Observable<AxiosResponse<Cat[]>>?
I tried the following but it gives me a subscriper object which I also don't know how to unwrap to get the actual data.
const cats = await this.catsService.findAll().subscribe((val) => val);

Using .toPromise() is deprecated now check this : https://rxjs.dev/deprecations/to-promise
Listing below is an example using the new approach
UPDATE Oct 2021
import { lastValueFrom } from 'rxjs';
.
.
.
const observable = await this.httpService.get('url').pipe(map((res) => res.data));
// you can use the data object now !!
const data = await lastValueFrom(observable);

#Injectable()
export class CatsService {
constructor(private httpService: HttpService) {}
findAll(): Promise<Cat[]> {
return this.httpService.get('http://localhost:3000/cats').toPromise();
}
}
const cats = await this.catsService.findAll().data
worked for me.

Try this:
findAll(): Observable<Cat[]> {
return this.httpService.get('http://localhost:3000/cats')
.pipe(map(response => response.data);
}

Related

Nest js POST Request Not Recognizing DTO method

I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.

how to add delay in http request using angular? [duplicate]

I understand that Observable.debounce() can be used to process rapid fire form input. As Http GET also returns an Observable, I wonder it it is possible to debounce rapid http requests? I tried debounceTime() but it did not appear to do anything.
public getStuff(p1, area:string, p2:string): Observable<number> {
return this.http.get(some_url)
.map(r => r.json())
.debounceTime(10000)
.catch(this.handleError);
};
The debounceTime allows to buffer events and only handle the last one after an amount of time.
It's useful in the context of inputs but it should be defined on the observable that triggers the event not on the one created for the HTTP request.
Here is a example on a control associated with an input that leverages the debounceTime operator:
#Component({
(...)
template: `
<input [ngFormControl]="ctrl"/>
`
})
export class MyComponent {
constructor() {
this.ctrl = new Control();
this.ctrl.valueChanges
.debounceTime(500)
.distinctUntilChanged()
.switchMap((value: string) => {
// Get data according to the filled value
return this.service.getData(entry);
})
.subscribe(data => {
// Update the linked list
this.list = data;
});
}
}
This article could also interest you:
https://jaxenter.com/reactive-programming-http-and-angular-2-124560.html (see section "Linking with user events")
Following the micronyks's comment, here is an additional link:
Everything is a stream: http://slides.com/robwormald/everything-is-a-stream (youtube: https://www.youtube.com/watch?v=UHI0AzD_WfY)
You have to transform from subject observable into an http observable with switchMap like this:
observableObj$: Observable<any>;
subjectObj = new Subject();
ngOnInit() {
this.observableObj$ = this.subjectObj.pipe(
debounceTime(1000),
switchMap(() => {
...
return this.http.get(some_url).map(r => r.json());
}),
);
this.observableObj$.subscribe((data) => {
// result of http get...
...
});
}
getStuff() {
this.subjectObj.next();
}
in Angular7:
import { Observable, of, timer } from 'rxjs';
import { catchError, retry, map, debounce } from 'rxjs/operators';
public getStuff(p1, area:string, p2:string): Observable<number> {
return this.http.get(some_url)
.pipe(
debounce(() => timer(10000)),
catchError(this.handleError)
);
};

Angular 2 service data can't assign to variable in constructor

In my Angular 2 application's constructor i need to assign service function returning data to a public variable and show in html view. Console log works well but html data not showing. So i'm assuming data is not assigning to the variable. Following is my code snippet.
export class ListComponent implements OnInit {
public listname ="Laravel list";
constructor(private laraveldataservice: LaravelDataServiceService) { }
public dataList = this.laraveldataservice.getUsers();
ngOnInit() {
this.laraveldataservice.getUsers();
}
}
Basically ng oninit data is loading in console log. But dataList value is not assigning.
Service code,
getUsers(){
const url = 'http://localhost/laravel_app/public/api/users';
this.http.get(url).subscribe(
res => {
const data = res.json();
console.log(data);
return data;
}
);
}
Your console.log works because it fires in your Rx subscription. Your dataList never gets populated with anything due to the async nature of the call. What I do in my code is to convert it to a promise and then await it.
Example:
async ngOninit() {
this.dataList = await this._httpClient
.get<TData>(url)
.toPromise();
}
Place it inside ngOnit and declare variable outside
public dataList : any;
ngOnInit() {
this.dataList = this.laraveldataservice.getUsers();
}
Try this snippet
Service
private defaultDataSub = new Subject<any>();
onDefalultDataLoad = this.defaultDataSub.asObservable();
getUsers(){
const url = 'http://localhost/laravel_app/public/api/users';
this.http.get(url).subscribe(
res => {
const data = res.json();
this.defaultDataSub.next(data);
}
);
}
Component
import {Subscription} from "rxjs";
export class ListComponent implements OnInit, OnDestroy{
public listname ="Laravel list";
constructor(private laraveldataservice: LaravelDataServiceService) { }
public dataList;
public subscription:Subscription;
ngOnInit() {
this.subscription = this.laraveldataservice.onDefalultDataLoad.subscribe(data => {
this.dataList = data;
});
}
ngOnDestroy(){
this.subscription.unsubscribe();
}
}
You are setting dataList property before data coming from server. May be this is the reason for your problem
What you need to do is :
getUsers(){
const url = 'http://localhost/laravel_app/public/api/users';
return this.http.get(url).pipe(map(res => res.json()))
}
And in your component.ts file :
export class ListComponent implements OnInit {
public listname ="Laravel list";
constructor(private laraveldataservice: LaravelDataServiceService) { }
public dataList ;
ngOnInit() {
this.laraveldataservice.getUsers().subscribe(res => {
this.dataList = res
}
}

Angular - get synchronously from Promise

I want to print history of products. I have an id of product in ActivatedRoute.params. In ngOnInit method I have to get all history of product and assign to variable. Then I want to map product to productHistory, because I want to have last version with history toghether. But the problem is with getting history. Method to getting history return Promise and I cannot get length of productsHistory when I use this property and I get undefined. How can I get this property after loading from service?
I want to execute method after execution getHistory().
My code:
ProductService.ts:
import { Injectable } from '#angular/core';
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
// rest imports
#Injectable()
export class ProductService {
// URL to web api
private projectsUrl = 'http://localhost:8080/products';
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http) {}
getHistory(id: number): Promise<ProductHistory[]> {
const url = `${this.projectsUrl}/projectId/${id}`;
return this.http.get(url)
.toPromise()
.then(response => response.json() as ProductHistory[])
.catch(this.handleError);
}
handleError() {
//...
// implementation is irrelevant
}
}
ProductHistoryComponent.ts:
import { Component, Input, OnInit } from '#angular/core';
import { ActivatedRoute, Params } from '#angular/router';
import { Location } from '#angular/common';
import { ProductService } from './product.service';
import { ProductHistory } from './product-history';
import { Product } from './model/product';
import 'rxjs/add/operator/switchMap';
#Component({
selector: 'product-history',
templateUrl: './product-history.component.html',
styleUrls: [ './product-history.component.css' ]
})
export class ProductHistoryComponent implements OnInit {
auditProducts: ProductHistory[] = new Array<ProductHistory[]>();
selectedProduct: ProductHistory;
constructor(
private route: ActivatedRoute,
private location: Location,
private productService: ProductService
) {}
ngOnInit(): void {
let id: number = this.route.snapshot.params['id'];
this.productService.getHistory(id)
.then(history => this.historyProducts = history);
this.productService.getProduct(id)
.then(product => {
let lastVersion: ProductHistory = this.createLastVersion(product);
this.auditProducts.push(lastVersion);
});
}
onSelect(ProductHistory: ProductHistory): void {
this.selectedProduct = ProductHistory;
this.compare(this.selectedProduct);
}
goBack(): void {
this.location.back();
}
compare(history: ProductHistory): void {
let previous: ProductHistory;
if (history.changeNumber != null && history.changeNumber > 1) {
previous = this.historyProducts[history.changeNumber - 2];
if (typeof previous != 'undefined') {
this.setPreviousDiffsFalse(previous);
if (previous.name !== history.name) {
history.nameDiff = true;
}
if (previous.price !== history.price) {
history.priceDiff = true;
}
}
}
}
createLastVersion(product: Product): ProductHistory {
let lastVersionProduct: ProductHistory = new ProductHistory();
lastVersionProduct.id = this.historyProducts.length + 1;
lastVersionProduct.name = product.name;
lastVersionProduct.price = product.price;
lastVersionProduct.changeNumber = this.historyProducts[this.historyProducts.length - 1].changeNumber + 1;
return lastVersionProduct;
}
setPreviousDiffsFalse(previous: ProductHistory): void {
previous.nameDiff = false;
previous.priceDiff = false;
}
}
You can't run it synchronously, you have to wait for each promise to return a result before you can do something with that result. The normal way to do this is to nest code inside then blocks when using promises. Alternatively you can also use async/await with the latest version of typescript and you only have to change your component code as you are already returning the Promise type from your service. This makes code easier to read (IMO) although the emitted javascript code will still use function/callback nesting (unless you are targeting es7 I believe, maybe someone will correct or confirm this).
// note the use of async and await which gives the appearance of synchronous execution
async ngOnInit() {
let id: number = this.route.snapshot.params['id'];
const history = await this.productService.getHistory(id);
this.historyProducts = history;
const product = await this.productService.getProduct(id);
let lastVersion: ProductHistory = this.createLastVersion(product);
this.auditProducts.push(lastVersion);
}
I would suggest using observables instead of promises ... but to answer your question, you just need to perform the second request after the first is received. Something like this:
ngOnInit(): void {
let id: number = this.route.snapshot.params['id'];
this.productService.getHistory(id)
.then(history => {
this.historyProducts = history);
this.productService.getProduct(id)
.then(product => {
let lastVersion: ProductHistory = this.createLastVersion(product);
this.auditProducts.push(lastVersion);
});
}
}
I just moved the second request within the then of the first request. NOTE: I did not syntax check this.

Array undefined on map and subscribe Ionic 2

I creted a service that I want to get data from a JSON file and assign to an array (countries) to use at entire application (a lots of Pages), but when I call getCountries method, the countries is undefined, what is wrong at my approach?
import { Http } from '#angular/http';
import { Injectable } from '#angular/core';
#Injectable()
export class CountryService {
private countries: any;
private isLoaded: boolean;
private url: string = 'http://localhost:8100/assets/data/countriesV2.json';
constructor(private http: Http) {
if (!this.isLoaded) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(result => {
this.countries = result;
});
this.isLoaded = true;
}
}
public getCountries() {
console.log(this.countries);
return this.countries();
}
}
Maybe changing return this.countries(); to return this.countries; may help
Also, check that your result is not empty :
.subscribe(result => {
this.countries = result;
console.log(result)
});
You should always map the data in the service, and subscribe in your component. The reason why this.countries is undefined because it IS undefined, even though you are trying to do the request in the constructor, it's not going to work. Change your service to this:
#Injectable()
export class CountryService {
private url: string = 'http://localhost:8100/assets/data/countriesV2.json';
constructor(private http: Http) { }
public getCountries() {
this.http.get(this.url)
.map(res => res.json())
}
}
And then in your components you call getCountries and subscribe to the request.
countries: any[] = [];
constructor(private countryService: CountryService) { }
ngOnInit() {
this.countryService.getCountries()
.subscribe(data => {
this.countries = data;
// here countries is not undefined, so call your "randomCountry" method here!
this.getRandomCountry();
});
}
Since this is an async operation, I suggest you use the safe navigation operator in that view, that does not try to show the property of country in case country is null. More info here. So the usage would be:
<div *ngFor="let country of countries">
{{country?.yourProperty}} // replace "yourProperty" with your actual property
</div>
Some more detailed explanations about HTTP here from the official docs
Hope this helps! :)

Categories

Resources