Угловой материал - компонент пользовательского автозаполнения

Я пытаюсь создать свой собственный компонент углового материала, который мог бы работать с контролем mat-form-field.

Добавив к этому, я бы хотел, чтобы элемент управления использовал директиву mat-autocomplete.

Моя цель состоит в том, чтобы создать более mat-autocomplete компонент mat-autocomplete со встроенной кнопкой clear и кнопкой css, как показано на следующем рисунке. Я успешно получил его, используя стандартный компонент и добавил то, что хотел, но теперь хочу экспортировать его в общий компонент.

material component aim

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

В настоящее время я сталкиваюсь с рядом проблем, которые, как я считаю, связаны:

  • Моя форма недействительна, даже если значение выбрано правильно.
  • Заполнитель не настроен правильно после выбора опции.
  • Опция автозаполнения фильтра не работает вообще
  • Фокусировка не срабатывает правильно, если я не нажимаю на вход специально.

Я считаю, что мои первые три проблемы вызваны значением автозаполнения, которое не связано с моей реактивной формой правильно.


Вот прямая ссылка на персонализированный публичный репозиторий с проектом (так как проблема немного велика для отображения здесь): Git Repository: 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 внутри этой формы, чтобы заменить первое поле.

Вот моя попытка кода обобщенного компонента (упрощенного):

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;
    }
}

Ответ 1

Наконец-то решил !!!

enter image description here

  1. Проблема здесь в том, что при создании поля ввода в дочернем элементе [SigaAutoCompleteComponent] родитель должен знать о значении, заполненном дочерним элементом [CreateDossierComponent], эта часть отсутствует, что является причиной, по которой она не может перейти в действительную, поскольку она считает, что вход поле не коснулось [остается недействительным] - решается путем испускания значения родительскому элементу, а затем при необходимости управляет элементом управления формой.
  2. Выделение поля формы мата и ввода вызвало проблему - решалось путем перемещения элемента mat-form-field к дочернему элементу, другой код остается нетронутым, и это решает как перекрытие заполнителя, так и щелчок на значке стрелки для отображения
  3. Это может быть done- [один способ выполнения путем перепроектирования], путем ввода услуги в дочерний компонент и выполнения функции автозаполнения там [я этого не реализовал, но это будет работать, как только копирование поля отдела)

    в 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);
    

    создание-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-hint для динамического отображения [который зависит от значений formcontrol], мы можем передать formcontrol от родителя к дочернему, что даже исключает необходимость испускать входное значение от дочернего к родительскому, задавая значение в родительском компоненте и [поле mat-hint остается в самом родительском компоненте]

    enter image description here