Angular Material: mat-select sem selecionar o padrão

109

Eu tenho um mat-select onde as opções são todos os objetos definidos em uma matriz. Estou tentando definir o valor padrão para uma das opções, no entanto, ele está sendo deixado selecionado quando a página é renderizada.

Meu arquivo datilografado contém:

  public options2 = [
    {"id": 1, "name": "a"},
    {"id": 2, "name": "b"}
  ]
  public selected2 = this.options2[1].id;

Meu arquivo HTML contém:

  <div>
    <mat-select
        [(value)]="selected2">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

Eu tentei configurar selected2e valuein mat-optionpara o objeto e seu id, e tentei usar ambos [(value)]e [(ngModel)]no mat-select, mas nenhum está funcionando.

Estou usando a versão do material 2.0.0-beta.10

William Moore
fonte
2
Use compareWith. É mais elegante.
Badis Merabet
DEVE TER compareWith, veja esta resposta aqui stackoverflow.com/questions/47333171/…
bresleveloper

Respostas:

144

Use uma ligação para o valor em seu modelo.

value="{{ option.id }}"

deveria estar

[value]="option.id"

E no valor selecionado, use em ngModelvez de value.

<mat-select [(value)]="selected2">

deveria estar

<mat-select [(ngModel)]="selected2">

Código completo:

<div>
  <mat-select [(ngModel)]="selected2">
    <mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}</mat-option>
  </mat-select>
</div>

Em uma observação lateral, a partir da versão 2.0.0-beta.12, o material select agora aceita um mat-form-fieldelemento como o elemento pai, portanto, é consistente com os outros controles de entrada de material. Substitua o divelemento pelo mat-form-fieldelemento após a atualização.

<mat-form-field>
  <mat-select [(ngModel)]="selected2">
    <mat-option *ngFor="let option of options2" [value]="option.id">{{ option.name }}</mat-option>
  </mat-select>
</mat-form-field>
Igor
fonte
10
"Parece que você está usando ngModel no mesmo campo de formulário que formControlName. O suporte para usar a propriedade de entrada ngModel e evento ngModelChange com diretivas de formulário reativas foi descontinuado no Angular v6 e será removido no Angular v7. Para obter mais informações sobre isso , consulte nossa documentação de API aqui: angular.io/api/forms/FormControlName#use-with-ngmodel "
ldgorman
1
@ldgorman - Não vejo como você está tirando essa conclusão. Se você está se referindo mat-form-field, este é ..."used to wrap several Angular Material components and apply common Text field styles", por isso, não é a mesma coisa. Diferente do que o OP e também a minha resposta não fez menção FormControl, FormGroupou FormControlName.
Igor
1
Estou tendo o mesmo problema mesmo após implementar o mesmo código acima @Igor
Chuck
@Chuck - Se você ainda estiver tendo problemas, faça uma nova pergunta e inclua um exemplo reproduzível mínimo . Se você quiser que eu dê uma olhada, pode responder a este comentário com um link para essa pergunta.
Igor
2
@Igor- Nós descobrimos que o valor estava sendo retornado como um número e o Mat-selecione-o procurando por uma string. [compareWith]diretiva foi o que usamos
Chuck
94

Use compareWithuma função para comparar os valores das opções com os valores selecionados. veja aqui: https://material.angular.io/components/select/api#MatSelect

Para um objeto com a seguinte estrutura:

listOfObjs = [{ name: 'john', id: '1'}, { name: 'jimmy', id: '2'},...]

Defina a marcação assim:

<mat-form-field>
  <mat-select
    [compareWith]="compareObjects"
    [(ngModel)]="obj">
       <mat-option  *ngFor="let obj of listOfObjs" [value]="obj">
          {{ obj.name }}
       </mat-option>
    </mat-select>
</mat-form-field>

E defina a função de comparação assim:

compareObjects(o1: any, o2: any): boolean {
  return o1.name === o2.name && o1.id === o2.id;
}
Badis Merabet
fonte
8
Perfeito para lidar com objetos e não com matrizes simples. Obrigado.
Riaan van Zyl
25

Estou usando Angular 5 e formas reativas com mat-select e não consigo fazer com que nenhuma das soluções acima exiba o valor inicial.

Tive de adicionar [compareWith] para lidar com os diferentes tipos usados ​​no componente de seleção de tapete. Internamente, parece que o mat-select usa uma matriz para manter o valor selecionado. Isso provavelmente permitirá que o mesmo código funcione com várias seleções se esse modo estiver ativado.

Angular Select Control Doc

Esta é minha solução:

Form Builder para inicializar o controle de formulário:

this.formGroup = this.fb.group({
    country: new FormControl([ this.myRecord.country.id ] ),
    ...
});

Em seguida, implemente a função compareWith em seu componente:

compareIds(id1: any, id2: any): boolean {
    const a1 = determineId(id1);
    const a2 = determineId(id2);
    return a1 === a2;
}

Em seguida, crie e exporte a função determineId (tive que criar uma função autônoma para que mat-select pudesse usá-la):

export function determineId(id: any): string {
    if (id.constructor.name === 'array' && id.length > 0) {
       return '' + id[0];
    }
    return '' + id;
}

Por fim, adicione o atributo compareWith ao seu mat-select:

<mat-form-field hintLabel="select one">
<mat-select placeholder="Country" formControlName="country" 
    [compareWith]="compareIds">

    <mat-option>None</mat-option>
    <mat-option *ngFor="let country of countries" [value]="country.id">
                        {{ country.name }}
    </mat-option>
</mat-select>
</mat-form-field>
Heather92065
fonte
Muito obrigado! Foi muito difícil encontrar uma causa.
Pax Beach de
@ Heather92065 Esta é a única solução que funcionou para mim. Eu sou muito grato a você!
Latin Warrior de
16

Você deve ser vinculativo, como [value]na mat-optioncomo abaixo,

<mat-select placeholder="Panel color" [(value)]="selected2">
  <mat-option *ngFor="let option of options2" [value]="option.id">
    {{ option.name }}
  </mat-option>
</mat-select>

DEMONSTRAÇÃO AO VIVO

Aravind
fonte
Isso funciona perfeitamente. Em vez de usar ngModel ou setValue (), este é o método mais fácil e perfeito
Rohit Parte
10

Você pode simplesmente implementar sua própria função de comparação

[compareWith]="compareItems"

Veja também o docu . Portanto, o código completo seria:

  <div>
    <mat-select
        [(value)]="selected2" [compareWith]="compareItems">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

e no arquivo Typescript:

  compareItems(i1, i2) {
    return i1 && i2 && i1.id===i2.id;
  }
LeO
fonte
Isso funcionou para mim e acho que é a maneira mais correta, mas se a lista contém apenas um elemento, não está funcionando. Obrigado
Código Kadiya de
Que tipo de exceção você obtém com apenas um elemento? Porque a comparação deve se responsabilizar se i1ou i2não existir.
LeO de
6

Como já mencionado no Angular 6, o uso de ngModel em formas reativas foi descontinuado (e removido no Angular 7), então modifiquei o modelo e o componente da seguinte maneira.

O modelo:

<mat-form-field>
    <mat-select [formControl]="filter" multiple 
                [compareWith]="compareFn">
        <mat-option *ngFor="let v of values" [value]="v">{{v.label}}</mat-option>
    </mat-select>
</mat-form-field>

As partes principais do componente ( onChangese outros detalhes são omitidos):

interface SelectItem {
    label: string;
    value: any;
}

export class FilterComponent implements OnInit {
    filter = new FormControl();

    @Input
    selected: SelectItem[] = [];

    @Input()
    values: SelectItem[] = [];

    constructor() { }

    ngOnInit() {
        this.filter.setValue(this.selected);
    }

    compareFn(v1: SelectItem, v2: SelectItem): boolean {
        return compareFn(v1, v2);
    }
}

function compareFn(v1: SelectItem, v2: SelectItem): boolean {
    return v1 && v2 ? v1.value === v2.value : v1 === v2;
}

Nota this.filter.setValue (this.selected) em ngOnInitcima.

Parece funcionar no Angular 6.

mp31415
fonte
Na verdade, essa deve ser a melhor resposta, pois também abrange as seleções de objetos ao lidar com dois resultados de API diferentes para comparação.
Marco Klein
(por exemplo, lista total de itens para selecionar e item selecionado em outra chamada de API).
Marco Klein
O Angular 7 ainda funciona com modelos dirigidos por modelo! Mas você não pode misturá-lo com formas reativas no mesmo modelo. Sua dica com o [compareWith]foi ótima
LeO
3

Eu fiz exatamente como nesses exemplos. Tentei definir o valor de seleção do tapete para o valor de uma das opções do tapete. Mas falhou.

Meu erro foi fazer [(value)] = "someNumberVariable" para uma variável de tipo numérico enquanto as de mat-options eram strings. Mesmo que parecessem iguais no modelo, não selecionaria essa opção.

Depois de analisar someNumberVariable em uma string, tudo ficou totalmente bem.

Portanto, parece que você precisa fazer com que os valores de seleção de tapete e opção de tapete não sejam apenas o mesmo número (se você estiver apresentando números), mas também que sejam do tipo string.

jg80
fonte
Esse foi o meu problema também. Um era numérico, o outro era uma string.
Vadim Berman
2

A solução para mim foi:

<mat-form-field>
  <mat-select #monedaSelect  formControlName="monedaDebito" [attr.disabled]="isLoading" [placeholder]="monedaLabel | async ">
  <mat-option *ngFor="let moneda of monedasList" [value]="moneda.id">{{moneda.detalle}}</mat-option>
</mat-select>

TS:

@ViewChild('monedaSelect') public monedaSelect: MatSelect;
this.genericService.getOpciones().subscribe(res => {

  this.monedasList = res;
  this.monedaSelect._onChange(res[0].id);


});

Usando o objeto: {id: number, detalle: string}

Seba Arce
fonte
1

Experimente isso!

this.selectedObjectList = [{id:1}, {id:2}, {id:3}]
this.allObjectList = [{id:1}, {id:2}, {id:3}, {id:4}, {id:5}]
let newList = this.allObjectList.filter(e => this.selectedObjectList.find(a => e.id == a.id))
this.selectedObjectList = newList
Facu Quintana
fonte
1

Minha solução é um pouco complicada e simples.

<div>
    <mat-select
        [placeholder]="selected2">
      <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

Acabei de usar o espaço reservado . A cor padrão do marcador de posição do material é light gray. Para fazer parecer que a opção está selecionada, apenas manipulei o CSS da seguinte maneira:

::ng-deep .mat-select-placeholder {
    color: black;
}
Steffi Keran Rani J
fonte
1

A vinculação ou configuração do valor padrão funciona apenas se o atributo de valor em MatSelect for comparável ao atributo de valor vinculado a MatOption . Se você ligar captiondo seu item de valor atributo de mat-opção elemento você deve definir o elemento padrão em mat-seleccionar a captionde seu artigo também. Se você vincular Idseu item à opção mat , você deve vincularid a seleção de tapete também, não um item inteiro, legenda ou qualquer outro, apenas o mesmo campo.

Mas você precisa fazer isso com ligação []

Dominik Miedziński
fonte
1

Eu segui as instruções acima com muito cuidado e ainda não consegui selecionar o valor inicial.

O motivo era que, embora meu valor limite fosse definido como uma string no texto datilografado, minha API de back-end estava retornando um número.

A digitação livre de Javascript simplesmente alterou o tipo em tempo de execução (sem erros), o que impediu a seleção do valor inicial.

Componente

myBoundValue: string;

Modelo

<mat-select [(ngModel)]="myBoundValue">

A solução foi atualizar a API para retornar um valor de string.

Alex Cooper
fonte
1

Uma maneira muito simples de conseguir isso é usando um formControlcom um valor padrão, dentro de umFormGroup (opcional), por exemplo. Este é um exemplo usando um seletor de unidade para uma entrada de área:

ts

H_AREA_UNIT = 1;
M_AREA_UNIT = 2;

exampleForm: FormGroup;

this.exampleForm = this.formBuilder.group({
  areaUnit: [this.H_AREA_UNIT],
});

html

<form [formGroup]="exampleForm">
 <mat-form-field>
   <mat-label>Unit</mat-label>
   <mat-select formControlName="areaUnit">
     <mat-option [value]="H_AREA_UNIT">h</mat-option>
     <mat-option [value]="M_AREA_UNIT">m</mat-option>
   </mat-select>
 </mat-form-field>
</form>
Juan Antonio
fonte
0

Uma comparação entre um número e uma string costuma ser falsa , portanto, converta o valor selecionado em uma string dentro de ngOnInit e funcionará.

Eu tive o mesmo problema, preenchi o mat-select com um enum, usando

Object.keys(MyAwesomeEnum).filter(k => !isNaN(Number(k)));

e eu tinha o valor enum que queria selecionar ...

Passei algumas horas lutando contra minha mente tentando identificar por que não estava funcionando. E fiz isso apenas depois de renderizar todas as variáveis ​​sendo usadas no mat-select, na coleção de chaves e no selecionado ... se você tiver ["0", "1", "2"] e quiser selecionar 1 ( que é um número) 1 == "1" é falso e por isso nada é selecionado.

portanto, a solução é converter o valor selecionado em uma string dentro de ngOnInit e isso funcionará.

Juan
fonte
1
Olá Juan, você pode querer dar uma olhada nesta postagem que detalha os diferentes operadores de igualdade em JS: stackoverflow.com/questions/359494/…
William Moore,
Olá William, que postagem ótima, lá estive poucas vezes ... E aprendi a comparar corretamente (espero, e sempre poderei revisar o doc) ... O problema aqui era que as ligações, forçadas por o controlador de material, onde usar diferentes tipos, números e strings ... Esse controlador espera ter os mesmos tipos, então, se selecionado for um número, a coleção deve ser uma coleção de números ... Esse era o problema.
Juan
0

Eu fiz isso.

<div>
    <mat-select [(ngModel)]="selected">
        <mat-option *ngFor="let option of options" 
            [value]="option.id === selected.id ? selected : option">
            {{ option.name }}
        </mat-option>
    </mat-select>
</div>

Normalmente você pode fazer [value]="option", a menos que obtenha suas opções de algum banco de dados ?? Acho que ou o atraso na obtenção dos dados faz com que não funcionem ou os objetos obtidos são diferentes de alguma forma, embora sejam os mesmos ?? Estranhamente, é mais provável que seja o último, pois também tentei [value]="option === selected ? selected : option"e não funcionou.

Novato
fonte
0

TS

   optionsFG: FormGroup;
   this.optionsFG = this.fb.group({
       optionValue: [null, Validators.required]
   });

   this.optionsFG.get('optionValue').setValue(option[0]); //option is the arrayName

HTML

   <div class="text-right" [formGroup]="optionsFG">
     <mat-form-field>
         <mat-select placeholder="Category" formControlName="optionValue">
           <mat-option *ngFor="let option of options;let i =index" [value]="option">
            {{option.Value}}
          </mat-option>
        </mat-select>
      </mat-form-field>
  </div>
Arokia Lijas
fonte
0

public options2 = [
  {"id": 1, "name": "a"},
  {"id": 2, "name": "b"}
]
 
YourFormGroup = FormGroup; 
mode: 'create' | 'update' = 'create';

constructor(@Inject(MAT_DIALOG_DATA) private defaults: defautValuesCpnt,
      private fb: FormBuilder,
      private cd: ChangeDetectorRef) {
}
  
ngOnInit() {

  if (this.defaults) {
    this.mode = 'update';
  } else {
    this.defaults = {} as Cpnt;
  }

  this.YourFormGroup.patchValue({
    ...
    fCtrlName: this.options2.find(x => x.name === this.defaults.name).id,
    ... 
  });

  this.YourFormGroup = this.fb.group({
    fCtrlName: [ , Validators.required]
  });

}
  <div>
    <mat-select formControlName="fCtrlName"> <mat-option
          *ngFor="let option of options2"
          value="{{ option.id }}">
        {{ option.name }}
      </mat-option>
    </mat-select>
  </div>

lilandos didas
fonte
Isso ajudará quando você estiver usando Editar e Atualizar no componente seguro,
lilandos didas