I'm very new to Angular2!. We have a search method which create an async observable and executing it. When provide a valid input, it works good. But an invalid input is causing an Infinite loop. Please let me know if you see the issue.
Please see the code below.
const ngPromiseToObservable = <T>(p: ng.IHttpPromise<ng.IHttpPromiseCallbackArg<T>>): Observable<ng.IHttpPromiseCallbackArg<T>> => {
const o = new Subject();
p.catch((e) => o.error(e));
p.then((v) => o.next(v));
return o;
};
export class HttpObservable
{
public static $inject = [
"$rootScope",
"$http",
"HttpErrors",
];
constructor(
private $rootScope: ng.IScope,
private $http: ng.IHttpService,
private httpErrors: Subject<Object>
) {}
handleHttpResponseErrors<T>(o: Observable<T>): Observable<T>
{
return o.do(
_.noop,
(e: HttpResponseError) => this.httpErrors.next(e),
_.noop
);
}
applyAsyncOnAction<T>(o: Observable<T>): Observable<T>
{
let applyAsync: () => void = () => {
return this.$rootScope.$applyAsync();
};
return o.do(applyAsync, applyAsync, applyAsync);
}
// tslint:disable-next-line:no-any
post<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.post(url, data, config)).map((x) => x.data)));
}
// tslint:disable-next-line:no-any
put<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.put(url, data, config)).map((x) => x.data)));
}
get<T>(path: string): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.get(path)).map((x) => x.data)));
}
Related
I have following function in service to retrieve data:
public getData(): Observable<dataObj> {
const endpointURL = 'xyz';
return response = this.responseHandler
.makeRequest<dataObj>('get', endpointURL)
.pipe(
startWithTap(() => this._requestsInProgress.next(++this.requestsInProgress)),
finalize(() => this._requestsInProgress.next(--this.requestsInProgress))
)
}
in different place I have something like this and I call that function:
export class SearchAndFilterService {
private _data: BehaviorSubject<dataObj> = new BehaviorSubject(null);
private _searchTerm: BehaviorSubject<string> = new BehaviorSubject(null);
private _filterTerms: BehaviorSubject<string[]> = new BehaviorSubject(null);
public readonly data$: Observable<dataObj>;
constructor(
private service: Service
) {
this.data$ = combineLatest([
this._data.asObservable(),
this._searchTerm.asObservable(),
this._filterTerms.asObservable()
]).pipe(
map((tuple) => {
const [data, searchTerm, filterTerms] = tuple;
if (data) {
return {
...data,
array: this.searchAndFilterData(
data?.array,
searchTerm,
filterTerms
)
};
}
return data;
}),
);
}
public updateDonorAppointments() {
this.service.getData().pipe(tap(
(data) => this._data.next(data)
))
}
I thought that pipe should create a subscription to returned observable and this will work. However instead of pipe I must use .subscribe to make it work. Like this:
public updateDonorAppointments() {
this.service.getData().subscribe(
(data) => this._data.next(data)
)
}
Am I missing something? Or is this approach totally wrong?
Thanks
I'm trying to create a mutation which will accept list of products. But doing so GraphQL is throwing error for createMultipleProducts method. Not sure what is the mistake here.
import { Inject } from "#nestjs/common";
import { Args, Mutation, Query, Resolver } from "#nestjs/graphql";
import { ClientProxy } from "#nestjs/microservices";
import { ProductRequest } from "src/types/ms-product/product.request.type";
import { ProductResponse } from "src/types/ms-product/product.response.type";
#Resolver(of => ProductResponse)
export class ProductResolver {
constructor(
#Inject('SERVICE__PRODUCT') private readonly clientServiceProduct: ClientProxy
) {}
#Mutation(returns => ProductResponse)
async createProduct(#Args('data') product: ProductRequest): Promise<ProductResponse> {
const PATTERN = {cmd: 'ms-product-create'};
const PAYLOAD = product;
return this.clientServiceProduct.send(PATTERN, PAYLOAD)
.toPromise()
.then((response: ProductResponse) => {
return response;
})
.catch((error) => {
return error;
})
}
#Mutation(returns => [ProductResponse])
async createMultipleProducts(#Args('data') products: [ProductRequest]): Promise<Array<ProductResponse>> {
try {
const PROMISES = products.map(async (product: ProductRequest) => {
const PATTERN = {cmd: 'ms-product-create'};
const PAYLOAD = product;
return await this.clientServiceProduct.send(PATTERN, PAYLOAD).toPromise();
});
return await Promise.all(PROMISES);
} catch (error) {
throw new Error(error);
}
}
#Query(returns => ProductResponse)
async readProduct(#Args('data') id: string) {
return {}
}
}
I'm getting this error:
UnhandledPromiseRejectionWarning: Error: Undefined type error. Make sure you are providing an explicit type for the "createMultipleProducts" (parameter at index [0]) of the "ProductResolver" class.
There is a need to inform GraphQL the type of the arguments explicitly if it is a complex object array.
#Mutation(returns => [ProductResponse])
async createMultipleProducts(#Args({ name: 'data', type: () => [ProductRequest] }) products: ProductRequest[]): Promise<Array<ProductResponse>> {
...
}
New format 2022
#Query(returns => ProductResponse)
async readProduct(#Args('data', () => String ) id: string) {
return {}
}
Here is my code:
#Injectable()
export class TraitementDetailEffects {
ingoing_loadDetail: { traitementID: number, obs: Promise<any> };
#Effect()
loadTraitementDetail$: Observable<Action> = this.actions$.pipe(
ofType(ETraitementDetailActions.loadTraitementDetail),
map((action: LoadTraitementDetail) => action.payload),
switchMap((traitementID) => {
if (this.ingoing_loadDetail && this.ingoing_loadDetail.traitementID === traitementID) {
return this.ingoing_loadDetail.obs;
}
const obs = this.traitementsService.loadDetail(traitementID);
this.ingoing_loadDetail = {traitementID: traitementID, obs: obs};
return obs;
}),
map(result => {
this.ingoing_loadDetail = null;
//here I don't have access to traitementID :'(
return new LoadTraitementDetailSuccess(traitementID, result);
})
);
constructor(
private actions$: Actions,
private traitementsService: TraitementsService
) {
}
}
I'm trying to pass the variable or value traitementID to the last map.
I tried to avoid the last map with an async await but then I get a weird errors "Effect dispatched an invalid action" and "Actions must have a type property" (FYI all my actions have a type property).
Try to bake this id into observable's resolve, like:
switchMap((traitementID) => {
return this.traitementsService.loadDetail(traitementID).pipe(
map(detail => ({detail,traitementID}))
);
}),
map(({detail,traitementID}) => {
...
})
I'm pretty new to RxJS and am wondering if I am doing this right... in the ngOnInit() function below, I get a client object, then pipe it...
Is there a better way to do the repeat switchMap/map operations below?
My code works... but I am wondering if there is a more elegant approach that I should be adopting...
public client: Client;
public contract: Contract;
public alreadyPendingContract: boolean;
public alreadyActiveContract: boolean;
public minimumStartDate: Date;
public minimumEndDate: Date;
public rolloverExclusionDate: Date;
public startDateFilter;
ngOnInit() {
this.clientService.getClient$().pipe(
filter(client => client != null),
map(client => this.client = client),
pluck('client_id'),
map((client_id: string) => {
this.clientContractForm.get('client_id').setValue(client_id);
return client_id;
}),
switchMap((client_id: string) => {
return this.contractAddService.getAlreadyPendingContract$(client_id);
}),
map(alreadyPendingContract => {
this.alreadyPendingContract = alreadyPendingContract;
return this.client.client_id;
}),
switchMap((client_id: string) => {
return this.contractAddService.getAlreadyActiveContract$(client_id);
}),
map(alreadyActiveContract => {
this.alreadyActiveContract = alreadyActiveContract;
}),
switchMap(() => {
return this.contractAddService.getMinimumStartDate$(this.client.client_id);
}),
map((minimumStartDate: IMinimumStartDate) => {
this.minimumStartDate = minimumStartDate.minimumStartDate;
this.rolloverExclusionDate = minimumStartDate.rolloverExclusionDate;
this.startDateFilter = (m: Moment): boolean => {
// Filters out the rollover exclusion day from being an available start date.
return !moment.utc(m).isSame(moment.utc(this.rolloverExclusionDate), 'day');
}
})
).subscribe();
}
I am not sure this is more elegant, but it is an alternative way
ngOnInit() {
this.clientService.getClient$().pipe(
filter(client => client != null),
map(client => {
this.client = client;
this.clientContractForm.get('client_id').setValue(client_id);
return client.client_id;
},
switchMap(client_id => this.doStuffWithClientId(client_id)),
map((minimumStartDate: IMinimumStartDate) => {
this.minimumStartDate = minimumStartDate.minimumStartDate;
this.rolloverExclusionDate = minimumStartDate.rolloverExclusionDate;
this.startDateFilter = (m: Moment): boolean => {
// Filters out the rollover exclusion day from being an available start date.
return !moment.utc(m).isSame(moment.utc(this.rolloverExclusionDate), 'day');
}
})
).subscribe();
}
doStuffWithClientId(clientId: string) {
return this.contractAddService.getAlreadyPendingContract$(client_id).pipe(
tap(alreadyPendingContract => this.alreadyPendingContract = alreadyPendingContract),
switchMap(() => this.contractAddService.getAlreadyActiveContract$(clientId)),
tap(alreadyActiveContract => this.alreadyActiveContract = alreadyActiveContract),
switchMap(() => this.contractAddService.getMinimumStartDate$(clientId)),
)
}
I have not tested the code, so there may well be syntax mistakes. The basic idea though is to isolate all the things which depend on client_id into one function which receives the clientId as input and therefore makes it visible throughout the entire function.
Here a quite complex sample:
Main:
this.runInstructionAndGetResult().subscribe({
next: val => console.log(`NEXT VALUE: ${val}`),
error: val => console.log(`ERROR VALUE: ${val}`),
complete: val => console.log(`COMPLETE`)
});
Observables:
public runInstructionAndGetResult(): Observable<string> {
return this.runAnInstruction()
.flatMap((data) => {
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.flatMap((data) => {
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
return data;
});
});
}
public runAnInstruction(): Observable<string> {
return Observable.of("StartRun");
}
public getInstructionResult(): Observable<string> {
return Observable.of("FinalResult");
}
public getInstructionExecutionStatusInPolling(): Observable<string> {
return Observable.interval(1000)
.concatMap(data => {
return this.getInstructionExecutionStatus();
});
}
public getInstructionExecutionStatus(): Observable<string> {
return Observable.of("Polling", "Terminate");
}
Here plunk:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Main problem is that i just would like to be notify about "evolution" of inner stream outside.
Right now we have "next" event on main only when all inner flatMap are completed.
How to get notify? How can i emit explicit values to main stream for example during polling?
Thanks.
I found a solution to share.
Here plunker updated:
https://plnkr.co/edit/c1cahMtVARQnLgnHWlEe?p=preview
Basically i create a simple observable using : https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/create.md
then i call programmatically next method and complete finally:
public runInstructionAndGetResult(): Observable<string> {
return Observable.create((ops)=> {
ops.next(1);
this.runAnInstruction()
.concatMap((data) => {
ops.next(2);
console.info("flatMap of runAnInstruction:", data);
return this.getInstructionExecutionStatusInPolling()
.filter(data => data != "Polling")
.take(1)
.concatMap((data) => {
ops.next(3);
console.info("flatMap of getInstructionExecutionStatusInPolling:", data);
return this.getInstructionResult();
}).map((data) => {
console.info("Map of getInstructionResult:", data);
ops.next(4);
ops.complete();
return data;
});
}).subscribe();
});
}