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)()
Related
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)
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.
I have been trying to observe changes on a Map Object in JavaScript but for some reason only can observe the creation of the object. Do observables not work when adding/removing data from a Map?
Here is the observable:
test(): Observable<Map<string, Object>> {
return of(this.testModel.test());
}
This is me subscribing to it:
test(): Observable<Map<string, Object>> {
let mapOb = this.testModel.test();
return Observable.create((obsrvr) => {
const originalSet = mapOb.set;
const originalDelete = mapOb.delete;
mapOb.set = (...args) => {
obsrvr.next(originalSet.call(mapOb, ...args));
};
mapOb.delete = (...args) => {
obsrvr.next(originalDelete.call(mapOb, ...args));
}
});
}
I see the log statement during the creation of the Map, but if i add any new entries to the Map nothing is logged. Anyone know why this may be happening?
I get an error at maoOb.set and mapOb.delete:
Type '(key: string, value: Object) => void' is not assignable to type '(key: string, value: Object) => Map<string, Object>'.
Type 'void' is not assignable to type 'Map<string, Object>'
You current approach doesn't seem correct when you want to listen to addition/deletion of data in a Map.
You are simply returning an Observable using of(Object Reference), this will no way know about things that you are doing with the Object you are passing with it.
You need to have an Observable which emits when you perform set() or delete() over the MapInstance.
You may modify your Map instance this way to achieve what you desire.
createObservable(mapOb) {
return Observable.create((obsrvr) => {
const originalSet = mapOb.set;
const originalDelete = mapOb.delete;
mapOb.set = (...args) => {
const setReturn = originalSet.call(mapOb, ...args);
obsrvr.next(setReturn);
return setReturn;
};
mapOb.delete = (...args) => {
const deleteReturn = originalDelete.call(mapOb, ...args);
obsrvr.next(deleteReturn);
return deleteReturn;
}
});
}
Pass the map to createObservable() method and subscribe to it. In this method, I have modified the set and delete methods of your map, so that it emits a value when those methods are called.
I have created a dummy example for the answer: Link.
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;
}));
}
I am adding return type values to my functions based on comments I have received
in a code review and I don't know what to assign the return type to on this function:
function mysteryTypeFunction(): mysteryType {
return function(): void {
console.log('Doing some work!');
};
}
What is the mysteryType for this function?
Typescript will infer the return type, and the easiest way to find out what it infers is to hover over the symbol:
As we can see the return type is () => void. Which is a function signature of a function with no argument (the () part), that returns void (the => void part).
function mysteryTypeFunction(): () => void {
return function(): void {
console.log('Doing some work!');
};
}
It would be () => void
It's something that you can check by hovering over the function name itself.
use this.
mysteryTypeFunction(): () => void {
return () => {
console.log('Doing some work!');
};
}