Search code examples
angularautocompleteangular-materialng-templateng-container

ngTemplateOutlet inside angular materials autocomplete only renders with default option


I am trying to create a set of custom templates using materials autocomplete and ngTemplateOutlet to switch in which template I want to use to display the results (e.g. if there are grouped results they will display differently to traditional autocomplete options).

ngTemplateOutlet seems to render the template only if there is a default option provided. It seems unable to attach the options directly to the mat-autocomplete without at least one mat-option existing. Ideally, the only options being rendered would be the ones fetched from the component (in a more complicated example, the following code is simple and still replicates the problem). Essentially, I'm looking for a workaround.

I have tried every variation I can think of in terms of ng-container and template.

For example, with a template of

<ng-template #default let-options="options">
  <mat-option *ngFor="let option of options" [value]="option">
  {{option}}
  </mat-option>
</ng-template>

This doesn't render any autocomplete options

<mat-autocomplete #auto="matAutocomplete">
    <ng-container *ngTemplateOutlet="default; context:{options: options}"></ng-container>
</mat-autocomplete>

But, with the inclusion of at least one mat-option, this renders all of them

<mat-autocomplete #auto="matAutocomplete">
    <mat-option>Test</mat-option>
    <ng-container *ngTemplateOutlet="default; context:{options: options}"></ng-container>
</mat-autocomplete>

Here's a stackblitz: https://stackblitz.com/edit/angular-bz45ae?file=app/autocomplete-simple-example.html


Solution

  • This is related to the mat-autocomplete behavior. It uses @ContentChildren to access the QueryList of MatOption's. mat-autocomplete also has an ngAfterContentInit hook where it creates a ListKeyManger, that manages the active option in a list of items, passing the QueryList of MatOptions's as a constructor argument. You can see it here.

    Yes, those mat-option's are rendered, but it still won't work as normal, you can check it by adding the optionSelected listener:

    <mat-autocomplete #auto="matAutoComplete" (optionSelected)="optionSelected($event)">
    

    Assume we do a console.log inside the optionSelected method of our component - it won't be invoked when you click on any option that's rendered via ngTemplateOutlet. mat-autocomplete cannot access this list just because this list is a part of another view (everything inside ng-template is a separately compiled view definition).

    You could just create a reusable component where you will pass an options binding and link it with a parent form via injecting the FormGroupDirective.