5

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?

2

2 Answers 2

2

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 => {}
        );
    }
}
Sign up to request clarification or add additional context in comments.

Comments

0

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;
        }));
      }

4 Comments

I don't think that has anything to do with the Angular Version. Rather it depends on whether he's using Http or HttpClient. Also in the eg above, he'll eventually have to subscribe to response to actually have access to the response data which beats the whole purpose of making the function generic in the first place.
@SiddAjmera take a look to this post stackoverflow.com/a/39296015/6651984
Sorry about that. Still not able to understand the point you're trying to put here.
Why not? in response you will get the generic response provided by genericFunction

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.