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
Related
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.
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)));
}
Having a bit of trouble working with the Subject exposed by Rx.Observable.webSocket. While the WebSocket does become reconnected after complete, subsequent subscriptions to the Subject are immediately completed as well, instead of pushing the next messages that come over the socket.
I think I'm missing something fundamental about how this is supposed to work.
Here's a requirebin/paste that I hope illustrates a bit better what I mean, and the behavior I was expecting. Thinking it'll be something super simple I overlooked.
Requirebin
var Rx = require('rxjs')
var subject = Rx.Observable.webSocket('wss://echo.websocket.org')
subject.next(JSON.stringify('one'))
subject.subscribe(
function (msg) {
console.log('a', msg)
},
null,
function () {
console.log('a complete')
}
)
setTimeout(function () {
subject.complete()
}, 1000)
setTimeout(function () {
subject.next(JSON.stringify('two'))
}, 3000)
setTimeout(function () {
subject.next(JSON.stringify('three'))
subject.subscribe(
function (msg) {
// Was hoping to get 'two' and 'three'
console.log('b', msg)
},
null,
function () {
// Instead, we immediately get here.
console.log('b complete')
}
)
}, 5000)
Another neat solution would be to use a wrapper over WebSocketSubject.
class RxWebsocketSubject<T> extends Subject<T> {
private reconnectionObservable: Observable<number>;
private wsSubjectConfig: WebSocketSubjectConfig;
private socket: WebSocketSubject<any>;
private connectionObserver: Observer<boolean>;
public connectionStatus: Observable<boolean>;
defaultResultSelector = (e: MessageEvent) => {
return JSON.parse(e.data);
}
defaultSerializer = (data: any): string => {
return JSON.stringify(data);
}
constructor(
private url: string,
private reconnectInterval: number = 5000,
private reconnectAttempts: number = 10,
private resultSelector?: (e: MessageEvent) => any,
private serializer?: (data: any) => string,
) {
super();
this.connectionStatus = new Observable((observer) => {
this.connectionObserver = observer;
}).share().distinctUntilChanged();
if (!resultSelector) {
this.resultSelector = this.defaultResultSelector;
}
if (!this.serializer) {
this.serializer = this.defaultSerializer;
}
this.wsSubjectConfig = {
url: url,
closeObserver: {
next: (e: CloseEvent) => {
this.socket = null;
this.connectionObserver.next(false);
}
},
openObserver: {
next: (e: Event) => {
this.connectionObserver.next(true);
}
}
};
this.connect();
this.connectionStatus.subscribe((isConnected) => {
if (!this.reconnectionObservable && typeof(isConnected) == "boolean" && !isConnected) {
this.reconnect();
}
});
}
connect(): void {
this.socket = new WebSocketSubject(this.wsSubjectConfig);
this.socket.subscribe(
(m) => {
this.next(m);
},
(error: Event) => {
if (!this.socket) {
this.reconnect();
}
});
}
reconnect(): void {
this.reconnectionObservable = Observable.interval(this.reconnectInterval)
.takeWhile((v, index) => {
return index < this.reconnectAttempts && !this.socket
});
this.reconnectionObservable.subscribe(
() => {
this.connect();
},
null,
() => {
this.reconnectionObservable = null;
if (!this.socket) {
this.complete();
this.connectionObserver.complete();
}
});
}
send(data: any): void {
this.socket.next(this.serializer(data));
}
}
for more information refer to the following article and source code:
Auto WebSocket reconnection with RxJS
GitHub - Full working rxjs websocket example
I ended up not using Rx.Observable.webSocket, instead opting for observable-socket and a bit of code to make reconnections once sockets are closed:
requirebin
const observableSocket = require('observable-socket')
const Rx = require('rxjs')
const EventEmitter = require('events')
function makeObservableLoop (socketEmitter, send, receive) {
socketEmitter.once('open', function onSocketEmit (wSocket) {
const oSocket = observableSocket(wSocket)
const sendSubscription = send.subscribe(msg => oSocket.next(msg))
oSocket.subscribe(
function onNext (msg) {
receive.next(msg)
},
function onError (err) {
error(err)
sendSubscription.unsubscribe()
makeObservableLoop(socketEmitter, send, receive)
},
function onComplete () {
sendSubscription.unsubscribe()
makeObservableLoop(socketEmitter, send, receive)
}
)
})
}
function makeSocketLoop (emitter) {
const websocket = new WebSocket('wss://echo.websocket.org')
function onOpen () {
emitter.emit('open', websocket)
setTimeout(function () {
websocket.close()
}, 5000)
}
function onClose () {
makeSocketLoop(emitter)
}
websocket.onopen = onOpen
websocket.onclose = onClose
}
function init (socketEmitter) {
const _send = new Rx.Subject()
const _receive = new Rx.Subject()
makeObservableLoop(socketEmitter, _send, _receive)
const send = msg => _send.next(JSON.stringify(msg))
const receive = _receive.asObservable()
return {
send: send,
read: receive,
}
}
const emitter = new EventEmitter()
makeSocketLoop(emitter)
const theSubjectz = init(emitter)
setInterval(function () {
theSubjectz.send('echo, you there?')
}, 1000)
theSubjectz.read.subscribe(function (el) {
console.log(el)
})
I'm going to use Angular2 to receive websocket incoming messages and update a webpage based on those received messages. Right now, I'm using a dummy echo websocket service and will replace it.
From my understanding, the function which receive websocket messages has to return an observable that is subscribed by a handler who will update the webpage. But I can't figure out how to return an observable.
Code snippet is attached below. The MonitorService creates a websocket connection and return an observable containing the received messages.
#Injectable()
export class MonitorService {
private actionUrl: string;
private headers: Headers;
private websocket: any;
private receivedMsg: any;
constructor(private http: Http, private configuration: AppConfiguration) {
this.actionUrl = configuration.BaseUrl + 'monitor/';
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
this.headers.append('Accept', 'application/json');
}
public GetInstanceStatus = (): Observable<Response> => {
this.websocket = new WebSocket("ws://echo.websocket.org/"); //dummy echo websocket service
this.websocket.onopen = (evt) => {
this.websocket.send("Hello World");
};
this.websocket.onmessage = (evt) => {
this.receivedMsg = evt;
};
return new Observable(this.receivedMsg).share();
}
}
Below is another component which subscribes to the observable returned from above and updates webpages correspondingly.
export class InstanceListComponent {
private instanceStatus: boolean
private instanceName: string
private instanceIcon: string
constructor(private monitor: MonitorService) {
this.monitor.GetInstanceStatus().subscribe((result) => {
this.setInstanceProperties(result);
});
}
setInstanceProperties(res:any) {
this.instanceName = res.Instance.toUpperCase();
this.instanceStatus = res.Status;
if (res.Status == true)
{
this.instanceIcon = "images/icon/healthy.svg#Layer_1";
} else {
this.instanceIcon = "images/icon/cancel.svg#cancel";
}
}
}
Now, I'm running into this error in the browser console
TypeError: this._subscribe is not a function
I put it on a plunker and I added a function for sending message to the Websocket endpoint. Here is the important edit:
public GetInstanceStatus(): Observable<any>{
this.websocket = new WebSocket("ws://echo.websocket.org/"); //dummy echo websocket service
this.websocket.onopen = (evt) => {
this.websocket.send("Hello World");
};
return Observable.create(observer=>{
this.websocket.onmessage = (evt) => {
observer.next(evt);
};
})
.share();
}
Update
As you mentioned in your comment, a better alternative way is to use Observable.fromEvent()
websocket = new WebSocket("ws://echo.websocket.org/");
public GetInstanceStatus(): Observable<Event>{
return Observable.fromEvent(this.websocket,'message');
}
plunker example for Observable.fromEvent();
Also, you can do it using WebSocketSubject, although, it doesn't look like it's ready yet (as of rc.4):
constructor(){
this.websocket = WebSocketSubject.create("ws://echo.websocket.org/");
}
public sendMessage(text:string){
let msg = {msg:text};
this.websocket.next(JSON.stringify(msg));
}
plunker example
Get onMessage data from socket.
import { Injectable } from '#angular/core';
import {Observable} from 'rxjs/Rx';
#Injectable()
export class HpmaDashboardService {
private socketUrl: any = 'ws://127.0.0.0/util/test/dataserver/ws';
private websocket: any;
public GetAllInstanceStatus(objStr): Observable<any> {
this.websocket = new WebSocket(this.socketUrl);
this.websocket.onopen = (evt) => {
this.websocket.send(JSON.stringify(objStr));
};
return Observable.create(observer => {
this.websocket.onmessage = (evt) => {
observer.next(evt);
};
}).map(res => res.data).share();
}
**Get only single mesage from socket.**
public GetSingleInstanceStatus(objStr): Observable<any> {
this.websocket = new WebSocket(this.socketUrl);
this.websocket.onopen = (evt) => {
this.websocket.send(JSON.stringify(objStr));
};
return Observable.create(observer => {
this.websocket.onmessage = (evt) => {
observer.next(evt);
this.websocket.close();
};
}).map(res => res.data).share();
}
}
A different approach I used is with subject:
export class WebSocketClient {
private client: WebSocket | undefined;
private subject = new Subject<string>();
...
private connect() {
const client = new WebSocket(fakeUrl);
const client.onmessage = (event) => {
this.subject.next(event.data);
};
}
private watch() { return this.subject } // can be mapped
}
And using it will be in my opinion clearer:
const client = new WebSocketClient(); // can also be injected
client.connect();
client.watch().subscribe(x => ...);
Happy coding!