chave de acesso e valor do objeto usando * ngFor

426

Estou um pouco confuso sobre como obter o keye valuede um objeto em angular2 durante a utilização *ngForpara a iteração sobre o objeto. Eu sei que no 1.x angular existe uma sintaxe como

ng-repeat="(key, value) in demo"

mas eu não sei como fazer o mesmo em angular2. Eu tentei algo semelhante, sem sucesso:

<ul>
  <li *ngFor='#key of demo'>{{key}}</li>
</ul>

demo = {
    'key1': [{'key11':'value11'}, {'key12':'value12'}],
    'key2': [{'key21':'value21'}, {'key22':'value22'}],
  }

Aqui está um plnkr com a minha tentativa: http://plnkr.co/edit/mIj619FncOpfdwrR0KeG?p=preview

Como posso obter key1e key2usar dinamicamente *ngFor? Depois de pesquisar bastante, achei a ideia de usar pipes, mas não sei como fazê-lo. Existe algum tubo embutido para fazer o mesmo em angular2?

Pardeep Jain
fonte
2
atualmente não há suporte key, valuepar tipo de sintaxe no angular2 ngFor, você deve olhar para esta resposta
Pankaj Parkar
@PankajParkar sim, já leia esta resposta. alguma alternativa por enquanto?
precisa
@Pradeep Eu não acho que de qualquer outra maneira para isso agora, você deve ir para a criação própria Pipepara este ..
Pankaj Parkar
hmm, mas eu não tenho idéia de como criar pipe para o mesmo.
precisa saber é o seguinte
A resposta @ Pradeep, que lhe dei como referência, tem essa implementação. eles devem trabalhar ..
Pankaj Parkar

Respostas:

399

Tenha Object.keysacessível no modelo e use-o no *ngFor.

@Component({
  selector: 'app-myview',
  template: `<div *ngFor="let key of objectKeys(items)">{{key + ' : ' + items[key]}}</div>`
})

export class MyComponent {
  objectKeys = Object.keys;
  items = { keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3' };
  constructor(){}
}
tomtastico
fonte
1
@PardeepJain é um método ES5 developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
tomtastico 15/17
25
Esta é uma solução melhor e mais eficiente
Aous1000
1
@tomtastico Como você exibia isso para uma matriz 3D? Por exemplo {"1": {"1.1": ["1.1.1", "1.1.2"]}}. E então nidificação 3 ngFor de
Frank
@ Frank, você acabou de dizer você mesmo. Aninhe os *ngFors. Primeiros dois usando objectKeys, o mais interno não é necessário (pois é apenas uma matriz).
tomtastico
1
Impressionante. Definir objectKeys = Object.keys é o método mais simples que já vi verificar o comprimento de um objeto a partir do HTML.
JAC
394

Como na versão mais recente do Angular (v6.1.0) , o Angular Team adicionou um novo pipe interno com o mesmo nome de keyvaluepipe para ajudá-lo a percorrer objetos, mapas e matrizes no commonmódulo do pacote angular. Por exemplo -

<div *ngFor="let item of testObject | keyvalue">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

Exemplo bifurcado de trabalho

confira aqui para mais informações úteis -

Se você estiver usando o Angular v5 ou abaixo ou quiser usar o pipe, siga esta resposta

Pardeep Jain
fonte
1
lol eu tinha que fazer uma atualização NG6 apenas para acesso este tubo - grande coisa - THX
danday74
36
Você pode manter a ordem da chave original usando um comparador personalizado: *ngFor="let item of testObject | keyvalue:keepOriginalOrder" e em sua classe define: public keepOriginalOrder = (a, b) => a.key
mike-Shtil
2
pública keepOriginalOrder = (a, b) => thx a.key muito para este
Kumaresan Perumal
1
esta deve ser a resposta - trabalhando bem em angulares 7
calios
1
Inacreditável isso não estava lá desde a primeira versão
Carlos Pinzón
227

Você pode criar um canal personalizado para retornar a lista de chaves para cada elemento. Algo parecido:

import { PipeTransform, Pipe } from '@angular/core';

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push(key);
    }
    return keys;
  }
}

e use assim:

<tr *ngFor="let c of content">           
  <td *ngFor="let key of c | keys">{{key}}: {{c[key]}}</td>
</tr>

Editar

Você também pode retornar uma entrada contendo chave e valor:

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push({key: key, value: value[key]});
    }
    return keys;
  }
}

e use assim:

<span *ngFor="let entry of content | keys">           
  Key: {{entry.key}}, value: {{entry.value}}
</span>
Thierry Templier
fonte
1
observe o colchete ausente emkeys.push({key: key, value: value[key]);
Marton Pallagi 08/09
49
Na verdade, desencorajo qualquer pessoa a usar pipes para criar coleções dentro da *ngForexpressão. Ele cria um grande gargalo de desempenho, pois precisa gerar a coleção toda vez que o detector de alterações verifica alterações.
martin
3
Obrigado pela solução ... o problema é que sempre que o objeto muda, o canal não é atualizado. Se eu adicionar pure:falseao tubo, torna-se muito ineficiente. Você tem uma solução para atualizar o canal manualmente sempre que eu mudar o objeto (remover item)?
Nchen
4
A resposta está um pouco desatualizada. A linha * ngFor = "# entrada do conteúdo | chaves" não funciona corretamente e o loop for ... in é melhor para mudar para "for (chave const de Object.keys (value))"
Experimentador
2
@RachChen Não nos modelos: common: NgFor has been removed as it was deprecated since v4. Use NgForOf instead. This does not impact the use of*ngFor in your templates.( jaxenter.com/road-to-angular-5-133253.html )
mwld
49

Atualizar

No 6.1.0-beta.1, KeyValuePipe foi introduzido https://github.com/angular/angular/pull/24319

<div *ngFor="let item of {'b': 1, 'a': 1} | keyvalue">
  {{ item.key }} - {{ item.value }}
</div>

Exemplo de Plunker

Versão anterior

Outra abordagem é criar uma NgForIndiretiva que será usada como:

<div *ngFor="let key in obj">
   <b>{{ key }}</b>: {{ obj[key] }}
</div>

Exemplo de Plunker

ngforin.directive.ts

@Directive({
  selector: '[ngFor][ngForIn]'
})
export class NgForIn<T> extends NgForOf<T> implements OnChanges {

  @Input() ngForIn: any;

  ngOnChanges(changes: NgForInChanges): void {
    if (changes.ngForIn) {
      this.ngForOf = Object.keys(this.ngForIn) as Array<any>;

      const change = changes.ngForIn;
      const currentValue = Object.keys(change.currentValue);
      const previousValue = change.previousValue ? Object.keys(change.previousValue) : undefined;
      changes.ngForOf =  new SimpleChange(previousValue, currentValue, change.firstChange);

      super.ngOnChanges(changes);
    }
  }
}
yurzui
fonte
43

No Angular 6.1, você pode usar o pipe keyvalue :

<div *ngFor="let item of testObject | keyvalue">
    Key: <b>{{item.key}}</b> and Value: <b>{{item.value}}</b>
</div>

Mas tem o inconveniente de classificar a lista resultante pelo valor da chave. Se você precisar de algo neutro:

@Pipe({ name: 'keyValueUnsorted', pure: false  })
export class KeyValuePipe implements PipeTransform {
  transform(input: any): any {
    let keys = [];
    for (let key in input) {
      if (input.hasOwnProperty(key)) {
        keys.push({ key: key, value: input[key]});
      }
    }
    return keys;
  }
}

Não se esqueça de especificar o atributo pure: false pipe. Nesse caso, o canal é chamado em cada ciclo de detecção de alterações, mesmo que a referência de entrada não tenha sido alterada (o mesmo ocorre quando você adiciona propriedades a um objeto).

Gerard Carbó
fonte
Já partilhou a mesma resposta acima stackoverflow.com/a/51491848/5043867
Pardeep Jain
26

Elaboração da resposta de @ Thierry com exemplo.

Não existe um método ou tubo embutido para obter key and valuedo loop * ngFor. então temos que criar um canal personalizado para o mesmo. Como Thierry disse, aqui está a resposta com o código.

** A classe pipe implementa o método de transformação da interface PipeTransform, que pega um valor de entrada e uma matriz opcional de cadeias de parâmetros e retorna o valor transformado.

** O método de transformação é essencial para um tubo. A interface PipeTransform define esse método e guia as ferramentas e o compilador. É opcional; Angular procura e executa o método de transformação independentemente. para mais informações sobre tubos, consulte aqui

import {Component, Pipe, PipeTransform} from 'angular2/core';
import {CORE_DIRECTIVES, NgClass, FORM_DIRECTIVES, Control, ControlGroup, FormBuilder, Validators} from 'angular2/common';

@Component({
    selector: 'my-app',
    templateUrl: 'mytemplate.html',
    directives: [CORE_DIRECTIVES, FORM_DIRECTIVES],
    pipes: [KeysPipe]
})
export class AppComponent { 

  demo = {
    'key1': 'ANGULAR 2',
    'key2': 'Pardeep',
    'key3': 'Jain',
  }
}


@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform {
  transform(value, args:string[]) : any {
    let keys = [];
    for (let key in value) {
      keys.push({key: key, value: value[key]});
    }
    return keys;
  }
}

e parte HTML é:

<ul>
  <li *ngFor='#key of demo | keys'>
   Key: {{key.key}}, value: {{key.value}}
  </li>
</ul>

Working Plnkr http://plnkr.co/edit/50LlK0k6OnMnkc2kNHM2?p=preview

atualizar para RC

como sugerido por user6123723 (obrigado) no comentário aqui é atualização.

<ul>
  <li *ngFor='let key of demo | keys'>
   Key: {{key.key}}, value: {{key.value}}
  </li>
</ul>
Pardeep Jain
fonte
Isso precisa ser atualizado: o aviso que recebo "#" dentro das expressões foi preterido. Use "let" em vez disso! ("</li> -> <ul * ngIf =" demo "> <li [ERRO ->] * ngFor = '# chave da demonstração | chaves'> Chave: {{key.key}}, valor: { {key.value}} </li> "): myComponent @ 56: 6
user6123723
Não tenho certeza se isso é novo, mas cite os documentos:> Devemos incluir nosso pipe na matriz de declarações do AppModule.
Akzidenzgrotesk
18

A @Marton fez uma objeção importante à resposta aceita, alegando que o pipe cria uma nova coleção em cada detecção de alteração. Em vez disso, eu criaria um HtmlService que fornece uma variedade de funções utilitárias que o modo de exibição pode usar da seguinte maneira:

@Component({
  selector: 'app-myview',
  template: `<div *ngFor="let i of html.keys(items)">{{i + ' : ' + items[i]}}</div>`
})
export class MyComponent {
  items = {keyOne: 'value 1', keyTwo: 'value 2', keyThree: 'value 3'};
  constructor(private html: HtmlService){}
}

@Injectable()
export class HtmlService {
  keys(object: {}) {
    return Object.keys(object);
  }
  // ... other useful methods not available inside html, like isObject(), isArray(), findInArray(), and others...
}
Stephen Paul
fonte
2
e como isso é melhor do que apenas Object.keys(...)dentro do * ngFor?
Simon_Weaver
8
Porque ele vai jogar: TypeError: Cannot read property 'keys' of undefined. Não parece ser suportado no modelo.
Stephen Paul
1
Isso funciona muito bem como uma solução e evita os problemas de desempenho mencionados acima. stackoverflow.com/questions/35534959/…
J. Adam Connor
Olá, isso pode ser usado não na templateopção, mas no código html real do modelo? obrigado
Scaramouche
16

Se você já está usando o Lodash, pode fazer essa abordagem simples, que inclui chave e valor:

<ul>
  <li *ngFor='let key of _.keys(demo)'>{{key}}: {{demo[key]}}</li>
</ul>

No arquivo datilografado, inclua:

import * as _ from 'lodash';

e no componente exportado, inclua:

_: any = _;
Jeremy Moritz
fonte
desculpe, mas não há necessidade de usar uma biblioteca extra como o Lodash para essas coisas. de qualquer maneira novos métodos são sempre bem-vindos :)
Pardeep Jain
8

Obrigado pelo pipe, mas tive que fazer algumas alterações antes de poder usá-lo no angular 2 RC5. Alterou a linha de importação de tubulação e também adicionou um tipo de qualquer à inicialização da matriz de chaves.

 import {Pipe, PipeTransform} from '@angular/core';

 @Pipe({name: 'keys'})
 export class KeysPipe implements PipeTransform {
 transform(value) {
   let keys:any = [];
   for (let key in value) {
      keys.push( {key: key, value: value[key]} );
    }
     return keys;
  }
}
Adam Winnipass
fonte
Sim importações mudou
Pardeep Jain
7

Nenhuma das respostas aqui funcionou para mim imediatamente, eis o que funcionou para mim:

Crie pipes/keys.tscom o conteúdo:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({name: 'keys'})
export class KeysPipe implements PipeTransform
{
    transform(value:any, args:string[]): any {
        let keys:any[] = [];
        for (let key in value) {
            keys.push({key: key, value: value[key]});
        }
        return keys;
    }
}

Adicione a app.module.ts(seu módulo principal):

import { KeysPipe } from './pipes/keys';

e adicione à matriz de declarações do módulo algo como isto:

@NgModule({
    declarations: [
        KeysPipe
    ]
})
export class AppModule {}

Então, no seu modelo de exibição, você pode usar algo como isto:

<option *ngFor="let entry of (myData | keys)" value="{{ entry.key }}">{{ entry.value }}</option>

Aqui está uma boa referência que encontrei se você quiser ler mais.

cjohansson
fonte
posso saber qual é a diferença entre a sua resposta e a de outras respostas (usando apenas pipe) fornecidas acima? parece o mesmo que acima
Pardeep Jain 18/17
1
Claro 1. Os exemplos acima usam * ngFor = "# entry" em vez de * ngFor = "let entry of" e meu compilador não aceitou a sintaxe #entry, a referência também não usa #. "deixar a entrada de (myData | keys)" parece ser uma solução melhor. 2. Meu compilador não validou o exemplo Classe de tubulação porque estava faltando tipos de dados explícitos, então eu adicionei isso. 3. Os exemplos acima não mostram como integrar o Pipe em um projeto que minha resposta faz; você precisa importá-lo para o módulo principal.
Cjohansson 18/05/19
haha sim offcourese, porque quando resposta foi dada em que a sintaxe tempo, incluindo #etc. btw sua resposta é também corrigir, sem dúvida
Pardeep Jain
6

Há uma biblioteca muito legal que faz isso entre outros cachimbos. É chamado ngx-pipes .

Por exemplo, o tubo de chaves retorna chaves para um objeto e o tubo de valores retorna valores para um objeto:

tubo de chaves

<div *ngFor="let key of {foo: 1, bar: 2} | keys">{{key}}</div> 
<!-- Output: 'foo' and 'bar -->

tubo de valores

<div *ngFor="let value of {foo: 1, bar: 2} | values">{{value}}</div>
<!-- Output: 1 and 2 -->

Não há necessidade de criar seu próprio tubo personalizado :)

RichieRock
fonte
2
boa alternativa, mas coisa é por que usar biblioteca externa para simples paz de código se podemos fazer isso usando simples pedaço de código como a tubulação
Pardeep Jain
2
Umm ... mas é um cachimbo? É apenas uma linha no seu package.json e outras duas linhas no seu módulo quando você importa a biblioteca. Por outro lado, um canal personalizado precisa de um arquivo separado com cerca de 10 a 20 linhas de código e também as linhas de importação no seu módulo. Achamos muito fácil usar o ngx-pipes em nossos projetos. Por que devemos reinventar a roda? :)
RichieRock
Sim, sem dúvida, na verdade é uma opinião baseada, você pode escolher entre esses dois, ninguém está errado.
Pardeep Jain
2
Não se esqueça, se você escrever um tubo de costume, você deve testar essa tubulação costume bem . Portanto, são 10 a 20 linhas de código de canal e, provavelmente, 20 a 40 linhas de código de teste para testar o canal.
precisa saber é o seguinte
4

Usar índice:

<div *ngFor="let value of Objects; index as key">

Uso:

{{key}} -> {{value}}
Adonias Vasquez
fonte
1
Isso é algo novo para mim, melhor. Se você pudesse adicionar um exemplo junto com sua resposta :) Também pode me indicar alguma documentação para o mesmo?
Pardeep Jain
Qual é o tipo de objetos? Matriz ou mapa? Por favor, deixe claro. Agradecemos antecipadamente
Basil Mohammed
3

Aqui está a solução simples

Você pode usar iteradores datilografados para isso

import {Component} from 'angular2/core';
declare var Symbol;
@Component({
    selector: 'my-app',
    template:`<div>
    <h4>Iterating an Object using Typescript Symbol</h4><br>
Object is : <p>{{obj | json}}</p>
</div>
============================<br>
Iterated object params are:
<div *ngFor="#o of obj">
{{o}}
</div>

`
})
export class AppComponent {
  public obj: any = {
    "type1": ["A1", "A2", "A3","A4"],
    "type2": ["B1"],
    "type3": ["C1"],
    "type4": ["D1","D2"]
  };

  constructor() {
    this.obj[Symbol.iterator] =  () => {
          let i =0;

          return {
            next: () => {
              i++;
              return {
                  done: i > 4?true:false,
                  value: this.obj['type'+i]
              }
            }
          }
    };
  }
}

http://plnkr.co/edit/GpmX8g?p=info

Sudheer KB
fonte
3

alterar o tipo de demonstração para matriz ou iterar sobre seu objeto e enviar para outra matriz

public details =[];   
Object.keys(demo).forEach(key => {
      this.details.push({"key":key,"value":demo[key]);
    });

e do html:

<div *ngFor="obj of details">
  <p>{{obj.key}}</p>
  <p>{{obj.value}}</p>
  <p></p>
</div>
Mohammad Reza Mrg
fonte
Este não é um método apropriado, isso pode ser feito facilmente por qualquer pessoa.
Pardeep Jain 17/07/1918
1

Eu acho que Object.keys é a melhor solução para esse problema. Para qualquer um que se deparar com essa resposta e tentar descobrir por que Object.keys está dando a eles ['0', '1'] em vez de ['key1', 'key2'], uma história de advertência - tenha cuidado com a diferença entre " de "e" em ":

Eu já estava usando Object.keys, algo semelhante a este:

interface demo {
    key: string;
    value: string;
}

createDemo(mydemo: any): Array<demo> {
    const tempdemo: Array<demo> = [];

    // Caution: use "of" and not "in"
    for (const key of Object.keys(mydemo)) {
        tempdemo.push(
            { key: key, value: mydemo[key]}
        );
    }

    return tempdemo;
}

No entanto, em vez de

for (const key OF Object.keys(mydemo)) {

Eu escrevi inadvertidamente

for (const key IN Object.keys(mydemo)) {

que "funcionou" perfeitamente bem sem nenhum erro e retornou

[{key: '0', value: undefined}, {key: '1', value: undefined}]

Isso me custou cerca de 2 horas pesquisando e xingando ..

(bate na testa)

Ciaran Bruen
fonte
1

você pode obter a chave do objeto dinâmico tentando isso

myObj['key']
Rizo
fonte
0

Você tem que fazer isso assim por enquanto, eu sei que não é muito eficiente, pois você não deseja converter o objeto que recebe do firebase.

    this.af.database.list('/data/' + this.base64Email).subscribe(years => {
        years.forEach(year => {

            var localYears = [];

            Object.keys(year).forEach(month => {
                localYears.push(year[month])
            });

            year.months = localYears;

        })

        this.years = years;

    });
kevinius
fonte