RxJs custom operator cant access 'this' - javascript

I created a custom operator that access the this but it seems that it's always undefined, even though I passed to the function this with bind
custom operator
function shouldLoadNewOptimizationData() {
return filter(function([p1,p2,p3]) {
// some logic
if(){
this.store.dispatch()...
}
})
}
custom operator usage
effect = createEffect(()=> this.actions$.pipe(
ofType(//action type),
withLatestFrom(
//...selectors
),
shouldLoadNewOptimizationData().bind(this),
// more operators..
)
)

Generally a rxjs custom operator is in the way
function myOperator<T>(source: Observable<T>) {
...some logic...
return source; //or return source.pipe(....)
}
Or with arguments
function myOperator<T>(...args) {
return function<T>(source: Observable<T>): Observable<T> {
..some logic..
return source //or return source.pipe(....)
};
}
See this great link about this

Pass the store to shouldLoadNewOptimizationData. This way you avoid having to bind this every time you use the function:
function shouldLoadNewOptimizationData(store) {
return filter(function([p1,p2,p3]) {
// some logic
if(){
store.dispatch()... // -> use the provided store instead of `this.store`
}
})
}
effect = createEffect(()=> this.actions$.pipe(
ofType(//action type),
withLatestFrom(
//...selectors
),
shouldLoadNewOptimizationData(this.store),
// more operators..
);

Change to arrow function
function shouldLoadNewOptimizationData() {
return filter(([p1,p2,p3]) => {
// some logic
if(){
this.store.dispatch()...
}
})
}

If you want to go full angular 15 (who needs this, decorators, ngmodules, constructors, ...)
import { Store } from '#ngrx/store';
...
const shouldLoadNewOptimizationData = () => {
const store = inject(Store)
return filter(([p1,p2,p3]) => {
// some logic
if(){
store.dispatch()...
}
})
}

Related

How do I make sure one Subcription finishes before another?

globleVariable: any;
ngOnInit() {
// This doesn't work. methodTwo throws error saying "cannot read someField from null. "
this.methodOne();
this.methodTwo();
}
methodOne() {
this.firstService.subscribe((res) => { this.globleVariable = res });
}
methodTwo() {
this.secondService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
As shown above, methodOne set the value of globleVariable and methodTwo uses it, therefore the former must finish running before the latter.
I am wondering how to achieve that.
Instead of subscribing in the methods, combine them into one stream and subscribe to that in ngInit(). You can use tap to perform the side effect of updating globaleVariable that you were previously performing in subscribe().
In the example below the "methods" are converted into fields since there is no reason for them to be methods anymore (you can keep them as methods if you want). Then the concat operator is used to create a single stream, where methodOne$ will execute and then when it's complete, methodTwo$ will execute.
Because concat executes in order, you are guaranteed that globaleVariable will be set by methodOne$ before methodTwo$ begins.
globleVariable: any;
methodOne$ = this.someService.pipe(tap((res) => this.globleVariable = res));
methodTwo$ = this.someService.pipe(tap((res) => console.log(this.globleVariable.someField));
ngOnInit() {
concat(this.methodOne$, this.methodTwo$).subscribe();
}
You can create a subject for which observable 2 will wait to subscribe like below :-
globalVariable: any;
subject: Subject = new Subject();
methodOne() {
this.someService.subscribe((res) => { this.globleVariable = res; this.subject.next(); });
}
methodTwo() {
this.subject.pipe(take(1), mergeMap(() => this.someService)).subscribe((res) => {
console.log(this.globleVariable.someField) });
}
The only way to guarantee a method call after a subscription yields is to use the subscription callbacks.
Subscriptions have two main callbacks a success and a failure.
So the way to implement a method call after the subscription yeilds is to chain it like this:
globleVariable: any;
ngOnInit() {
this.methodOne();
}
methodOne() {
this.someService.subscribe((res) => {
this.globleVariable = res
this.methodTwo(); // <-- here, in the callback
});
}
methodTwo() {
this.someService.subscribe((res) => { console.log(this.globleVariable.someField) });
}
You might want to chain the calls with some other rxjs operators for a more standard usage.
ngOnInit() {
this.someService.method1.pipe(
take(1),
tap(res1 => this.globleVariable = res1)
switchmap(res1 => this.someService.method2), // <-- when first service call yelds success
catchError(err => { // <-- failure callback
console.log(err);
return throwError(err)
}),
).subscribe(res2 => { // <-- when second service call yelds success
console.log(this.globleVariable.someField) });
});
}
Please remember to complete any subscriptions when the component is destroyed to avoid the common memory leak.
my take,
so it's a bit confusing when you use same service that throws different results, so instead of someService I used firstService and secondService here.
this.firstService.pipe(
switchMap(globalVariable) =>
this.secondService.pipe(
map(fields => Object.assign({}, globalVariable, { someField: fields }))
)
)
).subscribe(result => {
this.globalVariable = result;
})
What I like about this approach is that you have the flexibility on how you want to use the final result as it is decoupled with any of the property in your class.

Assign observable within callback in Angular 9

I am completely a newbie to Angular. Some how struggled to get the initial code working now I am stuck here.
Use case : Show loading bar. Fetch merchant list to an observable. Stop loading bar.
Problem:
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm);
--> this.isLoading=false;
}
}
this.isLoading becomes false even before getting the response from getMerchantsBySearch call. I know I will need to perform this step in callback but I am not sure how to.
I want to do something like this but I a missing something. What is the correct way to do it.
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm).subscribe(
res={
---> this.people$ =res;
---> this.isLoading=false;
}
);
}
}
dashboard.component.ts
people$: Observable<IMerchant[]>;
fetchUsers($event) {
let searchTerm = $event.term;
if (searchTerm.length > 3) {
this.isLoading=true;
this.people$ = null;
this.userService.getMerchantsBySearch(searchTerm);
this.isLoading=false;
}
}
user.service.ts
getMerchantProfile(id){
var options = this.getOptions();
return this.http.get<IMerchant[]>(environment.APIBaseURL+"/getMerchantProfile/"+id,options);
}
merchant.model.ts
export interface IMerchant{
email:String,
mobile : String,
password: String,
businessName : String,
otp:Number,
info:string,
url:String
}
i prefer using the teardown method .add(). it doesn't pollute your pipe chain of data manipulation and is also executed after an error is handled:
this.userService.getMerchantsBySearch(searchTerm)
.pipe(
// manipulate data here
)
.subscribe(
res => {
// handle final data
},
error => {
// handle error
}
).add(() => {
// teardown here (e.g. this.isLoading=false;)
});
By the way: if this.people$ is an observable, you can not assign it inside the subscribe callback. you'll need to assign it to the response of the userService method you are using. Be careful not to call subscribe on the stream since it will return you a subscription instead of an observable:
this.people$ = this.userService.getMerchantsBySearch(searchTerm).pipe(...)
You can actually pipe it and add a finalize
this.userService.getMerchantsBySearch(searchTerm)
.pipe(finalize(() => this.isLoading=false))
.subscribe(
res => {
this.people$ =res;
});

How to convert a class's recursive function to be a redux action?

A recursive function shown bellow locates in a class. And, no state modification happens in the code section.
//delete target node and its subtree
function deleteSubTree(cur,nodearray){
if(the node is not a leave) {
//go to leaves
deleteSubTree(cur.child,nodearray);
}
else{
delete cur;
if(cur.parent has a child) deleteSubTree(cur.parent,nodearray)
else return;
}
}
But because now I need to refactor my code to fit the redux framework, I need to turn this into a action.
I think it should be converted to be
export function deleteSubTreeAction(node) {
return (diespatch)=>{
....
dispatch(deleteSubTreeAction(node.rChild));
...
};
}
However, this looks quite weird. Any suggestions?
Split it into two actions:
DELETE_SUBTREE_START
DELETE_SUBTREE_END
Dispatch them like this:
// Helper function / side effect
function deleteSubTree(cur, nodearray, done){
if(the node is not a leave) {
//go to leaves
deleteSubTree(cur.child, nodearray, done);
}
else {
delete cur;
if (cur.parent has a child) {
deleteSubTree(cur.parent, nodearray, done)
} else {
return done();
}
}
}
// Action creator
export function deleteSubTreeAction(node) {
return (dispatch) => {
...
dispatch({ type: 'DELETE_SUBTREE_START' });
deleteSubTree(
node.rChild,
?, // not sure if you need the second argument here?
() => dispatch({ type: 'DELETE_SUBTREE_END' })
);
...
};
}
This way the recursive function can signal when it's done() and can take as long as it needs. For performance, consider limiting the maximum tree depth though, which could dispatch something like DELETE_SUBTREE_ABORT.

Javascript (react native): how to avoid that = this?

I'd like to avoid let that = this; because it seems to be a dirty solution. Is it e.g. possible to use .bind(this) anyhow?
My current code:
// ...
componentDidMount() {
let that = this; // <- how to avoid this line?
this.props.myService.listensTo('action', (data) => {
that.handleData(data);
});
}
handleData(data) {
// handle data
}
// ...
Thanks in advance!
Basically arrow functions will help with this and since React-Native doesnt have to deal with browser compatibility you can define you're functions like this:
handleData = (data) => {
this.setState({ data });
}
You won't ever have to .bind or that=this if you use this.
this is already bound because of the arrow function you've used.
// ...
componentDidMount() {
this.props.myService.listensTo(
'action',
(data) => this.handleData(data)
);
}
handleData(data) {
// handle data
}
// ...

How do I stub a chain of methods in Sinon?

I know how to use stub to replace one function.
sandbox.stub(Cars, "findOne",
() => {return car1 });
But now I have a line in my function I want to test that I need to stub that looks like this
Cars.find().fetch()
So there is a chain of function here and I'm unsure what I need to do. How do I stub "find" to return something that I can use to stub "fetch"?
IMHO, we can just use returns to do this. We don't need to use callsFake or mock it as function.
// Cars.find().fetch()
sinon.stub(Cars, 'find').returns({
fetch: sinon.stub().returns(anything)
});
in case, if there is another method after fetch(), we can use returnsThis()
// Cars.find().fetch().where()
sinon.stub(Cars, 'find').returns({
fetch: sinon.stub().returnsThis(),
where: sinon.stub().returns(anything)
});
Ref:
https://sinonjs.org/releases/v6.3.3/
Hope it helps
Try this:
sandbox.stub(Cars, "find", () => {
return {
fetch: sinon.stub().returns(anything);
};
});
The form of attaching a function to a stub shown here:
sandbox.stub(Cars, "find", () => {
return {
fetch: sinon.stub().returns(anything);
};
});
is deprecated.
It's now, as of version 6.3
sandbox.stub(Cars, "find").callsFake(() => {
return {
fetch: sinon.stub().returns(anything);
};
});
This is another approach that also allows spying on chains of jQuery methods - which took me a long time to figure out.
In the example, I am trying to test that an email field is cleared out
//set up stub and spy
const valSpy = sandbox.spy();
const jQueryStub = sandbox
.stub($.prototype, "find") // this prototype is important
.withArgs("input[name=email]")
.returns({ val: valSpy });
// call function under test
learnerAlreadyAccepted(inviteDoc);
// check expectations
expect(jQueryStub).to.have.been.called; // not really necessary
expect(valSpy).to.have.been.calledWith("");
and the function under test is (roughly):
learnerAlreadyAccepted = function(doc) {
$("form").find("input[name=email]").val("");
}
I ran into this problem and, though I liked the solution for a single test, wanted something more dynamic that would allow for reuse across tests. I also preferred the sandbox approach, as it made restoring much easier for larger suites. End result:
export function setupChainedMethodStub(sandbox: sinon.SinonSandbox, obj: any, methodName: string, methodChain: string[], value: any) {
return sandbox.stub(obj, methodName).returns(generateReturns(sandbox, methodChain, value));
}
function generateReturns(sandbox: sinon.SinonSandbox, methodChain: string[], value: any): any {
if (methodChain.length === 1) {
return {
[methodChain[0]]: sandbox.stub().returns(value),
};
} else {
return {
[methodChain[0]]: sandbox.stub().returns(generateReturns(sandbox, methodChain.slice(1), value)),
};
}
}
Wherever I want to set up a stub on the fly, I pass in the created sandbox and the other parameters:
setupChainedMethodStub(sandbox, MyMongooseModel, 'findOne', ['sort', 'exec'], { foo: 'bar' })
Then I just have a sandbox.restore() in my highest scoped afterEach()
There are a few changes from v2.0.
More details here
One of them is:
stub(obj, 'meth', fn) has been removed, see documentation
You can downgrade but I would not recommend it, instead you can do something like this:
let stub = sinon.stub(obj, "meth").callsFake(() => {
return {
meth2: sinon.stub().callsFake(() => {
return {
meth3: sinon.stub().returns(yourFixture),
};
}),
};
});
I have a simple solution that hopefully works for others.
Presuming that fetch is also a method on Cars, and fetch and find support method chaining, Cars may look something like this:
class Cars {
fetch() {
// do stuff
return this;
}
find() {
// do stuff
return this;
}
}
[ANSWER] We should be able to support method chaining with the stub like this:
sandbox.stub(Cars, 'fetch').callsFake(function () { return this; }); // optional
sandbox.stub(Cars, 'findOne').callsFake(function () { return this; });

Categories

Resources