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.
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:
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;
}
}
Enfin résolu !!!
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]