user12425844
user12425844

Reputation:

Angular Material Select Dropdown, Emit Whole Object Item, not just a value

Angular Material select dropdown API only emits a value. Both [(ngModel)] and (selectionChange) only emit single member , not the whole object data field.

For example eg, it only emits the food.value = 2, does not emit other class fields for a row list item, like foodName, foodDescription, foodWeight, etc, (need to emit all the corresponding members for a listId)

How do I emit the whole object for food, given an food.value ? Needs to work for Any Material Dropdown in the future created, not just specific cases. Does Material Angular have any specific options to allow this?

The answers below, only work for one particular example, trying to see larger picture.

https://material.angular.io/components/select/api

<mat-form-field>
  <mat-label>Favorite food</mat-label>
  <mat-select>
    <mat-option *ngFor="let food of foods" [value]="food.value">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
</mat-form-field>

Class example, for 2, need foodCategory, Calories, and other fields, etc

export class SelectOverviewExample {
  foods: Food[] = [
    {value: '0', viewValue: 'Steak', foodCategory: 'Meat', Calories: 50},
    {value: '1', viewValue: 'Pizza', foodCategory: 'Carbs', Calories: 100},
    {value: '2', viewValue: 'Apple', foodCategory: 'Fruit', Calories: 25}
  ];
}

Does anyone have a better solution? Otherwise, will to write this in the selectionChange onOutput method. Seems like Angular Materials would have better option,

this.foods.find(x=>x.value =="2")

Note for solutions below; Need to have solution for any different classes, with many type of members, etc

Upvotes: 0

Views: 5240

Answers (7)

Irmin Okic
Irmin Okic

Reputation: 66

There is a solution without using indexes or searching through the collection. The issue is that the select component doesn't know how to compare two objects. The component API allows to set a compareWith function to accomplish this.

Example:

My component gets a Set/Array of DTOs passed to it:

<app-user-select [(selectedUsers)]="dock.authorizedUsers"></app-user-select>

The template is a simple select, but with compareWith set:

<mat-form-field appearance="fill">
  <mat-label>Users</mat-label>
  <mat-select [(value)]="selectedUsers" (valueChange)="selectedUsersChange.emit(selectedUsers)" [compareWith]="deepCompare" multiple>
    <mat-option *ngFor="let user of availableUsers" [value]="user">{{user.name}}</mat-option>
  </mat-select>
</mat-form-field>

In the controller I define what the object equality is in the deepCompare function. Equality in my model is the equality of the two fields identitySourceId and id:

export class UserSelectComponent implements OnInit {

  @Input()
  selectedUsers: Set<UserDto> = new Set<UserDto>();
  @Output()
  selectedUsersChange = new EventEmitter<Set<UserDto>>();

  availableUsers: Array<UserDto>;

  constructor(private userApiService: UserApiService) { }

  ngOnInit(): void {
    this.getAvailableUsers();
  }

  private getAvailableUsers() {
    this.userApiService.getUsers().subscribe({
      next: users => this.availableUsers = users
    })
  }

  public deepCompare(optionValue: UserDto, selectionValue: UserDto): boolean {
    return optionValue.identitySourceId == selectionValue.identitySourceId && optionValue.id == selectionValue.id;
  }

}

The compareWith function can of course do a deep equality check too.

This code loads the collection properly into the selected values and also emits the full collection to the parent.

Upvotes: 0

Tam&#225;s Osztein
Tam&#225;s Osztein

Reputation: 29

Your onSelectionChange($event) function can emit any object, it is not tightly coupled with the data structure you want to display. I had a similar problem with a list of locations, I modified your solution a bit:

Template:

<form formGroup="foodForm">
  <mat-form-field>
    <mat-label>Favorite food</mat-label>
    <mat-select [formControl]="foodControl">
      <mat-option *ngFor="let food of foods" [value]="food.value">
        {{food.viewValue}}
      </mat-option>
    </mat-select>
  </mat-form-field>
</form>

Controller:

// Food list may come from anywhere, even from inside the controller, or from 
  an API
foods: Food[] = [
  {value: '0', viewValue: 'Steak', foodCategory: 'Meat', Calories: 50},
  {value: '1', viewValue: 'Pizza', foodCategory: 'Carbs', Calories: 100},
  {value: '2', viewValue: 'Apple', foodCategory: 'Fruit', Calories: 25}
];
export class SelectOverviewExample {
  @Output() foodSelectionChange = new EventEmitter<any>();
  // if food comes from a parent component @Input() foods;

  foodForm: FormGroup;
  foodControl = new FormControl();

  constructor(private fb: FormBuilder) {
    this.foodForm = fb.group({
      food: this.locationControl
    });
  }

  onFoodSelectionChange(event): void {
    const food = this.foods.find(element => element.value === event.value);
    this.foodSelectionChange.emit(food);
  }
}

You can set the default value of your formControl in the OnInit lifecycle:

ngOnInit() {
  this.foodForm.controls['food'].setValue(/any value/);
}

Upvotes: 1

Ling Vu
Ling Vu

Reputation: 5181

How to emit the whole object in a Material Select Component

The Select component from the Angular Material library does emit the whole object. Anything you define in the value in the mat-option input directive will be emitted when you the select the correspondent value. This is the usual way how it behave.

As side note: Be careful with the used brackets [] as they define that the set value is an object and not a string.

Don't use it like that

<mat-option value="option3">Option 3</mat-option>

Instead

<mat-option [value]="someObject">Option 3</mat-option>

  1. Set the object as value to the mat-option components

    See how the value is set using the row object of the data array

<mat-form-field>
  <mat-label>Select a food option</mat-label>
  <mat-select (selectionChange)="handleSelect($event)">
    <mat-option *ngFor="let row of data" [value]="row">{{ row.name }}</mat-option>
  </mat-select>
</mat-form-field>
  1. Define a method for the event emitter
  handleSelect(food: any) {
    // Here is the actual selected object
    console.log(food.value);
    this.selected = JSON.stringify(food.value);
  }

For your reference: https://stackblitz.com/angular/annvxmlqlqe

Upvotes: 1

Delink
Delink

Reputation: 161

Here is a selection that returns the object

import { Component } from "@angular/core";
// import { Observable }    from 'rxjs/Observable';

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  selected = "";
  data = [
    { value: "0", viewValue: "Steak", foodCategory: "Meat", Calories: 50 },
    { value: "1", viewValue: "Pizza", foodCategory: "Carbs", Calories: 100 },
    { value: "2", viewValue: "Apple", foodCategory: "Fruit", Calories: 25 }
  ];
}
<h2>Select Food</h2>
<mat-form-field>
	<mat-select [(value)]="selected">
		<mat-option value="">Select favorite food</mat-option>
		<mat-option *ngFor="let food of data" [value]="food">
			{{food.viewValue}}
		</mat-option>
	</mat-select>
</mat-form-field>
<div>
	<p>Your Favorite food is</p>
  <ul>
    <li>Food Name: {{selected.viewValue}}</li>
    <li>Calories: {{selected.Calories}}</li>
    <li>Category: {{selected.foodCategory}}</li>
  </ul>
</div>

Look it up at: https://stackblitz.com/edit/angular-h4oivc

Upvotes: 0

user11640989
user11640989

Reputation:

try this one

<mat-form-field>
  <mat-label>Favorite food</mat-label>
    <mat-select [(value)]="selected">
       <mat-option *ngFor="let food of data; let i = index" [value]="i">
          {{food.viewValue}}
       </mat-option>
    </mat-select>
</mat-form-field>
Selected value: {{data[selected].foodCategory}}

https://stackblitz.com/edit/angular-thvpvz

Upvotes: 0

Tarun Khurana
Tarun Khurana

Reputation: 1037

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { Observable }    from 'rxjs/Observable';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  formGroup: FormGroup;
  titleAlert: string = 'This field is required';
  selected;
  data = [
    {value: '0', viewValue: 'Steak', foodCategory: 'Meat', Calories: 50},
    {value: '1', viewValue: 'Pizza', foodCategory: 'Carbs', Calories: 100},
    {value: '2', viewValue: 'Apple', foodCategory: 'Fruit', Calories: 25}
  ];;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit() {
    this.selected = 0
  }


}
<h4>Basic mat-select</h4>
<mat-form-field>
  <mat-label>Favorite food</mat-label>
  <mat-select [(value)]="selected">
    <mat-option *ngFor="let food of data; let i = index" [value]="i">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
</mat-form-field>
<div>
Selected value: {{data[selected].foodCategory}}
</div>

Upvotes: 0

Vna
Vna

Reputation: 542

<mat-form-field>
  <mat-label>Favorite food</mat-label>
  <mat-select>
    <mat-option *ngFor="let food of foods; let i = index" [value]="i" (onSelectionChange)="onValueChange(food)">
      {{food.viewValue}}
    </mat-option>
  </mat-select>
</mat-form-field>

Upvotes: 1

Related Questions