Why would you ever call .call() on Observable func

2020-03-13 09:12发布

问题:

I am a relative beginner in Angular, and I am struggling to understand some source I am reading from the ng-bootstrap project. The source code can be found here.

I am very confused by the code in ngOnInit:

ngOnInit(): void {
    const inputValues$ = _do.call(this._valueChanges, value => {
      this._userInput = value;
      if (this.editable) {
        this._onChange(value);
      }
    });
    const results$ = letProto.call(inputValues$, this.ngbTypeahead);
    const processedResults$ = _do.call(results$, () => {
      if (!this.editable) {
        this._onChange(undefined);
      }
    });
    const userInput$ = switchMap.call(this._resubscribeTypeahead, () => processedResults$);
    this._subscription = this._subscribeToUserInput(userInput$);
  }

What is the point of calling .call(...) on these Observable functions? What kind of behaviour is this trying to achieve? Is this a normal pattern?

I've done a lot of reading/watching about Observables (no pun intended) as part of my Angular education but I have never come across anything like this. Any explanation would be appreciated

回答1:

To understand it, first you can have a look at the predefined JavaScript function method "call":

var person = {
    firstName:"John",
    lastName: "Doe",
    fullName: function() {
        return this.firstName + " " + this.lastName;
    }
}
var myObject = {
    firstName:"Mary",
    lastName: "Doe",
}
person.fullName.call(myObject);  // Will return "Mary Doe"

The reason of calling "call" is to invoke a function in object "person" and pass the context to it "myObject".

Similarly, the reason of this calling "call" below:

const inputValues$ = _do.call(this._valueChanges, value => {
  this._userInput = value;
  if (this.editable) {
    this._onChange(value);
  }
});

is providing the context "this._valueChanges", but also provide the function to be called base on that context, that is the second parameter, the anonymous function

value => {
  this._userInput = value;
  if (this.editable) {
    this._onChange(value);
  }
}

In the example that you're using:

  • this._valueChanges is the Input Event Observerable

  • The _do.call is for doing some side affects whenever the event input happens, then it returns a mirrored Observable of the source Observable (the event observable)

UPDATED Example code: https://plnkr.co/edit/dJNRNI?p=preview

About the do calling:

You can call it on an Observable like this:

const source = Rx.Observable.of(1,2,3,4,5);
const example = source
.do(val => console.log(`BEFORE MAP: ${val}`))
.map(val => val + 10)
.do(val => console.log(`AFTER MAP: ${val}`));
const subscribe = example.subscribe(val => console.log(val));

In this case you don't have to pass the first parameter as the context "Observable".

But when you call it from its own place like you said, you need to pass the first parameter as the "Observable" that you want to call on. That's the different.

as @Fan Cheung mentioned, if you don't want to call it from its own place, you can do it like:

const inputValues$=this._valueChanges.do(value=>{
  this._userInput = value;
  if (this.editable) {
    this._onChange(value);
  }
})


回答2:

My personal opinion is that they were using this for RxJS prior 5.5 which introduced lettable operators. The same style is used internally by Angular. For example: https://github.com/angular/angular/blob/master/packages/router/src/router_preloader.ts#L91.

The reason for this is that by default they would have to patch the Observable class with rxjs/add/operators/XXX. The disadvantage of this is that some 3rd party library is modifying a global object that might unexpectedly cause problems somewhere else in your app. See https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#why.

You can see at the beginning of the file that they import each operator separately https://github.com/ng-bootstrap/ng-bootstrap/blob/master/src/typeahead/typeahead.ts#L22-L25.

So by using .call() they can use any operator and still avoid patching the Observable class.



回答3:

I suppose

const inputValues$ = _do.call(this._valueChanges, value => {
  this._userInput = value;
  if (this.editable) {
    this._onChange(value);
  }
}); 

is equivalent to

const inputValues$=this._valueChanges.do(value=>{
 this._userInput = value;
  if (this.editable) {
    this._onChange(value);
  }
})

In my opinion it's not an usual pattern(I think it is the same pattern but written in different fashion) for working with observable. _do() in the code is being used as standalone function take a callback as argument and required to be binded to the scope of the source Observable

https://github.com/ReactiveX/rxjs/blob/master/src/operator/do.ts