Vincular elemento selecionado ao objeto em Angular

409

Gostaria de vincular um elemento select a uma lista de objetos - o que é bastante fácil:

@Component({
   selector: 'myApp',
   template: `<h1>My Application</h1>
              <select [(ngModel)]="selectedValue">
                 <option *ngFor="#c of countries" value="c.id">{{c.name}}</option>
              </select>`
})
export class AppComponent{
    countries = [
       {id: 1, name: "United States"},
       {id: 2, name: "Australia"}
       {id: 3, name: "Canada"},
       {id: 4, name: "Brazil"},
       {id: 5, name: "England"}
     ];
    selectedValue = null;
}

Nesse caso, parece que selectedValueseria um número - o ID do item selecionado.

No entanto, eu realmente gostaria de vincular ao próprio objeto do país, para que esse selectedValueseja o objeto, e não apenas o ID. Tentei alterar o valor da opção da seguinte forma:

<option *ngFor="#c of countries" value="c">{{c.name}}</option>

mas isso não parece funcionar. Parece colocar um objeto no meu selectedValue- mas não o objeto que estou esperando. Você pode ver isso no meu exemplo do Plunker .

Também tentei vincular o evento de alteração para poder definir o objeto com base no ID selecionado; no entanto, parece que o evento de alteração é acionado antes que o ngModel ligado seja atualizado - o que significa que não tenho acesso ao novo valor selecionado nesse momento.

Existe uma maneira limpa de vincular um elemento de seleção a um objeto com Angular 2?

RHarris
fonte
Acabei de perceber que meu Plunk funciona de maneira um pouco diferente no IE vs. Chrome. Nenhum deles realmente funciona do jeito que eu estou querendo, mas para sua informação.
21316 RHarris

Respostas:

735
<h1>My Application</h1>
<select [(ngModel)]="selectedValue">
  <option *ngFor="let c of countries" [ngValue]="c">{{c.name}}</option>
</select>

Exemplo de StackBlitz

NOTA: você pode usar em [ngValue]="c"vez de [ngValue]="c.id"onde c é o objeto completo do país.

[value]="..."suporta apenas valores de cadeia de caracteres
[ngValue]="..."suporta qualquer tipo

atualizar

Se valuefor um objeto, a instância pré-selecionada precisará ser idêntica a um dos valores.

Consulte também a comparação personalizada adicionada recentemente, https://github.com/angular/angular/issues/13268 disponível desde 4.0.0-beta.7

<select [compareWith]="compareFn" ...

Cuide se você deseja acessar thisdentro compareFn.

compareFn = this._compareFn.bind(this);

// or 
// compareFn = (a, b) => this._compareFn(a, b);

_compareFn(a, b) {
   // Handle compare logic (eg check if unique ids are the same)
   return a.id === b.id;
}
Günter Zöchbauer
fonte
21
Tentei, mas isso parece vincular dados apenas do menu suspenso para o modelo. Se entrar na página com o modelo já definido, o menu suspenso não está definido adequadamente ...
Strinder 19/07/16
13
@Strinder Um erro frequente é usar outra instância de objeto para selectedValuealém de para c(o item padrão). Um objeto diferente, mesmo com as mesmas propriedades e valores, não funciona, deve ser a mesma instância do objeto.
Günter Zöchbauer
11
@ GünterZöchbauer Yeah. Já pensei em pensamento. Portanto, não há uma maneira fácil de sincronizar diretamente com o modelo e uma lista de valores? = sempre via onChange?
Strinder
11
Em breve, poderemos comparar os objetos ngModel por suas propriedades usando a função comparadora personalizada. Veja esta edição: github.com/angular/angular/issues/13268
kub1x
11
É sempre fácil quando você o conhece ;-) mas angular.io/api/forms/NgSelectOption#description contém um link angular.io/api/forms/SelectControlValueAccessor com bons documentos.
Günter Zöchbauer
41

Isso pode ajudar:

    <select [(ngModel)]="selectedValue">
          <option *ngFor="#c of countries" [value]="c.id">{{c.name}}</option>
    </select>
Carolina Faedo
fonte
5
Eu usei [value] em vez de [ngValue]. Não é o mesmo. Isso funcionou para mim
Carolina Faedo
2
Erro no '#' no angular 4
sea-kg
2
Use em letvez de #@ sea-kg
Ashraful Islam
11
Essa resposta não recebe o valor selecionado
San Jaisy
20

Você também pode fazer isso sem a necessidade de usar [(ngModel)]em sua <select>tag

Declare uma variável no seu arquivo ts

toStr = JSON.stringify;

e no seu modelo faça isso

 <option *ngFor="let v of values;" [value]="toStr(v)">
      {{v}}
 </option>

e depois use

let value=JSON.parse(event.target.value)

analisar a sequência novamente em um objeto JavaScript válido

Rahul Kumar
fonte
11
Isso é realmente factível, mas em objetos grandes se tornará uma dor. Além disso, a capacidade sublinhada da Angular de detecção de alterações é algo a ser pensado. A saída de informações como json, facilmente analisável por bots, aumenta o desempenho. O uso da detecção de alterações da Angular oculta (encapsula) a lógica dos dados e garante as informações necessárias. @ Günter Zöchbauer resposta é a maneira de fazê-lo em Angular. :)
Lucaci Andrei
Ajudou-me onde eu tinha uma única lista e alterando um valor não deve atualizar a próxima para que ele ajudou a usar isso como um hack sem o uso de ngmodel, obrigado :)
Melvin
Isso funciona para objetos JavaScript simples, mas observe que, nas instâncias de uma classe, você perderia todos os métodos nela.
KhalilRavanna
13

Funcionou para mim:

HTML do modelo:

Eu adicionei (ngModelChange)="selectChange($event)"ao meu select.

<div>
  <label for="myListOptions">My List Options</label>
  <select (ngModelChange)="selectChange($event)" [(ngModel)]=model.myListOptions.id >
    <option *ngFor="let oneOption of listOptions" [ngValue]="oneOption.id">{{oneOption.name}}</option>
  </select>
</div>

Em component.ts:

  listOptions = [
    { id: 0, name: "Perfect" },
    { id: 1, name: "Low" },
    { id: 2, name: "Minor" },
    { id: 3, name: "High" },
  ];

Você precisa adicionar a component.tsesta função:

  selectChange( $event) {
    //In my case $event come with a id value
    this.model.myListOptions = this.listOptions[$event];
  }

Nota: eu tento com [select]="oneOption.id==model.myListOptions.id" e não trabalho.

============= Outras formas podem ser: =========

HTML do modelo:

Eu adicionei [compareWith]="compareByOptionIdao meu select.

<div>
  <label for="myListOptions">My List Options</label>
  <select [(ngModel)]=model.myListOptions [compareWith]="compareByOptionId">
    <option *ngFor="let oneOption of listOptions" [ngValue]="oneOption">{{oneOption.name}}</option>
  </select>
</div>

Em component.ts:

  listOptions = [
    { id: 0, name: "Perfect" },
    { id: 1, name: "Low" },
    { id: 2, name: "Minor" },
    { id: 3, name: "High" },
  ];

Você precisa adicionar a component.tsesta função:

 /* Return true or false if it is the selected */
 compareByOptionId(idFist, idSecond) {
    return idFist && idSecond && idFist.id == idSecond.id;
 }
Jose Carlos Ramos Carmenates
fonte
Isso é bom se você também quiser manipular o evento de alteração para fazer algo extra (como informar um retorno de chamada de alteração). Embora, nesse caso, você só precise colocar [ngModel]e definir seu modelo manualmente no retorno de chamada de alteração personalizado definido em (ngModelChange).
esmagar
9

Apenas no caso de alguém estar olhando para fazer o mesmo usando o Reactive Forms:

<form [formGroup]="form">
  <select formControlName="country">
    <option *ngFor="let country of countries" [ngValue]="country">{{country.name}}</option>
  </select>
  <p>Selected Country: {{country?.name}}</p>
</form>

Veja aqui o exemplo de trabalho

elvin
fonte
5

Você pode selecionar o ID usando uma função

<option *ngFor="#c of countries" (change)="onchange(c.id)">{{c.name}}</option>
Eng.Gabr
fonte
4

Para mim, está funcionando assim, você pode consolar event.target.value.

<select (change) = "ChangeValue($event)" (ngModel)="opt">   
    <option *ngFor=" let opt of titleArr" [value]="opt"></option>
</select>
Shubhranshu
fonte
2

Além disso, se nada mais de determinadas soluções não funcionar, verifique se você importou "FormsModule" dentro de "AppModule", isso foi uma chave para mim.

nikola.maksimovic
fonte
2

Crie outro getter para o item selecionado

<form [formGroup]="countryForm">
  <select formControlName="country">
    <option *ngFor="let c of countries" [value]="c.id">{{c.name}}</option>
  </select>

  <p>Selected Country: {{selectedCountry?.name}}</p>
</form>

Em st:

get selectedCountry(){
  let countryId = this.countryForm.controls.country.value;
  let selected = this.countries.find(c=> c.id == countryId);
  return selected;
}
Rafi
fonte
1

Você também pode obter o valor selecionado com a ajuda de click () passando o valor selecionado pela função

<md-select placeholder="Select Categorie"  
    name="Select Categorie" >
  <md-option *ngFor="let list of categ" [value]="list.value" (click)="sub_cat(list.category_id)" >
    {{ list.category }}
  </md-option>
</md-select>
Jose Kj
fonte
0

use dessa maneira também ..

<h1>My Application</h1>
<select [(ngModel)]="selectedValue">
     <option *ngFor="let c of countries" value="{{c.id}}">{{c.name}}</option>
 </select>
Rathinavel
fonte
0

Em app.component.html:

 <select type="number" [(ngModel)]="selectedLevel">
          <option *ngFor="let level of levels" [ngValue]="level">{{level.name}}</option>
        </select>

E app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  levelNum:number;
  levels:Array<Object> = [
      {num: 0, name: "AA"},
      {num: 1, name: "BB"}
  ];

  toNumber(){
    this.levelNum = +this.levelNum;
    console.log(this.levelNum);
  }

  selectedLevel = this.levels[0];

  selectedLevelCustomCompare = {num: 1, name: "BB"}

  compareFn(a, b) {
    console.log(a, b, a && b && a.num == b.num);
    return a && b && a.num == b.num;
  }
}
Mojtaba Nava
fonte
0

<select name="typeFather"
    [(ngModel)]="type.typeFather">
        <option *ngFor="let type of types" [ngValue]="type">{{type.title}}</option>
</select>

essa abordagem sempre funcionará, no entanto, se você tiver uma lista dinâmica, carregue-a antes do modelo

Jack Sowell
fonte