Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
208 views
in Technique[技术] by (71.8m points)

angular - Angular2 - get data from service on init with Switchmap

I'm doing a term search with Angular 2. It's about the same code used in the Heroes tutorial.

My service :

search(term: string = null, page: string = null, limit: string = null): Observable<Bibliographie[]> {
    let params = new URLSearchParams();
    if (term) params.set('q', term);
    if (page) params.set('_page', page);
    if (limit) params.set('_limit', limit);
    return this.http
        .get('http://localhost:3000/bibliographie', {search: params})
        .map(response => response.json() as Bibliographie[])
        .catch(this.handleError);
}

My component:

ngOnInit(): void {

    this.bibliographies = this.searchTerms
        .debounceTime(300)
        .switchMap(term => term
            ? this.bibliographieSearchService.search(term)
            : this.bibliographieSearchService.search(null, "1", "20"))
        .catch(error => {
            console.log(error);
            return Observable.of<Bibliographie[]>([]);
        });
}

It works well if I type some text in the input dialog. Problem is, I want to have some results before typing anything. In the code above, I added the line

: this.bibliographieSearchService.search(null, "1", "20"))

for this purpose, hoping that a page of results would be shown when term is null. But it only works when I clear the input dialog. I also tried to load the data on init with another service function. But then, the search does not work anymore.

Any idea?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem with your solution is that this.searchTerms is probably an Observable of valueChanges, so until the value inside the input does not change, any event is being emitted.

Let's say you want to trigger the search each time that the input is being focused, and also each time that the value is changing.

So, in this case, what do you need is to listen to the focus event as well, to be able to emit a value when the focus event is triggered by the input. But you always need to take the last value that valueChanged emitted. In this case a combineLatest operator would make sense, so you listen to both the focus and the value change. The combineLatest operator emits the latest values of each source Observables, so even in when the focus event is the one that emits, you will receive the last one from this.searchTerms.

To be able to listen to the focus event, you will need to use @ViewChild on your input, to get a reference in your component.

// html
<input ... #myComponent>

// ts

export class MyComponent {
  @ViewChild('myComponent') myComponent;
  myComponentFocus$: Observable;

  constructor () {}
}

Then you can create an observable of the focus event of your input in the ngOnInit function,

ngOnInit(): void {
  this.myComponentFocus = Observable.fromEvent(this.myComponent.nativeElement, 'focus')
  ...
}

Now your bibliographies would be something like:

this.bibliographies = Observable.combineLatest(this.searchTerms, this.myComponentFocus)
    .debounceTime(300)
    .switchMap(([focusEvt, term]) => term
        ? this.bibliographieSearchService.search(term)
        : this.bibliographieSearchService.search(null, "1", "20"))
    .catch(error => {
        console.log(error);
        return Observable.of<Bibliographie[]>([]);
    });

BUT, there is still a problem with this solution. combineLatest won't emit until all the Observables combined have emitted any value, so our this.bibliographies will still need for the value change to emit before emitting anything.

To solve that, we can create an Observable of null, and then concat this.searchTerms to this null Observable. This means the new Observable will have a value (null) from the beginning, so when focus event is emitted, this.bibliographies Observable will emit as well.

ngOnInit(): void {
      this.myComponentFocus = Observable.fromEvent(this.myComponent.nativeElement, 'focus')

      this.searchTermsWithNull = Observable.of(null).concat(this.searchTerms)

      this.bibliographies =     Observable.combineLatest(this.searchTermsWithNull, this.myComponentFocus)
        .debounceTime(300)
        .switchMap(([focusEvt, term]) => term
            ? this.bibliographieSearchService.search(term)
            : this.bibliographieSearchService.search(null, "1", "20"))
        .catch(error => {
            console.log(error);
            return Observable.of<Bibliographie[]>([]);
        });
      }

If, for instance, you only want to use the first focus of the event and not all the others, you can add a take(1) to the focus event observable.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...