Как сделать асинхронное автозаполнение в Angular 6 (rxjs 6), который не показывает никаких ненужных данных при быстрой печати - PullRequest
0 голосов
/ 04 октября 2018

Что у меня сейчас есть: У меня есть пользовательская реализация автозаполнения, выглядит так в html:

<input [formControl]="parentFormControl"
       (mouseup)="autocomplete()"
       (input)="autocomplete()">

<ul *ngIf="showAutocompleteList">
  <li *ngFor="let listItem of autocompleteList; let index = index;">
    <a role="option" href="#" type="button" (mousedown)="select(listItem)" [innerHtml]="highlight(listItem)"></a>
  </li>
</ul>

parentFormControl - это значение за входом (так сказать, модальное)и является FormControl.Таким образом, это инкапсулирует текст, который отображается на входе и передается с помощью кнопки отправки.

Теперь каждый раз, когда я набираю или нажимаю на вводе, вызывается функция автозаполнения и выполняется обычный асинхронный вызов с подпиской:

  autocomplete(): void {
    if (this.autocompleteSubscription) {
      this.autocompleteSubscription.unsubscribe();
    }
    this.autocompleteSubscription = this.http.get('url').subscribe(
      (autocompleteList:  Array<OptionItem>) => {
        this.autocompleteList = autocompleteList;
      }
    );
  }

На самом деле у меня нет задержки для вызова aysnc, поэтому каждый раз, когда пользователь печатает, я немедленно вызываю бэкэнд.Но представьте, что пользователь типа «Программное обеспечение», я, конечно, не хочу отображать какие-либо несущественные результаты.Например, мы набрали «Soft», и вызов XHR сработал.Мы продолжаем печатать и печатать «Softw», еще один вызов XHR выполняется до того, как первый вызов завершит свою работу.Так что я фактически немедленно отменил первое выполнение, так как оно больше не актуально.Вот почему каждый раз, когда вызывается функция автозаполнения, я вызываю отписку от предыдущей подписки.

Вопрос: Это способ, которым я должен реализовать свое поведение автозаполнения?Сегодня я видел художественную вещь об одной и той же вещи, но они реализуют это поведение с подробным (или чистым?) Rxjs, с Pipes.это ссылка: https://blog.strongbrew.io/building-a-safe-autocomplete-operator-with-rxjs/

Я также видел другие примеры, представляющие собой каналы, но они также содержат в своем html немного сторонний синтаксис, такой как '{{text $ |async}} '.

Итак, я хотел убедиться, является ли то, что у меня есть сейчас (подписаться и отписаться), хорошим способом достижения того, чего я хочу?Или я должен действительно изменить реализацию и реализовать что-то вроде того, что у них есть?Какая сторона имеет какие плюсы и минусы?

И если я должен переключить свою реализацию, можете ли вы предоставить пример кода с моим formControl в качестве модели?

Заранее спасибо

Редактировать:

У меня теперь есть что-то вроде этого:

  ngOnInit(): void {
    this.parentFormControl.valueChanges
      .pipe(
        debounceTime(1000),
        distinctUntilChanged(),
        filter(value => this.myGetRequestCondition() ? true : false),
        switchMap((value: string) =>  {
            return this.http.get('url' + value)
              .pipe(catchError(err => {
                 this.myErrorFunction(err);
                 return EMPTY;
              }));
        }))
      .subscribe(autocompleteList => {
        this.autocompleteList = autocompleteList;
      });
  }

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Реактивные экземпляры формы, такие как FormGroup и FormControl, имеют метод valueChages, который возвращает observable, который выдает последние значения.Поэтому вы можете подписаться на valueChanges, чтобы обновлять переменные экземпляра или выполнять операции.

Поэтому я хотел быть уверен, является ли то, что у меня есть сейчас (подписаться и отписаться), хорошим способом достижения того, что яхотите?

В вашем подходе нет ничего плохого, но работа с observable позволит вам использовать RxJs операторов, которые облегчают вашу работу, когда дело доходит до сопровождения, тестирования и прочего.

valueChanges возвращает observable, небо в значительной степени ограничивает то, что вы можете делать со значениями, которые испускаются .

Обработка ошибок.

Всякий раз, когда значение formControl Изменения switchMap() создает совершенно новый ajax observable.if вы получаете 404 с вашего сервера, ошибка являетсяраспространяется от внутренней к внешней Цепи и умирает весь поток.

Чтобы сохранить его в живых, вам необходимо защитить главную цепь observer.и чтобы изолировать основную цепочку наблюдателей, используйте оператор catchError() с switchMap ().

switchMap() не волнует, завершен ли внутренний Observable, он завершится только после завершения внешнего Observable.Несмотря на то, что внутренняя цепочка Observable умирает, внешняя цепочка Observable остается в живых, потому что обработчик ошибок не был вызван для внешней цепочки Observable.

Кроме этого, если ваш входной элемент управления заполнен и снова очищен, formcontrol заберет изменения иотправив запрос, даже если он пуст, вы можете воспользоваться оператором filter, который генерирует значения, соответствующие заданному условию, и distinctUntilChanged, который испускается, только если текущее значение отличается от последнего.

Вот решение.:

  ngOnInit(): void {
        this.parentFormControl.valueChanges
          .pipe(
            debounceTime(1000),
            distinctUntilChanged(),
            filter(value=>value? true: false),
            switchMap((value)=>this.http.get('url' + value).pipe(catchError(() => {return empty()}))
            ))
          .subscribe(autocompleteList => {
            this.autocomplete= autocompleteList;
          });
      }

Live Demo

0 голосов
/ 04 октября 2018

Если вы используете ReactiveForms, то лучший способ - наблюдать за изменением значений formcontrol.

this.parentFormControl.valueChanges.pipe(
      switchMap((value) => this.http.get('url').pipe(catchError(err => of([]))))
    ).subscribe(autocompleteList => this.autocompleteList = autocompleteList);

, когда исходная Наблюдаемая испускает switchMap, отменяет любые предыдущие подписки внутренней Наблюдаемой.Всякий раз, когда пользователь вводит новое значение, switchMap отменяет свою внутреннюю подписку (в данном случае, сетевой вызов).

Согласно вашему новому коду это будет решение.

ngOnInit(): void {
    this.parentFormControl.valueChanges
      .pipe(
        debounceTime(1000),
        switchMap((value: string) =>  {
          if (this.condition()) {
            return this.http.get('url' + value).pipe(catchError(err => of([])));
          }
          return of([]); // or of(this.autocompleteList)
        }))
      .subscribe(autocompleteList => {
        this.autocompleteList = autocompleteList;
      });
  }
...