Components: Autocomplete: for selected object, return individual property value (i.e. `id`)

Created on 9 Jan 2018  路  18Comments  路  Source: angular/components

Bug, feature request, or proposal:

This is a proposal.

What is the expected behavior?

An Autocomplete component being capable of returning an individual property value (like id) from a selected object, if specified. Otherwise, return the entire selected object as usual.

What is the current behavior?

Return the selected object as the value for an Autocomplete component.

What is the use-case or motivation for changing an existing behavior?

A common use case for autocomplete in forms is for combo boxes with key/value pairs, often stored in an object.

Currently, the Autocomplete component is not capable of returning a selected object's property as the form field value (id).

This is cumbersome when using FormGroup.value to instantiate a new object, because additional "id extraction" has to be implemented outside the component. If the autocomplete component is meant to be reusable in various forms, then the post-processing has to be implemented in multiple locations, violating the DRY principle. It also requires knowing details about the selected object inside the component, which messes with clean encapsulation.

Proposed enhancement

  • The matAutocomplete::displayWith property designates a function that computes the object property for the field to display.
  • There could be a complimentary matAutocomplete::returnWith property (or some other name) that designates a function to compute the return value of the form field.
  • If returnWith isn't specified, then go about business as usual (returning the whole object).

Something like this:

    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"  [returnWith]="returnFn">
      <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
        {{ option.name }}
      </mat-option>
    </mat-autocomplete> 
export class User {
  constructor(public id: number, public name: string) { }
}

...
export class AutocompleteDisplayExample {

  ...

  displayFn(user: User): string {
    return user ? user.name : user;
  }

  returnFn(user: User): number | undefined {
    return user ? user.id : undefined;
  }
}

When used, myFormGroup.value.user would be equal to the id of the selected user.

Is there anything else we should know?

Related:

feature

Most helpful comment

Why is this closed ???????????
This proposal is good and should be implemented, i also need this...
Please re-open

All 18 comments

@HuntedCodes maybe I'm missing something, but why not assign the id to the option's value?

    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"  [returnWith]="returnFn">
      <mat-option *ngFor="let option of filteredOptions | async" [value]="option.id">
        {{ option.name }}
      </mat-option>
    </mat-autocomplete> 

@HuntedCodes
I believe that it is because if you assign the id to the option's value, after the user selects an option, the displayed function would only receive the ID and not the whole object anymore:

See here:
https://stackblitz.com/edit/angular-qqcvcf?file=app%2Fautocomplete-simple-example.html

@hobojoe That's what I discovered too. The Autocomplete component understands objects internally.
An option gets pre-selected during initialization by passing an object, and when new options are
selected, objects are returned.

The goal was to return the ID of a selected object from the autocomplete FormControl,
directly assigning it to a field on a related object.

The object hierarchy goes:

  --> FormControl
    --> CustomAutocomplete
      --> MatAutocomplete

To keep things encapsulated, Object.defineProperty() was used to monkey
patch the parent FormGroup.value instance from CustomAutocomplete. This was a proof of concept that worked:

/* Override get() for FormGroup.value. Run from FormGroup context.
 * If the target FormControl value is an object that is
 * set by matAutocomplete, then return the `id` property.
 */  
Object.defineProperty(formGroup, 'value', {
  get() {
    let fgValues = {};

    for (let controlName in this.controls) {
      // Collect original control values.
      fgValues[controlName] = this.controls[controlName].value;

      // Override target control's value with `id` attribute.
      if (controlName === 'targetControlName' && fgValues[controlName]) {
        const targetObject = fgValues[controlName];
        const hasId = targetObject.hasOwnProperty('id');
        fgValues[controlName] = hasId ? fgValues[controlName].id : fgValues[controlName];
      }   
    }

    return fgValues;
  }
});

While not supported, it satisfies my requirements, so I'll go ahead and close the issue. Thanks!

I have the same issue, the solution seems like a hack, the behavior is weird, auto complete should just select/show the name and return the value(key). why does it return the text inside ?

Why is this closed ???????????
This proposal is good and should be implemented, i also need this...
Please re-open

+1

We use a list of value/view object to distinct the internal value from the label show to the user.

But with reactive form and the actual behavior it's endend up with weird results :

https://stackblitz.com/edit/issue-9293

  1. If i use the .value the form is ok but the input is overrided
  2. If i use the object the form contains the whole object but the input is ok (only with [displayWith])
  3. If a user choose to write it's own value, anything that is not in the dropdown, the form will look like (1.) wich is easier to deal than a mix with case (2.)

The [returnWith] could be a good solution to me.

Or a more natural way ? If it's technically possible...

    <mat-autocomplete autoActiveFirstOption #auto1="matAutocomplete">
      <mat-option *ngFor="let so of speciesOptions | async" [value]="so.value">
        {{ so.view }}
      </mat-option>
    </mat-autocomplete>

If the [value] is not an object, no need to use [displayWith].
And if the {{ so.view }} is it not an object it will be implicitly used in the input.

cc @crisbeto

@WizardPC agree that this is confusing with reactive forms. There either needs to be documentation that you can't access the array in the option loop and here's the workaround, or as mentioned [value] allows the ability to assign an id instead of an object. Otherwise, updating the formControl is cumbersome when you have it's form.value coming in from above.

Example for reactive form with redux input (resolved array via Input()... used a const as a proxy) instead of an observable) here: https://stackblitz.com/edit/angular-autocomplete-array-with-formcontrol-id?file=app%2Fautocomplete-display-example.html

All the typical use cases break as you can either get an object or an id, but not both simultaneously without reaching to get the original array in what seems to be an unnaturally complicated way.

@goelinsights - thanks for sharing that plunker. I modified it a bit to get it working with [value] assigned to the id (not the whole object) as I agree that is more natural given that later you're just trying to store that same ID to the DB, and given that storing the whole object makes no sense for relational data.

Here's the revised code..hope it helps someone!
https://stackblitz.com/edit/angular-autocomplete-array-with-formcontrol-id-1

@goelinsights thanks for the example, i modified it a bit.
I used a different approach, my solution is a bit hacky and not clean but it works.

I used a proxy element as a NgModel of the input.

<input type="text" [(ngModel)]="proxyValue" matInput [formControl]="myControl" [matAutocomplete]="auto">

and added optionSelected method on the autocomplete

<mat-autocomplete #auto="matAutocomplete" (optionSelected)="onSelectionChanged($event)" > <mat-option *ngFor="let option of filteredOptions" [value]="option"> {{ option.name }} </mat-option> </mat-autocomplete>

This element changes with selection of a option.

proxyValue: any; onSelectionChanged(event$)聽{ this.proxyValue = event$.option.value.name; }

https://stackblitz.com/edit/angular-autocomplete-array-with-formcontrol-id-4rffm3?file=app/autocomplete-display-example.ts

Inside the onSelectionChanged method you can change the formGroup value if you need.

+1

We would like to see this feature to be implemented

Any update on this matter?

+1 this needs to be reopened, all existing solutions are hacky.

+1 the way i do this so far is searching in the list of object for the value property =S, but in a list with +1K of object it does not work well

+1 same as Sandy

+1 and about re-open ? The example at documentation is to simple and does not reflect the actual cases.

I'm curious as to why there's still an interest in reviving this request, when there are solutions already, and furthermore the [returnWith] is a step back imho.

Considerations:

  1. The initial request assumed it was desirable to bind to the whole object. Some of the stackblitzes above suggest just binding the value to the id. Any good reason or scenario where you would not want to just do this? It obviates the need for the [returnWith].
  1. I've opened ticket 8436 in which I've argued for reducing dependency on [displayWith] because it creates inversion of control issues, making it harder to generate forms dynamically i.e. because you're forced to bind to a function in the html, you're no longer able to rely on dynamically generated html. This is an anti-pattern.

That said, if anyone has a good use case / need that isn't being addressed I'd suggest opening a new ticket with a full explanation, if you want it to get any traction.

I posted a decent solution and explanation here #4863, towards the bottom. I would prefer a cleaner api or for the default behavior to emulate the mat-select behavior, but this works for now. If you just want to click the stackblitz

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3mp3ri0r picture 3mp3ri0r  路  3Comments

savaryt picture savaryt  路  3Comments

Hiblton picture Hiblton  路  3Comments

theunreal picture theunreal  路  3Comments

MurhafSousli picture MurhafSousli  路  3Comments