Угловой пользовательский компонент ngx-mat-select-search - PullRequest
2 голосов
/ 11 июня 2019

Я пытаюсь использовать компонент ngx-mat-select-search, чтобы поместить в моем приложении раскрывающееся меню стиля mat-select с панелью поиска. https://www.npmjs.com/package/ngx-mat-select-search

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

Пока у меня есть это: site-dropdown-component.ts

import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {FormControl} from '@angular/forms';
import {ReplaySubject, Subject} from 'rxjs';
import {MatSelect} from '@angular/material';
import {take, takeUntil} from 'rxjs/operators';

@Component({
  selector: 'app-site-dropdown',
  template: `
    <mat-form-field class="w-100">
      <mat-select [formControl]="siteCtrl" placeholder="Site" #singleSelect>
        <mat-option>
          <ngx-mat-select-search [formControl]="siteFilterCtrl" [placeholderLabel]="'Search Sites...'"></ngx-mat-select-search>
        </mat-option>
        <mat-option *ngFor="let site of filteredSites | async" [value]="site">{{site.name}}</mat-option>
      </mat-select>
    </mat-form-field>
  `
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit {
  /** list of sites */
  protected sites: Site[] = SITES;

  /** control for the selected site */
  public siteCtrl: FormControl = new FormControl();

  /** control for the MatSelect filter keyword */
  public siteFilterCtrl: FormControl = new FormControl();

  /** list of sites filtered by search keyword */
  public filteredSites: ReplaySubject<Site[]> = new ReplaySubject<Site[]>(1);

  @ViewChild('singleSelect') singleSelect: MatSelect;

  /** Subject that emits when the component has been destroyed. */
  protected onDestroy = new Subject<void>();
  constructor() { }

  ngOnInit(): void {
    // set initial selection
    this.siteCtrl.setValue(this.sites);
    // load the initial site list
    this.filteredSites.next(this.sites.slice());
    // listen for search field value changes
    this.siteFilterCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(() => {
        this.filterSites();
      });
  }

  ngAfterViewInit(): void {
    this.setInitialValue();
  }

  ngOnDestroy(): void {
    this.onDestroy.next();
    this.onDestroy.complete();
  }

  /**
   * Sets the initial value after the filteredBanks are loaded initially
   */
  protected setInitialValue() {
    this.filteredSites
      .pipe(take(1), takeUntil(this.onDestroy))
      .subscribe(() => {
        // setting the compareWith property to a comparison function
        // triggers initializing the selection according to the initial value of
        // the form control (i.e. _initializeSelection())
        // this needs to be done after the filteredBanks are loaded initially
        // and after the mat-option elements are available
        this.singleSelect.compareWith = (a: Site, b: Site) => a && b && a.id === b.id;
      });
  }

  protected filterSites() {
    if (!this.sites) {
      return;
    }
    // get the search keyword
    let search = this.siteFilterCtrl.value;
    if (!search) {
      this.filteredSites.next(this.sites.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    // filter the sites
    this.filteredSites.next(
      this.sites.filter(site => site.name.toLowerCase().indexOf(search) > -1)
    );
  }
}


export interface Site {
  id: string;
  name: string;
}

export const SITES: Site[] = [
  {id: 'site1', name: 'Site 1'},
  {id: 'site2', name: 'Site 2'},
  {id: 'site3', name: 'Site 3'},
];

Для компонента, в котором я пытаюсь его использовать, у меня есть:

<app-site-dropdown formControlName="site"></app-site-dropdown>

А внутри класса компонентов у меня есть форма:

this.mySearchForm = this.formBuilder.group( {
  site: []
}); 

Я могу видеть выпадающий список и взаимодействовать с ним очень хорошо, но когда я отправляю свою форму, я не могу получить значение выбранного параметра. Просто всегда возвращается null, когда я пытаюсь mySearchForm.controls['site'].value

Чего мне не хватает, чтобы иметь возможность добавить свой пользовательский выпадающий компонент и получить его значение при отправке формы?

UPDATE:

Мне удалось заставить его работать, выполнив следующие действия:

Внутри site-dropdown.component.ts, я изменил

protected siteCtrl: FormControl;

до

@Input() siteCtrl: FormControl;

И внутри моего html, используя пользовательский выпадающий список, я добавил:

<app-site-dropdown [siteCtrl]="myForm.get('site')"></app-site-dropdown>

Это позволило мне сохранить выбранное значение в моей форме при отправке.

1 Ответ

2 голосов
/ 11 июня 2019

вы можете получить значение выбранной опции, если ваш SiteDropdownComponent реализует интерфейс ControlValueAccessor следующим образом, в результате чего ваш SiteDropdownComponent ведет себя как элемент управления формы и позволяет получить доступ к значению, например, с помощью. <app-site-dropdown formControlName="site"></app-site-dropdown>

...
import { forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-site-dropdown',
  template: ...
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SiteDropdownComponent),
      multi: true
    }
  ],
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor {
  ...

  onChange: Function = (_: any) => {};
  onTouched: Function = (_: any) => {};

  constructor() { }

  ngOnInit() {
    ...
    // call this.onChange to notify the parent component that the value has changed
    this.siteCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(value => this.onChange(value))
  }

  writeValue(value: string) {
    // set the value of siteCtrl when the value is set from outside the component
    this.siteCtrl.setValue(value);
  }

  registerOnChange(fn: Function) {
    this.onChange = fn;
  }

  registerOnTouched(fn: Function) {
    this.onTouched = fn;
  }

}

См. Например https://github.com/bithost-gmbh/ngx-mat-select-search/blob/d7ea78d511bbec45143c58c855f013a44d0d5055/src/app/mat-select-search/mat-select-search.component.ts#L134

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...