I created two observables from a database:
const roomData$ = estab
.query()
.where('estab.type', 'like', 'rooms')
.then((rooms) => {
rooms.forEach((room) => {
observer.next(room.data);
});
});
}).map(data => ({
roomsData: [
...data.rooms,
],
}));
const hotelData$ = Observable.create((observer) => {
estab
.query()
.where('estab.type', 'like', 'hotels')
.then((hotels) => {
hotels.forEach((hotel) => {
observer.next(hotel.data);
});
});
}).map(data => ({
hotelsData: [
...data.hotels,
],
}));
const hotelContentData$ = Observable.concat(roomData$, hotelData$);
hotelContentData$.subscribe((data) => {
fs.appendFile('data.json', (err) => {
if (err) throw err;
console.log('file has been appended!);
}
})
In the data.json file I found only the data received from the roomData$ observable. I change the order in Observable.concat(hotelData$, roomData$) and now I get the data of hotel observable. Why only one Observable is executed? How can I execute both of them and get data from hotelData$ and roomData$?
Observable.concat will take emissions from a stream until that stream completes, and then move to the next stream. Since you're creating your own observable sequences, be sure to complete each one.
const roomData$ = Observable.create( observer => {
estab
.query()
.where('estab.type', 'like', 'rooms')
.then((rooms) => {
rooms.forEach((room) => {
observer.next(room.data);
});
observer.complete(); // adding this will complete your stream.
});
}).map(data => ({
roomsData: [
...data.rooms,
],
}));
const hotelData$ = Observable.create((observer) => {
estab
.query()
.where('estab.type', 'like', 'hotels')
.then((hotels) => {
hotels.forEach((hotel) => {
observer.next(hotel.data);
});
observer.complete(); // adding this will complete your stream.
});
}).map(data => ({
hotelsData: [
...data.hotels,
],
}));
const hotelContentData$ = Observable.concat(roomData$, hotelData$);
hotelContentData$.subscribe((data) => {
fs.appendFile('data.json', (err) => {
if (err) throw err;
console.log('file has been appended!);
});
});
Related
I have actually a USB Device plug on my computer.
This device allow me to read NFC but when I'm putting my NFC to be red the navigator (Chrome) returned me this error message :
Uncaught (in promise) DOMException: Failed to execute 'claimInterface' on 'USBDevice': The requested interface implements a protected class.
My code is the next one :
var usbd = {};
let device;
let deviceEndpoint = 0x02;
let powerUpDevice = new Uint8Array([0x62,0x00, 0x00, 0x00, 0x00,0x00,0x00,0x01,0x00, 0x00]).buffer;
let getCardUID = new Uint8Array([0xff,0xca,0x00,0x00,0x04]).buffer;
(function() {
'use strict';
usbd.authorize = function(){
navigator.usb.requestDevice({ filters: [{ vendorId: 0x072f }] })
.then(selectedDevice => {
device = selectedDevice;
console.log(device.configuration.interfaces[0].interfaceNumber);
console.log(device.manufacturerName);
console.log(device.productName);
console.log(device);
return device.open()
.then(() => {
if (device.configuration === null) {
return device.selectConfiguration(1);
}
});
})
.then(() => device.claimInterface(0))
.then(() => device.transferOut(deviceEndpoint, powerUpDevice)
.then(transferResult => {
console.log(transferResult);
}, error => {
console.log(error);
device.close();
})
.catch(error => {
console.log(error);
})
);
};
usbd.getDevice = function(){
return navigator.usb.getDevices().then(devices => {
return devices.map(device => new usbd.device(device));
});
};
usbd.device = function(device) {
this.device_ = device;
};
usbd.getTagUID = function(){
device = this.getDevice();
device
.then(device => {
device[0].device_.open()
.then(() => device[0].device_.claimInterface(0))
.then(() => device[0].device_.transferOut(deviceEndpoint, getCardUID)
.then(transferResult => {
console.log(transferResult);
}, error => {
console.log(error);
device[0].device_.close();
})
.catch(error => {
console.log(error);
})
)
.then(() => device[0].device_.claimInterface(0))
.then(() => device[0].device_.transferIn(deviceEndpoint, 16)
.then(USBInTransferResult => {
if (USBInTransferResult.data) {
console.log(USBInTransferResult.data.getUint32(0));
}
}, error => {
console.log(error);
device[0].device_.close();
})
.catch(error => {
console.log(error);
})
);
});
};
$('#connect').click(function(){
usbd.authorize();
});
$('#readCard').click(function(){
usbd.getTagUID();
});
})();
How can I do to read my NFC on my website ?
What currently works with one action:
#Effect()
addAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.AddAssignment),
exhaustMap((action) => {
return this.assignmentDataService.addOrUpdateAssignment([action.payload]).pipe(
map((assignment) => {
return new assignmentActions.AddAssignmentSuccess(assignment);
})
);
}));
How I'm trying to refactor this:
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
map((action) => {
return action.payload;
}),
switchMap((payload) => {
return this.assignmentDataService.addOrUpdateAssignment([payload.postData]);
}),
switchMap((res) => {
return [
new assignmentActions.LastUpdatedAssignmentPost(action.payload.postData),
new assignmentActions.LastUpdatedAssignment(action.payload.mergedData),
new assignmentActions.UpdateAssignmentSuccess(action.payload.mergedData),
];
})
);
How ever ofcourse action.payload.mergedData & action.payload.postData are not available in the last switchMap, and since im quite noob to Effects and Observables I'm breaking my head on this.
Whats the right combination of operators in this one?
To get access to the payload, use the last switchMap in the observable pipeline of this.assignmentDataService.addOrUpdateAssignment API returned observable like this:
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
map((action) => {
return action.payload;
}),
switchMap((payload) => {
return this.assignmentDataService.addOrUpdateAssignment([payload.postData])
.pipe(
switchMap((res) => {
return [
new assignmentActions.LastUpdatedAssignmentPost(payload.postData),
new assignmentActions.LastUpdatedAssignment(payload.mergedData),
new assignmentActions.UpdateAssignmentSuccess(payload.mergedData),
];
})
);
})
);
Hope it helps.
Evantually I solved this just by adding more effects to the equation.
- First effect sends server request... on response sends a UpdateAssignmentSuccess action
- Second effect listens to UpdateAssignmentSuccess and send a LastUpdatedAssignmentPost action
- Third effect listens to LastUpdatedAssignmentPost action and sends a LastUpdatedAssignment action
#Effect()
updateAssignment$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignment),
exhaustMap((action) => {
return this.assignmentDataService.addOrUpdateAssignment([action.payload.postData]).pipe(
map((assignment) => {
return new assignmentActions.UpdateAssignmentSuccess(action.payload);
})
);
}),
catchError(() => {
return of({
type: assignmentActions.AssignmentsActionTypes.UpdateAssignmentFailure,
payload: true
});
})
);
#Effect()
updateAssignmentsSuccess$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.UpdateAssignmentSuccess),
map((action:any) => {
return new assignmentActions.LastUpdatedAssignmentPost(action.payload);
})
);
#Effect()
lastUpdateAssignmentsPost$ = this.actions$.pipe(
ofType(assignmentActions.AssignmentsActionTypes.LastUpdatedAssignmentPost),
map((action:any) => {
return new assignmentActions.LastUpdatedAssignment(action.payload);
})
);
I am learning to use RXJS. In this scenario, I am chaining a few async requests using rxjs. At the last mergeMap, I'd like to have access to the first mergeMap's params. I have explored the option using Global or withLatest, but neither options seem to be the right fit here.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => {
return readCSVFile(gauge.id);
}),
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id))),
catchError(error => of(`Bad Promise: ${error}`))
);
readCSVFile is an async request which returns an observable to read CSV from a remote server.
readStringToArray is another async request which returns an observable to convert string to Arrays
transposeArray just does the transpose
uploadToDB is async DB request, which needs gague.id from the first mergeMap.
How do I get that? It would be great to take some advice on why the way I am doing it is bad.
For now, I am just passing the ID layer by layer, but it doesn't feel to be correct.
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id)),
mergeMap(({ data, gaugeId }: any) => readStringToArray(data, gaugeId)),
map(({ data, gaugeId }) => transposeArray(data, gaugeId)),
mergeMap(({ data, gaugeId }) => uploadToDB(data, gaugeId)),
catchError(error => of(`Bad Promise: ${error}`))
);
Why don't you do simply this?
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => readCSVFile(gauge.id).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gauge.id)))
)),
catchError(error => of(`Bad Promise: ${error}`))
);
You can also wrap the inner observable in a function:
uploadCSVFilesFromGaugeID(gaugeID): Observable<void> {
return readCSVFile(gaugeID).pipe(
mergeMap((csvStr: any) => readStringToArray(csvStr.data)),
map((array: string[][]) => transposeArray(array)),
mergeMap((array: number[][]) => forkJoin(uploadToDB(array, gaugeID))
);
}
In order to do this at the end:
const arraySrc$ = from(gauges).pipe(
mergeMap(gauge => uploadCSVFileFromGaugeID(gauge.id)),
catchError(error => of(`Bad Promise: ${error}`))
);
MergeMap requires all observable inputs; else, previous values may be returned.
It is a difficult job to concatenate and display the merging response. But here is a straightforward example I made so you can have a better idea. How do we easily perform sophisticated merging.
async playWithBbservable() {
const observable1 = new Observable((subscriber) => {
subscriber.next(this.test1());
});
const observable2 = new Observable((subscriber) => {
subscriber.next(this.test2());
});
const observable3 = new Observable((subscriber) => {
setTimeout(() => {
subscriber.next(this.test3());
subscriber.complete();
}, 1000);
});
console.log('just before subscribe');
let result = observable1.pipe(
mergeMap((val: any) => {
return observable2.pipe(
mergeMap((val2: any) => {
return observable3.pipe(
map((val3: any) => {
console.log(`${val} ${val2} ${val3}`);
})
);
})
);
})
);
result.subscribe({
next(x) {
console.log('got value ' + x);
},
error(err) {
console.error('something wrong occurred: ' + err);
},
complete() {
console.log('done');
},
});
console.log('just after subscribe');
}
test1() {
return 'ABC';
}
test2() {
return 'PQR';
}
test3() {
return 'ZYX';
}
I'm trying to block main stream until config is initialized using withLatestFrom operator and secondary stream (which contains info about config state).
Here is my code:
#Effect()
public navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter((action: RouterNavigationAction<RouterStateProjection>) => {
return action.payload.event.url.includes('/redemption-offers/list/active');
})
.do(() => console.log('before withLatestFrom'))
.withLatestFrom(
this.store.select(selectConfigInitialized)
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.do(() => console.log('after withLatestFrom'))
.switchMap(data => {
const action = data[0] as RouterNavigationAction<RouterStateProjection>;
return [
new MapListingOptionsAction(action.payload.routerState.queryParams),
new LoadActiveOffersAction(),
];
});
Problem is that second do (after withLatestFrom) block never gets called.
Log:
before config filter false
before withLatestFrom
before config filter true
after config filter true
Thank you for your suggestions.
I think what you want is .combineLatest.
withLatestFrom only treats what comes before it as the trigger.
combineLatest treats both the source and the observables provided to it as the trigger.
So your issue is that the source (route change) fires and the observable passed to withLatest (state initialized) has not emited yet because of its filter. It only emits a value after the source fires. So it is ignored.
Here is a running example of what you are doing:
const first = Rx.Observable.create((o) => {
setTimeout(() => {
o.next();
}, 2000);
});
const second = Rx.Observable.create((o) => {
setTimeout(() => {
o.next(false);
}, 1000);
setTimeout(() => {
o.next(true);
}, 3000);
});
first
.do(() => console.log('before withLatestFrom'))
.withLatestFrom(
second
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.subscribe(() => console.log('after withLatestFrom'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
Here is with combineLatest:
const first = Rx.Observable.create((o) => {
setTimeout(() => {
o.next();
}, 2000);
});
const second = Rx.Observable.create((o) => {
setTimeout(() => {
o.next(false);
}, 1000);
setTimeout(() => {
o.next(true);
}, 3000);
});
first
.do(() => console.log('before withLatestFrom'))
.combineLatest(
second
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.subscribe(() => console.log('after withLatestFrom'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
The withLatestFrom emits only when its source Observable emits. What happens is that this.store.select emits before the source to withLatestFrom. You can see it from the order of emissions:
before config filter false
before withLatestFrom
...
Now it depends on what you want to achieve. You can trigger this.store.select(selectConfigInitialized) only when the source emits by using concatMap:
navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter(...)
.concatMap(() => this.store.select(selectConfigInitialized)...)
.switchMap(data => { ... })
...
Or you can pass along results from both Observables:
navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter(...)
.concatMap(nav => this.store.select(selectConfigInitialized)
.map(result => [nav, result])
)
.switchMap(data => { ... })
...
You can make it simpler by inverting the observables.
public navigationToActiveRedemptionOffers =
return this.store.select(selectConfigInitialized)
.filter(initialized => initialized)
.flatMap(x =>
return this.actions$.ofType(ROUTER_NAVIGATION)
.filter((action: RouterNavigationAction<RouterStateProjection>) => {
return action.payload.event.url.includes('/redemption-offers/list/active');
})
.map(action =>
return [
new MapListingOptionsAction(action.payload.routerState.queryParams),
new LoadActiveOffersAction(),
];
)
As this is a common pattern, I use a generic waitFor method.
export const waitFor$ = function() {
return this.filter(data => !!data ).take(1);
};
Usage,
return this.config$
.waitFor$()
.flatMap(config => {
return observableWithRequiredValue()
}
Fluent syntax provided by
Observable.prototype.waitFor$ = waitFor$;
declare module 'rxjs/Observable' {
interface Observable<T> {
waitFor$: typeof waitFor$;
}
}
Note, this method of operator creation has been superseded by Lettable Operators in rxjs v5.5 (but the above still works).
I work in angular 2 Project and use ngrx and rxjs technologies.
Now I have a problem:
I try to declare an Effect.
The effect has http request, and only when it success I want to call other http-request, and so only if it also success - then dispatch an success-action.
I has tested it by throw an error but it always dispatch the action!
See:
#Effect()
createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return this.httpService.getDefaultEntityData(action.payload.type).map((entity) => {
return Observable.throw("testing only");
/*if (entity) {
entity.title = entity.type;
return this.httpService.addEntity(entity);
}*/
})
.catch((error) => Observable.of(new createEntityFailure(error)))
.map(mappedResponse => ({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse }))
});
How about this:
this.actions$
.ofType(CREATE_ENTITY)
.map((action: CreateEntity) => action.payload)
.switchMap(payload =>
this.httpService.getDefaultEntityData(payload.type)
.mergeMap(entity => this.httpService.addEntity(entity))
// .mergeMap(entity => Observable.throw('error')) // or this for testing
.mergeMap(response => new actions.Action(...))
.catch(error => new actions.Error(...))
);
You can either split this up into multiple actions or just add another API call in the same effect using Observable.forkJoin
#Effect() createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return Observable.forkJoin(
this.httpService.callOne(),
this.httpService.callTwo()
)
.catch((error) => Observable.of(new createEntityFailure(error)))
.map(mappedResponse => ({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse }))
});
As forkJoin is parallel that won't work for you. You can just switchMap on the first API call and return the second:
#Effect() createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) => {
return this.httpService.callOne();
})
.switchMap((response) => {
return this.httpService.callTwo()
.map(secondResponse => ({
type: CREATE_ENTITY_SUCCESS,
payload: {
first: response,
second: secondResponse
}
}))
})
.catch((error) => Observable.of(new createEntityFailure(error)))
});
1) If you returning Observable you probably want swithMap instead of map
2) Action always has been dispatched because you return non error Observable from catch. Changing Observable.of to Observable.throw will throw error further
#Effect()
createEntity$ = this.actions$.ofType(CREATE_ENTITY)
.switchMap((action: CreateEntity) =>
this.httpService.getDefaultEntityData(action.payload.type)
)
.switchMap((entity) => { // <------ switchMap here
return Observable.throw("testing only");
/*if (entity) {
entity.title = entity.type;
return this.httpService.addEntity(entity);
}*/
})
.catch((error) =>
Observable.throw(new createEntityFailure(error)) // <------ throw here
)
.map((mappedResponse) =>
({ type: CREATE_ENTITY_SUCCESS, payload: mappedResponse })
);