Angular getting TypeError: You provided an invalid object where a stream was expected - rxjs

I am using Angular 11 and having a get method in a service as below.
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
get<T>(url: string, params: HttpParams = null): Observable<T> {
const httpOptions: any = {
headers: this.getHeaders(),
params: params
};
return this.httpClient.get(this.apiPath + url, httpOptions)
.pipe(
catchError((httpResponse: HttpResponse<T>) => {
return this.getError(httpResponse);
}));
}
private getError(res: HttpResponse<any>): Observable<any> {
return throwError(() => new Error(`Server error: ${res.statusText} (${res.status})`));
}
The above code keep throwing eerror inside getError method saying
TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable
Can anyone please let me know what am i doing wrong?
Thanks

Related

Return EventEmitter as Observable in Nest.js

EventEmitter in Nestjs is wrapper around EventEmitter2 module. I whant that Server-Sent Events return Observable with EE.
import { Controller, Post, Body, Sse } from '#nestjs/common';
import { fromEvent } from 'rxjs';
import { EventEmitter2 } from '#nestjs/event-emitter';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
#Controller('orders')
export class OrdersController {
constructor(private ordersService: OrdersService,
private eventEmitter2: EventEmitter2) {}
#Post()
createOrder(#Body() createOrderDto: CreateOrderDto) {
// save `Order` in Mongo
const newOrder = this.ordersService.save(createOrderDto);
// emit event with new order
this.eventEmitter2.emit('order.created', newOrder);
return newOrder;
}
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
}
But after subscribtion to this source from browser i've getting only errors
this.eventSource = new EventSource('http://localhost:3000/api/v1/orders/newOrders');
this.eventSource.addEventListener('open', (o) => {
console.log("The connection has been established.");
});
this.eventSource.addEventListener('error', (e) => {
console.log("Some erorro has happened");
console.log(e);
});
this.eventSource.addEventListener('message', (m) => {
const newOder = JSON.parse(m.data);
console.log(newOder);
});
It's quite likely that you forgot to format the event in the right way.
For SSE to work internally, each chunk needs to be a string of such format: data: <your_message>\n\n - whitespaces do matter here. See MDN reference.
With Nest.js, you don't need to create such message manually - you just need to return a JSON in the right structure.
So in your example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created');
}
would have to be adjusted to, for example:
#Sse('newOrders')
listenToTheNewOrders() {
// return Observable from EventEmitter2
return fromEvent(this.eventEmitter2, 'order.created')
.pipe(map((_) => ({ data: { newOrder } })));
}
the structure { data: { newOrder } } is key here. This will be later translated by Nest.js to earlier mentioned data: ${newOrder}\n\n

How to upload file using nestjs-graphql-fastify server and how to test such feature?

I struggle to upload .csv file to nestjs-graphql-fastify server. Tried following code:
#Mutation(() => Boolean)
async createUsers(
#Args({ name: 'file', type: () => GraphQLUpload })
{ createReadStream, filename }: FileUpload,
): Promise<boolean> {
try {
// backend logic . . .
} catch {
return false;
}
return true;
}
but all I get when testing with postman is this response:
{
"statusCode": 415,
"code": "FST_ERR_CTP_INVALID_MEDIA_TYPE",
"error": "Unsupported Media Type",
"message": "Unsupported Media Type: multipart/form-data; boundary=--------------------------511769018912715357993837"
}
Developing with code-first approach.
Update: Tried to use fastify-multipart but issue still remains. What has changed is response in postman:
POST body missing, invalid Content-Type, or JSON object has no keys.
Found some answer's on Nestjs discord channel.
You had to do following changes:
main.ts
async function bootstrap() {
const adapter = new FastifyAdapter();
const fastify = adapter.getInstance();
fastify.addContentTypeParser('multipart', (request, done) => {
request.isMultipart = true;
done();
});
fastify.addHook('preValidation', async function (request: any, reply) {
if (!request.raw.isMultipart) {
return;
}
request.body = await processRequest(request.raw, reply.raw);
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
adapter,
);
await app.listen(apiServerPort, apiServerHost);
}
bootstrap();
upload.scalar.ts
import { Scalar } from '#nestjs/graphql';
import { GraphQLUpload } from 'graphql-upload';
#Scalar('Upload')
export class UploadGraphQLScalar {
protected parseValue(value) {
return GraphQLUpload.parseValue(value);
}
protected serialize(value) {
return GraphQLUpload.serialize(value);
}
protected parseLiteral(ast) {
return GraphQLUpload.parseLiteral(ast, ast.value);
}
}
users.resolver.ts
#Mutation(() => CreateUsersOutput, {name: 'createUsers'})
async createUsers(
#Args('input', new ValidationPipe()) input: CreateUsersInput,
#ReqUser() reqUser: RequestUser,
): Promise<CreateUsersOutput> {
return this.usersService.createUsers(input, reqUser);
}
create-shared.input.ts
#InputType()
export class DataObject {
#Field(() => UploadGraphQLScalar)
#Exclude()
public readonly csv?: Promise<FileUpload>;
}
#InputType()
#ArgsType()
export class CreateUsersInput {
#Field(() => DataObject)
public readonly data: DataObject;
}
Also, I want to mention you should not use global validation pipes (in my case they made files unreadable)
// app.useGlobalPipes(new ValidationPipe({ transform: true }));
You could use graphql-python/gql to try to upload a file:
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
transport = AIOHTTPTransport(url='YOUR_URL')
client = Client(transport=transport)
query = gql('''
mutation($file: Upload!) {
createUsers(file: $file)
}
''')
with open("YOUR_FILE_PATH", "rb") as f:
params = {"file": f}
result = client.execute(
query, variable_values=params, upload_files=True
)
print(result)
If you activate logging, you can see some message exchanged between the client and the backend.

Retrieve data from RxJS observable in HttpModule

I am failing to understand how to map the data properties out of HttpService in my NestJS application. To my understanding, this Observable just wraps axios. Here's some example code:
interface Todo {
task: string,
completed: false
}
import {
Injectable,
HttpService,
Logger,
NotFoundException,
} from '#nestjs/common'
import { map } from 'rxjs/operators
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
resp in this case seems to be of type Observable. How do I retrieve just the data properties I want using map on this request to return my Todo interface?
Nest by default will subscribe to the observable for you it your return the Observable from your service. As this can be the case you can do something like
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Observable<Todo> {
return this.http.get(todoUrl).pipe(
map(resp => resp.data),
);
}
}
And so long as you have a controller class calling this.todoSerivce.getTodos(todoUrl) and returning it, the response will be sent out.
However, if you want to instead make it a promise as you are more accustomed to them, you can tack on a .toPromise() method to the observable chain and now it it awaitable (though it will be slower because it has to wait for the observable to emit its complete event).
Example with .toPromise():
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await this.http.get(todoUrl).pipe(
map(resp => resp.data),
).toPromise();
return myTodo;
}
}
Edit 1/20/22
In RxJS#^7, toPromise() is deprecated and will be removed in v8. Instead, you can use lastValueFrom to wrap the entire observable
#Injectable()
export class TodoService {
constructor(private readonly http: HttpService) {}
getTodos(todoUrl: string): Todo {
const myTodo = await lastValueFrom(this.http.get(todoUrl).pipe(
map(resp => resp.data),
));
return myTodo;
}
}
Looking at your code:
import { map } from 'rxjs/operators'
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp
}
getTodo returns an Observable, not the response. So your return value should be Observable<Todo>.
The code should look more like this:
getTodo(): Observable<Todo> {
return this.http.get<Todo>('https://example.com/todo_json')
.pipe(
map(response => response.data),
catchError(this.handleError)
);
}
EDIT: You can't just return the data from this method because it is asynchronous. It does not have the data yet. The method returns an Observable ... which is basically a contract saying that it will (at some later time) return the data for you.
Async functions need to return a promise, you can call toPromise on an observable to return a promise.
async getTodo(todoUrl: string): Todo {
const resp = this.httpService
.get('https://example.com/todo_json')
.pipe(map(response => response.data)) // map task/completed properties?
return resp.toPromise();
}
async getTodo(todoUrl: string): Todo {
const resp = await this.httpService
.get('https://example.com/todo_json')
.toPromise();
return resp.data;
}

How can get return value in pipe?

I have create(newItem) method which creates item on database and returns id (Observable) and getDetails(id) which returns Observable
import { mergeMap as _observableMergeMap, catchError as _observableCatch } from 'rxjs/operators';
import { Observable, throwError as _observableThrow, of as _observableOf } from 'rxjs';
create(input: Input | null | undefined): Observable<number> {
let url_ = this.baseUrl + "/api/services/app/create";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json"
})
};
return this.http.request("post", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processCreateAsync(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processCreateAsync(<any>response_);
} catch (e) {
return <Observable<number>><any>_observableThrow(e);
}
} else
return <Observable<number>><any>_observableThrow(response_);
}));
}
I am trying to call create method and get id from create method and get details with this id.
this._dataService.create(newItem)
.pipe(
switchMap( id => {
return this._dataService.getDetails(id);
})
)
.subscribe(result => {
// Do stuff with result
},
error => {
console.log(error);
}));
But I get an error which is "id is not defined" in switchMap
How can I get return value from create method and pass to getDetails method?
In your code example, the variable result in the subscribe should hold the value of id.
I downgraded rxjs to 6.2.0 and #types/node to 10.1.4 and it works now.

Using RxJS Observable.from with an object that implements the observable interface

Based on the docs for RxJS's Observable.from(), it sounds like you should be able to pass it an object that implements the observable interface. However, the following
const observable = {
subscribe(observer) {
const subscription = someAsyncProcess(res => observer.next(res));
return {
unsubscribe() {
subscription.unsubscribe();
}
}
}
};
Rx.Observable.from(observable)
.subscribe({
next(res) {
console.log(res);
}
});
throws the error
Uncaught TypeError: object is not observable
Is my observable implementation incorrect? Or am I misunderstanding from?
Note: this is more of an academic question about the Observable interface--I realize Observable.create() would work in the above situation.
You can "trick" RxJS into thinking that the object you're passing it is a real Observable by implementing a "symbol function" (I don't know what is the proper name for this). However, you probably never need to do this in practise and it's better to use Observable.create.
const Rx = require('rxjs/Rx');
const Symbol_observable = Rx.Symbol.observable;
const Observable = Rx.Observable;
const observable = {
[Symbol_observable]: function() {
return this;
},
subscribe: function(observer) {
// const subscription = someAsyncProcess(res => observer.next(res));
observer.next(42);
return {
unsubscribe() {
subscription.unsubscribe();
}
}
}
};
Observable.from(observable)
.subscribe({
next(res) {
console.log('Next:', res);
}
});
This prints:
Next: 42
You can use Observable.from if it is an array of events or Observable.of if it is a simple object. It doesn't have to be implementing any interface. The code below is printing a in the console.
Rx.Observable.from("a").subscribe(data=> console.log(data));

Resources