title: Angular Material - Custom Autocomplete component
body: <p>I'm trying to create my own custom angular material component that would be able to work with a <code>mat-form-field</code> control. </p>
  
  <p>Added to that, I'd like the control to use the <code>mat-autocomplete</code> directive.</p>
  
  <p>My aim is simply to create a better-looking <code>mat-autocomplete</code> component with an integrated <strong>clear-button</strong> and custom <strong>css arrow</strong> 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.</p>
  
  <p><a href="https://i.stack.imgur.com/nhW79.png" rel="noreferrer"><img src="https://i.stack.imgur.com/nhW79.png" alt="material component aim"></a></p>
  
  <p>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 :</p>
  
  <ul>
  <li><a href="https://material.angular.io/guide/creating-a-custom-form-field-control" rel="noreferrer">Angular Material - Create custom form field control</a></li>
  <li><a href="https://stackoverflow.com/questions/47020051/angular-material-2-custom-component-with-ng-value-accessor">Stack Overflow - custom component with ng value accessor
  </a></li>
  </ul>
  
  <p>I am currently facing several problems which I believe are linked : </p>
  
  <ul>
  <li><strong>My form is not valid even when the value is selected correctly.</strong></li>
  <li><strong>The placeholder is not setting itself correctly after an option is selected.</strong></li>
  <li><strong>The auto-complete filter option doesn't work at all</strong></li>
  <li><strong>The focus does not trigger correctly if I don't click on the input specifically.</strong></li>
  </ul>
  
  <p>I believe my first three issues are caused by the auto-complete value that is not linked to my reactive form correctly.</p>
  
  <hr>
  
  <p>Here is a direct link to a personnal public repository with the project (since the issue is a bit big to be displayed here) : <a href="https://github.com/Tenmak/material" rel="noreferrer">Git Repository : https://github.com/Tenmak/material</a>.</p>
  
  <hr>
  
  <p>Basically, the idea is to transform this : </p>
  
  <pre><code>  &lt;mat-form-field&gt;
      &lt;div fxLayout="row"&gt;
        &lt;input matInput placeholder="Thématique" [matAutocomplete]="thematicAutoComplete" formControlName="thematique" tabindex="1"&gt;
  
        &lt;div class="mat-select-arrow-wrapper"&gt;
          &lt;div class="mat-select-arrow" [ngClass]="{'mat-select-arrow-down': !thematicAutoComplete.isOpen, 'mat-select-arrow-up': thematicAutoComplete.isOpen}"&gt;&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;button mat-button *ngIf="formDossier.get('thematique').value" matSuffix mat-icon-button aria-label="Clear" (click)="formDossier.get('thematique').setValue('')"&gt;
        &lt;mat-icon&gt;close&lt;/mat-icon&gt;
      &lt;/button&gt;
  
      &lt;mat-hint class="material-hint-error" *ngIf="!formDossier.get('thematique').hasError('required') &amp;&amp; formDossier.get('thematique').touched &amp;&amp; formDossier.get('thematique').hasError('thematiqueNotFound')"&gt;
        &lt;strong&gt;
          Veuillez sélectionner un des choix parmi les options possibles.
        &lt;/strong&gt;
      &lt;/mat-hint&gt;
    &lt;/mat-form-field&gt;
  
    &lt;mat-autocomplete #thematicAutoComplete="matAutocomplete" [displayWith]="displayThematique"&gt;
      &lt;mat-option *ngFor="let thematique of filteredThematiques | async" [value]="thematique"&gt;
        &lt;span&gt; {{thematique.code}} &lt;/span&gt;
        &lt;span&gt; - &lt;/span&gt;
        &lt;span&gt; {{thematique.libelle}} &lt;/span&gt;
      &lt;/mat-option&gt;
    &lt;/mat-autocomplete&gt;
  </code></pre>
  
  <p>into this :</p>
  
  <pre><code>  &lt;mat-form-field&gt;
      &lt;siga-auto-complete placeholder="Thématique" [tabIndex]="1" [autoCompleteControl]="thematicAutoComplete" formControlName="thematique"&gt;
      &lt;/siga-auto-complete&gt;
  
      &lt;mat-hint class="material-hint-error" *ngIf="!formDossier.get('thematique').hasError('required') &amp;&amp; formDossier.get('thematique').touched &amp;&amp; formDossier.get('thematique').hasError('thematiqueNotFound')"&gt;
        &lt;strong&gt;
          Veuillez sélectionner un des choix parmi les options possibles.
        &lt;/strong&gt;
      &lt;/mat-hint&gt;
    &lt;/mat-form-field&gt;
  
    &lt;mat-autocomplete #thematicAutoComplete="matAutocomplete" [displayWith]="displayThematique"&gt;
      &lt;mat-option *ngFor="let thematique of filteredThematiques | async" [value]="thematique"&gt;
        &lt;span&gt; {{thematique.code}} &lt;/span&gt;
        &lt;span&gt; - &lt;/span&gt;
        &lt;span&gt; {{thematique.libelle}} &lt;/span&gt;
      &lt;/mat-option&gt;
    &lt;/mat-autocomplete&gt;
  </code></pre>
  
  <p>I'm currently working in the "dossiers" folder which displays my initial reactive form. And I'm using my custom component <code>autocomplete.component.ts</code> inside this form directly to replace the first field.</p>
  
  <p>Here is my attempt at the code of the generic component (<strong>simplified</strong>): </p>
  
  <pre><code>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(() =&gt; SigaAutoCompleteComponent),
              multi: true
          }
      ],
  })
  export class SigaAutoCompleteComponent implements MatFormFieldControl&lt;AutoCompleteInput&gt;, 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) =&gt; {
                  this.focused = !!origin;
                  this.stateChanges.next();
              })
          );
  
          this.parts = fb.group({
              'singleValue': '',
          });
  
          this.subs.push(this.parts.valueChanges.subscribe((value: string) =&gt; {
              this.propagateChange(value);
          }));
      }
  
      ngAfterViewInit() {
          // Wrong approach but some idea ?
          console.log(this.autoCompleteControl);
          this.autoCompleteControl.optionSelected.subscribe((event: MatAutocompleteSelectedEvent) =&gt; {
              console.log(event.option.value);
              this.value = event.option.value;
          })
      }
  
      ngOnDestroy() {
          this.stateChanges.complete();
          this.subs.forEach(s =&gt; s.unsubscribe());
          this.fm.stopMonitoring(this.elRef.nativeElement);
      }
  
      ...
  
      // CONTROL VALUE ACCESSOR
      private propagateChange = (_: any) =&gt; { };
  
      public writeValue(a: string) {
          console.log('wtf');
  
          if (a &amp;&amp; a !== '') {
              console.log('value =&gt; ', 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;
      }
  }
  </code></pre>