web-dev-qa-db-fra.com

Angular Material - Composant de saisie semi-automatique personnalisé

J'essaie de créer mon propre composant matériel angular angulaire qui pourrait fonctionner avec un contrôle mat-form-field.

De plus, j'aimerais que le contrôle utilise la directive mat-autocomplete.

Mon objectif est simplement de créer un composant mat-autocomplete Plus beau avec un bouton d'effacement intégré et personnalisé flèche css comme l'image suivante. Je l'ai obtenu avec succès en utilisant le composant standard et ajouté ce que je voulais mais maintenant je veux l'exporter dans un composant générique.

material component aim

J'utilise la documentation officielle angular pour créer mon propre contrôle de champ de formulaire et aussi un autre SO article à ce sujet qui m'a déjà beaucoup aidé:

Je suis actuellement confronté à plusieurs problèmes qui, je pense, sont liés:

  • Mon formulaire n'est pas valide même lorsque la valeur est sélectionnée correctement.
  • L'espace réservé ne se définit pas correctement après la sélection d'une option.
  • L'option de filtre de saisie automatique ne fonctionne pas du tout
  • Le focus ne se déclenche pas correctement si je ne clique pas spécifiquement sur l'entrée.

Je crois que mes trois premiers problèmes sont causés par la valeur de saisie semi-automatique qui n'est pas liée correctement à ma forme réactive.


Voici un lien direct vers un dépôt public personnel avec le projet (car le problème est un peu gros pour être affiché ici): Git Repository: https://github.com/Tenmak/material .


Fondamentalement, l'idée est de transformer cela:

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

en cela:

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

Je travaille actuellement dans le dossier "dossiers" qui affiche ma forme réactive initiale. Et j'utilise mon composant personnalisé autocomplete.component.ts Dans ce formulaire directement pour remplacer le premier champ.

Voici ma tentative sur le code du composant générique ( simplifié ):

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

Enfin résolu !!!

enter image description here

  1. Le problème ici est que lors de la création du champ d'entrée dans l'enfant [SigaAutoCompleteComponent], le parent doit connaître la valeur remplie dans l'enfant [CreateDossierComponent], cette partie est manquante, c'est la raison pour laquelle elle ne peut pas devenir valide car elle pense que l'entrée champ n'est pas touché [reste invalide] - résolu en émettant la valeur au parent puis en manipulant le contrôle de formulaire selon les besoins.
  2. La décomposition du champ mat-form-field et de l'entrée a provoqué le problème - résolu en déplaçant l'élément mat-form-field vers l'enfant, l'autre code reste intact et cela résout à la fois l'espace réservé se chevauchant et en cliquant sur l'icône de flèche pour afficher
  3. Cela peut être fait - [une façon de le faire en repensant], en injectant le service dans le composant enfant et en faisant la fonctionnalité de saisie semi-automatique là-bas [je n'ai pas implémenté cela, mais cela fonctionnera car il s'agit simplement d'une copie du champ du département]

    dans 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>
    

    dans 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>
    

    dans 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();
    
      }
    }
    

    cela résoudra tous les problèmes, s'il vous plaît laissez-moi savoir si vous voulez que je pousse vers github .. J'espère que cela aide !!!! Merci !!

    MISE À JOUR: Saisie automatique, mat-hint maintenant tout fonctionne ..

    Je comprends que vous ne voulez pas que l'entrée et le champ mat-form soient ensemble

    mais si c'est juste pour que mat-hint s'affiche dynamiquement [qui dépend des valeurs de formcontrol], nous pouvons passer le formcontrol du parent à l'enfant, ce qui élimine même la nécessité d'émettre la valeur d'entrée de l'enfant au parent en définissant la valeur dans le composant parent et [le champ mat-hint reste dans le composant parent lui-même]

    enter image description here

15
Ampati Hareesh