web-dev-qa-db-ja.com

Angular Material-カスタムオートコンプリートコンポーネント

私は、独自のカスタムangular mat-form-fieldコントロールで動作できる素材コンポーネントを作成しようとしています。

それに加えて、コントロールでmat-autocompleteディレクティブを使用したいと思います。

私の目的は、統合されたclear-buttonとカスタムcss矢印で見栄えの良いmat-autocompleteコンポーネントを作成することです。次の画像のように。標準コンポーネントを使用して正常に取得し、必要なものを追加しましたが、今は汎用コンポーネントにエクスポートします。

material component aim

私は公式のangularマテリアルドキュメントを使用して、独自のフォームフィールドコントロールを作成し、別のSO

私は現在、私がリンクしていると思ういくつかの問題に直面しています:

  • 値が正しく選択されていても、フォームは無効です。
  • オプションが選択された後、プレースホルダーはそれ自体を正しく設定しません。
  • オートコンプリートフィルターオプションがまったく機能しません
  • 入力をクリックしないと、フォーカスが正しくトリガーされません。

最初の3つの問題は、リアクティブフォームに正しくリンクされていないオートコンプリート値が原因であると考えています。


ここに、プロジェクトの個人公開リポジトリへの直接リンクがあります(ここでは問題が少し大きいため): Gitリポジトリ:https://github.com/Tenmak/material


基本的に、アイデアはこれを変換することです:

  <mat-form-field>
    <div fxLayout="row">
      <input matInput placeholder="Thématique" [matAutocomplete]="thematicAutoComplete" formControlName="thematique" tabindex="1">

      <div class="mat-select-arrow-wrapper">
        <div class="mat-select-arrow" [ngClass]="{'mat-select-arrow-down': !thematicAutoComplete.isOpen, 'mat-select-arrow-up': thematicAutoComplete.isOpen}"></div>
      </div>
    </div>
    <button mat-button *ngIf="formDossier.get('thematique').value" matSuffix mat-icon-button aria-label="Clear" (click)="formDossier.get('thematique').setValue('')">
      <mat-icon>close</mat-icon>
    </button>

    <mat-hint class="material-hint-error" *ngIf="!formDossier.get('thematique').hasError('required') && formDossier.get('thematique').touched && formDossier.get('thematique').hasError('thematiqueNotFound')">
      <strong>
        Veuillez sélectionner un des choix parmi les options possibles.
      </strong>
    </mat-hint>
  </mat-form-field>

  <mat-autocomplete #thematicAutoComplete="matAutocomplete" [displayWith]="displayThematique">
    <mat-option *ngFor="let thematique of filteredThematiques | async" [value]="thematique">
      <span> {{thematique.code}} </span>
      <span> - </span>
      <span> {{thematique.libelle}} </span>
    </mat-option>
  </mat-autocomplete>

これに:

  <mat-form-field>
    <siga-auto-complete placeholder="Thématique" [tabIndex]="1" [autoCompleteControl]="thematicAutoComplete" formControlName="thematique">
    </siga-auto-complete>

    <mat-hint class="material-hint-error" *ngIf="!formDossier.get('thematique').hasError('required') && formDossier.get('thematique').touched && formDossier.get('thematique').hasError('thematiqueNotFound')">
      <strong>
        Veuillez sélectionner un des choix parmi les options possibles.
      </strong>
    </mat-hint>
  </mat-form-field>

  <mat-autocomplete #thematicAutoComplete="matAutocomplete" [displayWith]="displayThematique">
    <mat-option *ngFor="let thematique of filteredThematiques | async" [value]="thematique">
      <span> {{thematique.code}} </span>
      <span> - </span>
      <span> {{thematique.libelle}} </span>
    </mat-option>
  </mat-autocomplete>

現在、最初のリアクティブフォームを表示する「関係書類」フォルダで作業しています。そして、このフォーム内でカスタムコンポーネントautocomplete.component.tsを直接使用して、最初のフィールドを置き換えています。

汎用コンポーネント(simplified)のコードでの私の試みは次のとおりです。

class AutoCompleteInput {
    constructor(public testValue: string) {
    }
}

@Component({
    selector: 'siga-auto-complete',
    templateUrl: './autocomplete.component.html',
    styleUrls: ['./autocomplete.component.scss'],
    providers: [
        {
            provide: MatFormFieldControl,
            useExisting: SigaAutoCompleteComponent
        },
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => SigaAutoCompleteComponent),
            multi: true
        }
    ],
})
export class SigaAutoCompleteComponent implements MatFormFieldControl<AutoCompleteInput>, AfterViewInit, OnDestroy, ControlValueAccessor {
    ...
    parts: FormGroup;
    ngControl = null;

    ...

    @Input()
    get value(): AutoCompleteInput | null {
        const n = this.parts.value as AutoCompleteInput;
        if (n.testValue) {
            return new AutoCompleteInput(n.testValue);
        }
        return null;
    }
    set value(value: AutoCompleteInput | null) {
        // Should set the value in the form through this.writeValue() ??
        console.log(value);
        this.writeValue(value.testValue);
        this.stateChanges.next();
    }

    @Input()
    set formControlName(formName) {
        this._formControlName = formName;
    }
    private _formControlName: string;

    // ADDITIONNAL
    @Input() autoCompleteControl: MatAutocomplete;
    @Input() tabIndex: string;

    private subs: Subscription[] = [];

    constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef) {
        this.subs.Push(
            fm.monitor(elRef.nativeElement, true).subscribe((Origin) => {
                this.focused = !!Origin;
                this.stateChanges.next();
            })
        );

        this.parts = fb.group({
            'singleValue': '',
        });

        this.subs.Push(this.parts.valueChanges.subscribe((value: string) => {
            this.propagateChange(value);
        }));
    }

    ngAfterViewInit() {
        // Wrong approach but some idea ?
        console.log(this.autoCompleteControl);
        this.autoCompleteControl.optionSelected.subscribe((event: MatAutocompleteSelectedEvent) => {
            console.log(event.option.value);
            this.value = event.option.value;
        })
    }

    ngOnDestroy() {
        this.stateChanges.complete();
        this.subs.forEach(s => s.unsubscribe());
        this.fm.stopMonitoring(this.elRef.nativeElement);
    }

    ...

    // CONTROL VALUE ACCESSOR
    private propagateChange = (_: any) => { };

    public writeValue(a: string) {
        console.log('wtf');

        if (a && a !== '') {
            console.log('value => ', a);
            this.parts.setValue({
                'singleValue': a
            });
        }
    }
    public registerOnChange(fn: any) {
        this.propagateChange = fn;
    }

    public registerOnTouched(fn: any): void {
        return;
    }

    public setDisabledState?(isDisabled: boolean): void {
        this.disabled = isDisabled;
    }
}
13
Alex Beugnet

最後に解決しました!!!

enter image description here

  1. ここでの問題は、子[SigaAutoCompleteComponent]に入力フィールドを作成するとき、親は子[CreateDossierComponent]に入力された値を知っている必要があることです。フィールドに触れない[無効のまま]-値を親に送信し、必要に応じてフォームコントロールを操作することで解決します。
  2. Mat-form-fieldと入力をつまむことで問題が発生しました-mat-form-field要素をchildに移動することで解決し、他のコードはそのまま残り、これにより、プレースホルダーが重複し、矢印アイコンをクリックして表示されます
  3. これは、[再設計による1つの方法]で実行できます。サービスを子コンポーネントに挿入し、オートコンプリート機能を実行します[これは実装していませんが、部門フィールドのコピーとして機能します]

    create-doiser.component.htmlで

          <!-- </div> -->
          <mat-autocomplete #thematicAutoComplete="matAutocomplete" [displayWith]="displayThematique">
            <mat-option *ngFor="let thematique of filteredThematiques | async" [value]="thematique">
              <span> {{thematique.code}} </span>
              <span> - </span>
              <span> {{thematique.libelle}} </span>
            </mat-option>
          </mat-autocomplete>
    

    autocomplete.component.html内

    <mat-form-field style="display:block;transition:none ">
    <div fxLayout="row">
      <input  matInput   placeholder="Thématique" [matAutocomplete]="autoCompleteControl" (optionSelected)="test($event)" tabindex="{{tabIndex}}">
      <div class="mat-select-arrow-wrapper" (click)="focus()">
        <div class="mat-select-arrow" [ngClass]="{'mat-select-arrow-down': !autoCompleteControl.isOpen, 'mat-select-arrow-up': autoCompleteControl.isOpen}"></div>
      </div>
    </div>
    
    </mat-form-field>
    

    autocomplete.component.ts内

    in set value emit the value to parent
    this.em.emit(value);
    

    create-dosier.component.ts

      this.thematique = new FormControl( ['', [Validators.required, this.thematiqueValidator]]
    
        ); 
    
    this.formDossier.addControl('thematique',this.thematique);
    call(event){
    
        this.thematique.setValue(event);
        this.thematique.validator=this.thematiqueValidator();
        this.thematique.markAsTouched();
        this.thematique.markAsDirty();
    
      }
    }
    

    これですべての問題が解決します。githubにプッシュしてほしい場合はお知らせください。ありがとう!!

    UPDATE:オートコンプリート、mat-h​​intはすべて正常に動作しています。

    入力とmat-form-fieldを一緒にしたくないことを理解しています

    しかし、mat-h​​intが動的に表示される場合(フォームコントロールの値に依存)、フォームコントロールを親から子に渡すことができます。これにより、子から親に入力値を送信する必要がなくなり、親コンポーネントの値を設定できますおよび[mat-h​​intフィールドは親コンポーネント自体にとどまります]

    enter image description here

15
Ampati Hareesh