I have following effect that uses the an observable source from a PlatformService provided by Angulars Dependency Injection.
public resume$ = createEffect(() => {
return this.platformService.resume().pipe(mapTo(PlatformActions.appResumed()));
});
constructor(private platformService: PlatformService) {
}
This works really well, but I do not really know how to test it elegantly. Usually we try to setup our TestingModule as unspecific as possible. e.G
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
beforeEach(() => {
platformServiceSpy = jasmine.createSpyObj({
resume: EMPTY
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// AAA: This test should setup the response from the platformService so the arrange setup is coupled in the test.
});
});
});
Sadly this does not seem to work as the effects are created in the constructor and therefore my event stream is always EMPTY and does not trigger the effect even if I overwrite the resume response in my spy.
Now I could set a default value in the beforeEach (e.G. of(undefined)) but then it is not really coupled in my tests and I cant use the AAA pattern.
On the other hand I could probably create a new Effects instance every time but that seems a bit overkill not?
Is there a better method? NgRx solved it really well with the actions stream and I wonder if there is a similar solution for effects using sources from DI.
For more info see, https://timdeschryver.dev/blog/testing-an-ngrx-project#effects
it('fetch$ dispatches a success action', () => {
// 🔦 The Effect Actions stream is created by instantiating a new `ActionsSubject`
const actions = new ActionsSubject();
const effects = new CustomersEffects(actions, newCustomerService());
// 🔦 Subscribe on the effect to catch emitted actions, which are used to assert the effect output
const result: Action[] = [];
effects.fetch$.subscribe((action) => {
result.push(action);
});
const action = customerPageActions.enter({ customerId: '3' });
actions.next(action);
expect(result).toEqual([
customersApiActions.fetchCustomerSuccess(
newCustomer({
id: action.customerId,
}),
),
]);
});
I tried a bit more and thought that the actions stream is probably just a subject and I could do the same for my own service method:
describe('PlatformEffects', () => {
let effects: PlatformEffects;
let platformServiceSpy: SpyObj<PlatformService>;
let resumeSubject: ReplaySubject<any>;
beforeEach(() => {
// create new subject that can be used as an event emitter
resumeSubject = new ReplaySubject<any>();
platformServiceSpy = jasmine.createSpyObj({
resume: resumeSubject.asObservable()
});
TestBed.configureTestingModule({
providers: [
PlatformEffects,
{
provide: PlatformService,
useValue: platformServiceSpy
}
]
});
effects = TestBed.inject(PlatformEffects);
});
it('should be created', () => {
expect(effects).toBeTruthy();
});
describe('resume$', () => {
it('dispatches appResumed action on resume event', (done) => {
// emit new test string in resume subject
resumeSubject.next('test');
effects.resume$.subscribe((res) => {
expect(res).toEqual(PlatformActions.appResumed());
done();
});
});
});
});
This works for this case but as soon as I try to do the same with a service method that returns a promise this solution does not work anymore.
Related
Component.ts
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
}
service.ts
constructor(private http: HttpClient) { }
public getEmp_ServiceFun(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.serverUrl + 'employees')
.pipe(
catchError(this.handleError)
);
}
it seems a bit strange to me to run the ngoninit method again since it is meant to run only once. I would wrap the employeeservice method in an observable interval. dont forget to unsubscribe though. otherwise it would keep calling the getEmp_ServiceFun until the whole app closes
ngOnInit() {
interval(1000).pipe(
map(() => {this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);})}
this would replace the whole array instead of just adding to it though. I would take a second look at the getEmp_ServiceFun so that you can ask only for new data and not all data and then push it to the array.
edit: even better would be to not subscribe in the the map but move it to after the pipe. you might need to use a switchMap
I used setTimeout() to refresh a component, it is working fine, but now I just need to check, is it good practice or not?
ngOnInit() {
this.employeeservice.getEmp_ServiceFun().subscribe(
(data: Employee[]) => this.employees = data,
error => this.error = error
);
//refresh this component
this.timeout = setTimeout(() => { this.ngOnInit() }, 1000 * 1)
}
Am new to Ngrx, We got stuck with the implementation , Need some help
Architecture:
We are trying to fill the store when the User is authenticated by that time am trying to redirect.
Expectation:
Effects should be called as async and In ui we have to do the redirect
Problem:
Redirection to Homepage is happening only after the ngrx/effect api call is done.
this.userService.authenticateUser(this.userId.toUpperCase(), this.password, (user) => {
this.authenticatedUser = user;
//Call the ngrx dispatchMethod to fill the store
this.router.navigateByUrl("/home");
this.ngrxGetModule_data();
async ngrxGetModule_data() {
this.store.dispatch({ type: Affaire_Action.Affaire_Addstore_Login });
//this.store.dispatch({ type: Affaire_Action.Initial_Store });
}
#Effect()
updateAffaireStore$: Observable<Action> = this.actions$
.ofType(Affaire_Action.Affaire_Addstore_Login)
.map<Action, string>(toPayload)
.switchMap(res => this.affaireService.getAffairesSearchSuggetions('')
//.delay(10000)
.map(res => (
{
type: Affaire_Action.Affaire_on_Success, payload: ({ "parameter": SearchSuggestion, "data": res })
}
)))
.catch(error => Observable.of({ type: Affaire_Action.Affaire_on_Failure, payload: null }))
What are you actually trying with the this.userService.authenticateUser ?
Seems like you are trying to call the function but the way you are doing is wrong.
What is the return type?!!! Observable or promise.
Suggestion: You should call services on you effect and dispatch actions from effect. You can also use this.router.navigateByUrl("/home"); on your effect.
Since in the latest redux-observable (0.17) is direct calling of store.dispatch() deprecated, I wonder what is an alternative if I need to dispatch actions from the outside of my redux app.
Example:
Let's say I have this function which initialize native module and set up native handler.
const configure = (dispatch) => {
const printingModule = NativeModules.PrintingManager
const eventEmitter = new NativeEventEmitter(printingModule)
eventEmitter.addListener(
"PrintingManagerNewPrinterConnected",
(payload) => dispatch({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
},
}))
printingModule.initialize()
}
What I typically do is that I will call this function from observable after something like APP_STARTUP_FINISHED:
const appStatePrepared = (action$: Object, { dispatch }) =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() => {
configurePrinters(dispatch)
})
What is the correct solution for this?
Thanks!
When using RxJS the ideal is to compose streams. So in this case we need to some how create a stream of "PrintingManagerNewPrinterConnected" events that we can then map each to their own PRINTER_MANAGER_NEW_PRINTER_CONNECTED action.a
Let's first learn how to do this completely custom.
Custom Observables
Creating your own custom Observables is very similar to creating a Promise. So say you had the most simple Promise in the world that just immediately resolves to the number 1
const items = new Promise(resolve => {
resolve(1);
});
The equivalent Observable looks super similar
const items = new Observable(observer => {
observer.next(1);
observer.complete();
});
Visually, the main differences are that instead of being passed (resolve, reject) callbacks we're given an Observer because there is next, error, and complete.
Semantically, Observables can represent more than one value by calling observer.next as many times as they'd like until they call observer.complete() to signal the end of the stream; this is in contrast to Promises, which only represent a single value.
Observables are also lazy and synchronous by default, whereas Promises are always eager and async.
Now that we have that understanding we want to take that and wrap your NativeEventEmitter API which uses addEventListener.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
(payload) => observer.next(payload)
);
printingModule.initialize();
});
};
configurePrinters()
.subscribe(payload => console.log(payload));
This works and is super simple, but there's one problem with it: we should call removeListener when they unsubscribe so that we clean up after ourselves and don't leak memory.
To do that, we need to return a subscription inside our custom Observable. A subscription in this context is an object that has an unsubscribe() method on it, which will be called automatically when the subscriber unsubscribes, an error is triggered, or your observable completes. This is your chance to clean up.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return a subscription that has our timer cleanup logic
return {
unsubscribe: () => {
clearInterval(timer);
}
};
});
Because returning an object is a bit verbose RxJS supports a shorthand where you just return a function which itself will be treated as the unsubscribe method.
const items = new Observable(observer => {
let i = 0;
const timer = setInterval(() => {
observer.next(i++);
}, 1000);
// return an "unsubscribe" function that has our timer cleanup logic
return () => {
clearInterval(timer);
};
});
Now we can apply this to our example, where we want to remove our listener when our unsubscribe teardown function is called.
const configurePrinters = () => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(
'PrintingManagerNewPrinterConnected',
listener
);
printingModule.initialize();
return () => eventEmitter.removeListener(
'PrintingManagerNewPrinterConnected',
listener
);
});
};
Now let's turn this into a reusable utility function
const fromPrinterEvent = (eventName) => {
return new Observable(observer => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
const listener = (payload) => observer.next(payload);
eventEmitter.addListener(eventName, listener);
printingModule.initialize();
return () => eventEmitter.removeListener(eventName, listener);
});
};
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.subscribe(payload => console.log(payload));
Observable.fromEvent
While NativeEventEmitter is a react-native thing, it follows the node-style EventEmitter interface and RxJS already comes with a utility helper to create an Observable from them to save you the effort. It's called fromEvent, found at Observable.fromEvent or import { fromEvent } from 'rxjs/observables/fromEvent'.
const fromPrinterEvent = (eventName) => {
return Observable.defer(() => {
const printingModule = NativeModules.PrintingManager;
const eventEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
return Observable.fromEvent(eventEmitter, eventName);
});
};
Here I also wrapped it in Observable.defer so that we don't create the NativeEventEmitter or printingModule.initialize() until someone actually subscribes (maintain laziness). This may or may not be neccesary for you, I don't know what PrintingManager does or how it behaves. e.g. it might be desirable to only create a single emitter and to initialize the module upfront.
const printingModule = NativeModules.PrintingManager;
const printerEmitter = new NativeEventEmitter(printingModule);
printingModule.initialize();
const fromPrinterEvent = (eventName) =>
Observable.fromEvent(printerEmitter, eventName);
So keep in mind that I'm just showing patterns without knowing what PrintingManager, etc does.
Use it within redux-observable
To use this within redux-observable and your epic is now the same as you'd use any other Observable. So we'll want to map the values from it to actions and
mergeMap, switchMap, concatMap, or exhaustMap that into our top-level stream.
Something like this:
const appStatePrepared = action$ =>
action$.ofType(APP_STATE_PREPARED)
.switchMap(() =>
fromPrinterEvent('PrintingManagerNewPrinterConnected')
.map(payload => ({
type: PRINTER_MANAGER_NEW_PRINTER_CONNECTED,
payload: {
macAddress: payload[2],
connectionType: payload[3],
}
}))
);
Remember that many streams, including our custom fromPrinterEvent('PrintingManagerNewPrinterConnected'), go forever until you unsubscribe from them. So if you only want one you'd use .take(1). If you want to unsubscribe when you receive another action you'd use .takeUntil(action$.ofType(WHATEVER)), etc. Normal RxJS patterns.
I'm dispatching an ADD_SOURCE action from my component that when it success dispatches another ADD_SOURCE_SUCCESS:
this.store$
.select(fromRoot.getUserState)
.filter(user => user.id != null && user.logged)
.takeUntil(this.componentDestroyed$)
.do(user => this.store$.dispatch({type: 'ADD_SOURCE', payload: user.username}))
.subscribe();
This is the effect that returns the ADD_SOURCE_SUCCESS according to a net call:
#Effect({ dispatch: true })
addSource$: Observable<Action> = this.actions$
.ofType('ADD_SOURCE')
.switchMap(
(action: Action) =>
this.userService.addCard(action.payload.username, action.payload.token)
.map((card: CardDTO) => {
return <Action>{
type: 'ADD_SOURCE_SUCCESS',
payload: <ICard>{ ... }
};
})
.catch(_ => {
return Observable.of(<Action>{ type: 'ADD_SOURCE_FAILED', payload: { }});
}));
So, then a new ADD_SOURCE_SUCCESS is dispatched on my reducer:
private static saveSourceSuccess(sourcesRdx, type, payload) {
return <ISourceRedux>{
ids: [ ...sourcesRdx.ids, payload.id ],
entities: Object.assign({}, sourcesRdx.entities, {[payload.id]: payload}),
selectedIds: sourcesRdx.selectedIds,
editingSource: null
};
}
Nevertheless, I don't quite figure out how to say on my component that the operation has been success and do one thing or another one...
Any ideas?
in your reducer, you should also handle the success & fail errors, set the store with an error message or whatever you need as data, and make a selector on it. Then the ui should subscribe to this new selector and you ll be notified
check this chapter
selectors
I cannot update the UI immediately after subscribing the data from database, I have to click somewhere
Also if I use the router to go to another page, it does not work
#Component({
selector: 'foo-component',
template: `
{{foo}}
`
})
export class FooComponent extends MeteorComponent {
foo: string ;
constructor() {
super();
this.subscribe('messages', () => {
// I cannot change foo value immediately, I have to click somewhere
this.foo = 'foo';
// Also if I use the router to go to another page, it does not work
// this.router.navigate(['Home']);
});
}
}
How to solve this?
Note the new angular2-meteor version autoBind is set to true by default. So you probably won't meet this issue again.
But you still need use NgZone in Accounts.changePassword or other similar Accounts.foo() functions.
This problem is because that part of code run out of Angular 2 zone, you need run it inside of zone to update UI immediately or use router to go to another page.
Where do these problems usually happen?
Most time you don't do this. So when do you need this? Usually in callback of Meteor functions:
this.autorun(() => {
// here you need
});
this.subscribe('messages', () => {
// here you need
});
this.call('newMessage', (error, result) => {
// here you need
});
Accounts.changePassword(currentPassword, newPassword, error => {
// here you need
});
How to solve?
Take
this.call('newMessage', (error, result) => {
this.router.navigate(['Home']);
});
for example, you need change to:
import { NgZone } from '#angular/core';
constructor(private zone: NgZone) {}
this.call('newMessage', (error, result) => {
this.zone.run(() => {
this.router.navigate(['Home']);
});
});
Is there a clean way?
Luckily, Angular2-Meteor helps you do this dirty work.
Check Angular2-Meteor API document, there is an autoBind parameter for this.subscribe and this.autorun.
So now you don't need use this.zone.run, instead you can just add a true:
this.autorun(() => {
// your code
}, true);
this.subscribe('messages', () => {
// your code
}, true);