Правильное использование flatMap — будут ли mergeMap или concatMap более эффективными

#angular #rxjs

#angular #rxjs

Вопрос:

как я уже обсуждал в прошлом, я новичок в программировании и пытаюсь научиться Angular — возможно, это приведет к новой карьере! Я создаю приложение для себя, чтобы разобраться с веб-разработкой. В настоящее время я пытаюсь использовать автозаполнение материала в форме, чтобы, когда пользователь добавляет / вставляет значение в текстовый ввод, событие keyup также прослушивалось в observable, который вызывает API, который возвращает некоторые данные, и я использую возвращенную дату для заполнения автозаполнения (у меня есть 3 на моей странице). Это моя HTML-форма…

 <form novalidate [formGroup]="assignmentForm">
      <div>
        <input type="text" matInput placeholder="User" formControlName="worker" name="worker" [matAutocomplete]="workerTemplate" #worker>
        <mat-autocomplete #workerTemplate="matAutocomplete">
          <mat-option *ngFor="let worker of workerTags" [value]="worker">{{ worker.displayName}}</mat-option>
        </mat-autocomplete>
      </div>

      <div>
        <input type="text" matInput placeholder="Company" formControlName="company" name="company" [matAutocomplete]="companyTemplate" #company>
        <mat-autocomplete #companyTemplate="matAutocomplete">
          <mat-option *ngFor="let company of companyTags" [value]="company">{{company.displayName}}</mat-option>
        </mat-autocomplete>
      </div>

      <div>
        <input type="text" matInput placeholder="Department" formControlName="department" name="department" [matAutocomplete]="departmentTemplate" #department>
        <mat-autocomplete #departmentTemplate="matAutocomplete">
          <mat-option *ngFor="let department of departmentTags" [value]="department">{{department.displayName}}</mat-option>
        </mat-autocomplete>
      </div>
    </form>
  

Теперь в моем компоненте я использую Observable.merge для прослушивания всех трех входных данных, я отключаю, чтобы пользователь не перегружал систему, я вызываю свой API и затем выполняю некоторую логику форматирования перед заполнением соответствующего массива данных для соответствующего автозаполнения. Вот код моего компонента (я сократил его для удобства чтения)

 public companyTags: any[];
public departmentTags: any[];
public workerTags: any[];

@ViewChild('company')
private companyEl: ElementRef;
@ViewChild('department')
private departmentEl: ElementRef;
@ViewChild('worker')
private workerEl: ElementRef;
private assignmentSubscription: Subscription;

constructor(private apiService: ApiService) {}

public ngOnInit() {
  const companySource = fromEvent(this.companyEl.nativeElement, 'keyup');
  const departmentSource = fromEvent(this.departmentEl.nativeElement, 'keyup');
  const workerSource = fromEvent(this.workerEl.nativeElement, 'keyup');

  const tagsSource = merge(companySource, departmentSource, workerSource)
    .pipe(
      debounceTime(500),
      distinctUntilChanged(),
      flatMap((ev: KeyboardEvent) => {
        // if the user presses backspace the value is "" and all results are returned (to set limit)
        if ((<HTMLInputElement>ev.target).value !== '') {
          return this.apiService.getTags((<HTMLInputElement>ev.target).name, (<HTMLInputElement>ev.target).value, 3)
        }

        return of([]);
      }),
    );

  this.assignmentSubscription = tagsSource.subscribe((res) => {
    this.clearAllTags();
    if (res.length > 0) {
      // the type is contained in the array so we can determine which array we need to populate
      // we can use interpolation rather than a horrible if then else
      this[`${res[0].type}Tags`] = res;
    }
  });
}

public clearAllTags(): void {
  this.companyTags = null;
  this.departmentTags = null;
  this.workerTags = null;
}
  

Все это работает, но мне интересно, является ли это наиболее эффективным способом сделать это? Я немного прочитал о flatMap, mergeMaop и concatMap, и я не уверен, какой метод лучше использовать в моем случае? Также я должен поместить логику, содержащуюся в flatMap, куда-нибудь еще, поскольку кажется, что это неправильное место? Я не уверен, как бы я вообще это сделал, используя цепочку или добавив другой метод в канал (.do?). Любые советы и соображения будут оценены. Если я не понимаю смысла или неправильно формулирую свой вопрос, пожалуйста, укажите это, и я перепишу / отредактирую. Заранее большое спасибо.

Ответ №1:

На самом деле вам следует использовать switchMap вместо either concatMap или flatMap , flatMap будет поддерживать работу внутренней observable и выдавать значение, даже если из исходного ввода поступает новое значение, в вашем случае ввод с клавиатуры. Таким образом, то, что пользователь в конечном итоге увидит на экране, будет отображать его последний результат ввода последний результат.

например, когда вы пытаетесь выполнить поиск ‘apple’, вы получаете ‘app’ из вашего исходного ввода, затем api выполнит поиск ‘app’, но непосредственно перед возвратом api вы ввели ‘le’ и теперь должны вернуть результат для ‘apple’. Если в этом случае используется flatMap, сначала отобразится результат для ‘app’, а затем быстро изменится на ‘apple’. В то время как switchMap используется, поисковый запрос api ‘app’ будет пропущен (на самом деле не отмена, но не выдача значения) и вместо этого будет возвращен поиск ‘apple’.