How to write proxy function over funciton? - javascript

There is a class with some several methods. One of them:
drawGeojson(layerid: string, geojsonObject) {
this.ngZone.run(() => {}
}
How to refactor the code - so that, on request, you can execute the method body with a wrapper
this.ngZone.run(() => {}
and without it?
I need something this:
The default call: drawGeojson()
The proxy call Wrapper.drawGeojson();

Seems like you want a decorator function.
function makeConditional<T extends unknown[], K>(fn: (...args: T) => K) {
return function(...args: [...T, boolean]): K | undefined {
if (args.pop()) {
return fn(...args as any);
}
}
}
and then used like
drawGeojson = makeConditional(function(layerId: string, geojsonObject: whatever) { ... });

If you want conditionally execute
this.ngZone.run(() => {})
You can add an additional parameter to your method and depend on whether it execute or not.
wrapperZone(method: Function, execute: boolean) {
execute ? this.ngZone.run(() => {
method()
}) : method()
}
wrapperZone(this.drawGeojson.bind(this), true)

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.

Define a function that returns a function that returns void

I don't understand why this gives an error
function foo(): () => string {
return () => 123;
}
But this does not
function foo(): () => void {
return () => 123;
}
And also this will not give an error as well
function foo(): (a: number) => string {
return () => '123';
}
I explicitly type that returned function should accept one argument, then return a function that does not accept any, but TS does not give me any error.
Assigning a function of type () => number to something of type () => string clearly is a type error. However, for void as a return type, TypeScript is more lenient. From the handbook on the Assignability of Functions:
The void return type for functions can produce some unusual, but
expected behavior.
Contextual typing with a return type of void does not force functions
to not return something. Another way to say this is a contextual
function type with a void return type (type vf = () => void), when
implemented, can return any other value, but it will be ignored.
[…]
There is one other special case to be aware of, when a literal
function definition has a void return type, that function must not
return anything.
There's also an FAQ entry about it.
My investigations ended up with a fact that it is impossible to do this with TypesScript, unfortunately.
Here are the links that explain why this unintuitive decision was made
Void type
Assignability of functions
FAQ
The solution that is closest to the desired functionality is to
Type returned function to return undefined
function foo(): () => undefined { ... }
Set on option in tsconfig.json
"compilerOptions": {"noImplicitReturns": false}
Explicitly return from the returned function
function foo(): () => undefined {
return () => { return; }
}
Looks a bit ugly but now we can define a function that for sure will return nothing.

Jasmine testing - Error upon spy definition

I have the following classes
export class Init {
constructor(url: URL) {}
getClient(url: URL): Client {
return new Client(url);
}
}
and the client is defined like
export class Client {
constructor(readonly url: URL) {}
foo(s: String): Promise<void> {}
// many other methods
}
and now I am trying to test this like so - with Jasmine
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
...
}
On the spy definition I get this error
Argument of type 'string' is not assignable to parameter of type 'never'
Why can I resolve this? Init's getClient method returns a Client object. Shouldn't the spy be able to identify this type?
My end result should look like this
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
expect(initCommand.getClient.foo).toHaveBeenCalledTimes(1);
}
You can only spyOn public methods. I would do this:
// mock `getClient` object however you like (some examples below)
// Return the object/value right away
spyOn(init, 'getClient').and.returnValue({ foo: (s: string) => Promise.resolve(s) });
// call a fake function every time init.getClient is called
spyOn(init, 'getClient').and.callFake((url) => return {});

Angular: pass a method as parameter

I have these two methods which are almost similar:
private firstFunction () {
this.serviceOne.methodOne().subscribe(
res => {
return resultOne = res;
},
err => {}
);
}
private secondFunction () {
this.serviceTwo.methodTwo().subscribe(
res => {
return resultTwo = res;
},
err => {}
);
}
I want to write a generic function, like this:
genericFunction (service ,method , result ) {
service.method().subscribe(
res => {
return result = res;
},
err => {}
);
}
And consequently I want to get something like this working:
genericFunction (serviceOne , methodOne , resultOne );
genericFunction (serviceTwo , methodTwo , resultTwo );
Actually, I cannot find how to pass methodOne and methodTwo as params. Any sugestions?
There are several issues in your code.
Firstly, you want to modify the field you pass in as a parameter (as suggested by result = res. You can't pass in a reference to a field, but you can pass in the field name, and use indexing to change the field. keyof T will allow you to pass in the field in a type safe way.
Secondly if you want to access a method on a service. Again we can do this passing in the method name, and we can constrain the service to have a method with the passed in method name, that returns an Observable. The result of the Observable can also be constrained to be of the same type of the field we are going to assign it to in order for the method to be fully type safe.
declare class Service1 {
method1() : Observable<number>
}
declare class Service2 {
method2() : Observable<string>
}
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
this.genericFunction(new Service1(), "method1", "resultOne");
this.genericFunction(new Service2(), "method2", "resultTwo");
this.genericFunction(new Service1(), "method1", "resultTwo"); // error resultTwo is a string, the method return Observable<number>
this.genericFunction(new Service2(), "method", "resultTwo"); // error method does not exit on Service2
this.genericFunction(new Service2(), "method2", "resultTwo2"); // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(service:Record<MethodKey, ()=> Observable<MyClass[ResultKey]>>, method:MethodKey, result: ResultKey){
service[method]().subscribe(
res => this[result] = res,
err => {}
);
}
}
Note We could have also passed in the function as a function not just as a name, but directly a typed function. The disadvantage of this is that we either have to use bind to ensure the service method will still have the correct this when it's called, or use an arrow function when calling (again to ensure the service method has the correct this). This is error prone though, bind results in an untyped function, so we can't check compatibility to the field, and someone might pass service.method directly and no error would be reported until runtime:
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
var service1 = new Service1()
var service2 = new Service2()
this.genericFunction(()=> service1.method1(), "resultOne");
this.genericFunction(()=> service2.method2(), "resultTwo");
this.genericFunction(service2.method2, "resultTwo"); // no error, depending on the implementation of method2 it might or might not work
this.genericFunction(service2.method2.bind(service2), "resultOne"); // no error, the service call will work, but we store it in an incompatible variable
this.genericFunction(()=> service1.method1(), "resultTwo");// error resultTwo is a string, the method return Observable<number>
this.genericFunction(()=> service2.method2(), "resultTwo2");// // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(method:()=> Observable<MyClass[ResultKey]>, result: ResultKey){
method().subscribe(
res => this[result] = res,
err => {}
);
}
}
try by using the following code:
private firstFunction () {
let response= genericFunction(this.serviceOne.methodOne())
}
private secondFunction () {
let response = genericFunction(this.serviceTwo.methodTwo())
}
Modify you Generic Function by just receiving a variable.
//if it is angular 4 or less
genericFunction (method: Observable) {
return method.map(res => {
return res.json();
});
}
//if it is angular 5 or 6
genericFunction (method: Observable) {
return method.pipe(
map(res => {
return res;
}));
}

Returning Observable based on callback

I try to adjust a working example from ngx-chips to my needs. This is how the onRemoving method example looks like:
public onRemoving(tag: TagModel): Observable<TagModel> {
const confirm = window.confirm('Do you really want to remove this tag?');
return Observable
.of(tag)
.filter(() => confirm);
}
Now instead of windows.confirm I want to use a custom component that has a AskQuestion method with the following signatur:
AskQuestion(question: string, yesCallback: () => void, noCallback?: () => void): void {
So now I have multiple callbacks but the ngx-chips components expect that I return an observable. I tried to convert the callback to an observable using the bindCallback method:
public onRemoving(tag: TagModel): Observable<TagModel> {
const choiceCallback = (choice: boolean): TagModel=> {
if (choice)
return tag;
};
this.questionService.AskQuestion("Remove item?", () => choiceCallback(true), () => choiceCallback(false))
return Observable.bindCallback(choiceCallback);
}
But it looks like I am doing it wrong. Any ideas?
The definition of bindCallback() reads:
Give it a function f of type f(x, callback) and it will return a function g that when called as g(x) will output an Observable.
And your usage does not fit this description. choiceCallback() does not return a function that returns an observable.
Use an Observable constructor instead:
public onRemoving(tag: TagModel): Observable <TagModel> {
return Observable.create(observer => {
const choiceCallback = (choice: boolean) => {
if (choice) {
observer.next(tag);
}
observer.complete();
};
this.questionService.AskQuestion("Remove item?", () => choiceCallback(true), () => choiceCallback(false));
});
}
I'm not 100% familiar with this stack, but as far as I could see, looks like bindCallback returns a function which returns an Observable (docs).
So maybe you need to call it to get an observable and use it on your return statement?
Since in your function signature says it would return an Observable type.
You could try to replace the return statement by:
return Observable.bindCallback(choiceCallback)()

Categories

Resources