I'm trying to create my own custom angular material component that would be able to work with a mat-form-field
control.
Added to that, I'd like the control to use the mat-autocomplete
directive.
My aim is simply to create a better-looking mat-autocomplete
component with an integrated clear-button and custom css arrow like the following image. I have succesfully obtained it by using the standard component and added what I wanted but now I want to export it into a generic component.
I'm using the official angular material documentation to create my own form field control and also another SO post about it which already helped me a lot :
I am currently facing several problems which I believe are linked :
I believe my first three issues are caused by the auto-complete value that is not linked to my reactive form correctly.
Here is a direct link to a personnal public repository with the project (since the issue is a bit big to be displayed here) : Git Repository : https://github.com/Tenmak/material.
Basically, the idea is to transform this :
<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>
into this :
<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>
I'm currently working in the "dossiers" folder which displays my initial reactive form. And I'm using my custom component autocomplete.component.ts
inside this form directly to replace the first field.
Here is my attempt at the code of the generic component (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;
}
}
Simple autocompleteStart by creating the autocomplete panel and the options displayed inside it. Each option should be defined by a mat-option tag. Set each option's value property to whatever you'd like the value of the text input to be when that option is selected.
The <mat-autocomplete>, an Angular Directive, is used as a special input control with an inbuilt dropdown to show all possible matches to a custom query. This control acts as a real-time suggestion box as soon as the user types in the input area.
Finally solved !!!
This can be done-[one way of doing by redesigning] , by injecting the service into child component and doing the autocomplete feature over there [i haven't implemented this, but this will work as it just copy of department field]
in 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>
in 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>
in 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();
}
}
this will resolve all the issues, Please let me know if you want me to push to github .. Hope this helps !!!! Thanks !!
UPDATE: Auto complete , mat-hint now everything is working..
I understand that you don't want input and mat-form-field to be together
but if it is just for mat-hint to show dynamically[which is depending on formcontrol values], we can pass the formcontrol from parent to child which even eliminates the necessity of emitting the input value from child to parent setting the value in parent component and [mat-hint field stays in the parent component itself]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With