firasKoubaa
firasKoubaa

Reputation: 6867

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?

Upvotes: 4

Views: 26655

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249546

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

Upvotes: 1

Abel Valdez
Abel Valdez

Reputation: 2408

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

Upvotes: 0

Related Questions