Iterar sobre objeto em Angular


130

Estou tentando fazer algumas coisas no Angular 2 Alpha 28 e estou tendo um problema com dicionários e NgFor.

Eu tenho uma interface no TypeScript parecida com esta:

interface Dictionary {
    [ index: string ]: string
}

Em JavaScript, isso será traduzido para um objeto que, com dados, será assim:

myDict={'key1':'value1','key2':'value2'}

Quero iterar sobre isso e tentei o seguinte:

<div *ngFor="(#key, #value) of myDict">{{key}}:{{value}}</div>

Mas sem sucesso, nenhum dos itens abaixo funcionou:

<div *ngFor="#value of myDict">{{value}}</div>
<div *ngFor="#value of myDict #key=index">{{key}}:{{value}}</div>

Em todos os casos, recebo erros como "Token inesperado" ou "Não é possível encontrar o objeto de suporte de tubo 'iterableDiff'"

O que estou perdendo aqui? Isso não é mais possível? (A primeira sintaxe funciona no Angular 1.x) ou a sintaxe é diferente para iterar sobre um objeto?


O que é um "dicionário"? Eu nunca vi ou ouvi esse termo em um contexto JavaScript, Angular ou TypeScript. Y

Dicionário significa um mapa, eu acho, o termo não é usado no contexto JS, mas em Python ou Ruby.
Cesar Jr Rodriguez

2
Acho que a resposta @bersling agora é a resposta correta para esta pergunta.
Joshua Kissoon

1
Por favor, marque a resposta correta melhor. Bersling está correto
activedecay 18/01/19

Respostas:


86

Parece que eles não querem dar suporte à sintaxe de ng1.

De acordo com Miško Hevery ( referência ):

Os mapas não têm pedidos de chaves e, portanto, a iteração é imprevisível. Isso foi suportado no ng1, mas achamos que foi um erro e não será suportado no NG2

O plano é ter um canal mapToIterable

<div *ngFor"var item of map | mapToIterable">

Portanto, para percorrer seu objeto, você precisará usar um "pipe". Atualmente, não há canal implementado que faça isso.

Como solução alternativa, aqui está um pequeno exemplo que itera sobre as chaves:

Componente:

import {Component} from 'angular2/core';

@Component({
  selector: 'component',
  templateUrl: `
       <ul>
       <li *ngFor="#key of keys();">{{key}}:{{myDict[key]}}</li>
       </ul>
  `
})
export class Home {
  myDict : Dictionary;
  constructor() {
    this.myDict = {'key1':'value1','key2':'value2'};
  }

  keys() : Array<string> {
    return Object.keys(this.myDict);
  }
}

interface Dictionary {
    [ index: string ]: string
}

1
Estou tentando o mesmo no objeto com keyas numbere valueas stringmas angular está jogando erro expression has changed after it was checked? por que alguma idéia?
precisa saber é o seguinte

Sim, isso está acontecendo para mim também. E o mesmo se eu usar a solução da @ obsur também.
User2294382

1
Por favor, veja a resposta de bersling porque existe uma solução trivial no último angular 7
activeedecay

156

Resposta Angular 6.1.0+

Use o keyvaluetubo integrado assim:

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

ou assim:

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

onde mySortingFunctionestá no seu .tsarquivo, por exemplo:

mySortingFunction = (a, b) => {
  return a.key > b.key ? -1 : 1;
}

Stackblitz: https://stackblitz.com/edit/angular-iterate-key-value

Você não precisará registrar isso em nenhum módulo, pois os tubos angulares funcionam de maneira imediata em qualquer modelo.

Também funciona para Javascript-Maps .


Você deve adicionar implements PipeTransformna definição de classe (ver angular.io/guide/pipes#custom-pipes )
toioski

1
@toioski obrigado, eu adicionei e atualizei para a nova sintaxe do loop for.
precisa saber é

Ótima resposta, usei isso para ngPara meu dicionário. Eu tive que fazer keyValuePair.val [0], já que meus valores acabaram [{}] e não {} #
jhhoff02

1
Existe uma vantagem nisso apenas return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Justin Morgan

Eu não vejo nenhum, na verdade eu usaria o seu caminho!
precisa saber é

72

tente usar esse tubo

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

@Pipe({ name: 'values',  pure: false })
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key]);
  }
}

<div *ngFor="#value of object | values"> </div>

5
Brilhante, e se eu quiser manter a referência à chave, apenas mapeio um objeto com chave e valor. Eu gostaria de poder marcar várias respostas como resposta aceita, já que esta é a solução para o meu problema, enquanto a resposta marcada é a resposta para a minha pergunta.
Rickard Staaf

1
@obscur - Se eu fizer o acima exposto agora, recebo o erro "a expressão foi alterada após a verificação" usando angular2.beta.0.0. Alguma ideia?
User2294382

Isso porque pura: false requer uma mudança detectionStrategy para ser injetado
Judson Terrell

1
Por que defini-lo como impuro?
precisa saber é o seguinte

Isto funcionou bem para mim. A única coisa foi que eu não poderia usar # no ngFor. Usado let em vez disso.
MartinJH 10/09

19

Além da resposta do @ obscur, aqui está um exemplo de como você pode acessar o keye a valuepartir do @View.

Tubo:

@Pipe({
   name: 'keyValueFilter'
})

export class keyValueFilterPipe {
    transform(value: any, args: any[] = null): any {

        return Object.keys(value).map(function(key) {
            let pair = {};
            let k = 'key';
            let v = 'value'


            pair[k] = key;
            pair[v] = value[key];

            return pair;
        });
    }

}

Visão:

<li *ngFor="let u of myObject | 
keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>

Portanto, se o objeto fosse parecido com:

myObject = {
    Daario: Naharis,
    Victarion: Greyjoy,
    Quentyn: Ball
}

O resultado gerado seria:

Nome: Daario
Sobrenome: Naharis

Primeiro nome: Victarion
Sobrenome: Greyjoy

Nome: Quentyn
Sobrenome: Ball


2
apenas uma coisa a mencionar que você precisa alterar a exibição: como <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 de mim.
sib10

O código dentro de sua função de mapa pode ser simplificada como: return { 'key' : key, 'value' : value[key] };
Makotosan

17

Atualizado em: Angular agora está fornecendo o pipe para percorrer o objeto json via keyvalue:

<div *ngFor="let item of myDict | keyvalue">
  {{item.key}}:{{item.value}}
</div>

DEMONSTRAÇÃO DE TRABALHO , e para mais detalhes Leia


Anteriormente (para a versão mais antiga): Até agora, a melhor / menor resposta que encontrei é (sem filtro de tubulação ou função personalizada do lado do componente)

Lado do componente:

objectKeys = Object.keys;

Lado do modelo:

<div *ngFor='let key of objectKeys(jsonObj)'>
   Key: {{key}}

    <div *ngFor='let obj of jsonObj[key]'>
        {{ obj.title }}
        {{ obj.desc }}
    </div>

</div>

DEMONSTRAÇÃO DE TRABALHO


1
let item of myDict | keyvalueisso resolveu meu problema.
Silambarasan RD

13

Adicionando ao SimonHawesome excelente resposta de . Fiz uma versão sucinta que utiliza alguns dos novos recursos datilografados. Percebo que a versão de SimonHawesome é intencionalmente detalhada para explicar os detalhes subjacentes. Também adicionei uma verificação antecipada para que o pipe funcione com valores falsos . Por exemplo, se o mapa estiver null.

Observe que o uso de uma transformação de iterador (como feito aqui) pode ser mais eficiente, pois não precisamos alocar memória para uma matriz temporária (como feito em algumas das outras respostas).

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

@Pipe({
    name: 'mapToIterable'
})
export class MapToIterable implements PipeTransform {
    transform(map: { [key: string]: any }, ...parameters: any[]) {
        if (!map)
            return undefined;
        return Object.keys(map)
            .map((key) => ({ 'key': key, 'value': map[key] }));
    }
}

3
adoro esse tópico, com um comentário sendo construído sobre outro! Eu estava prestes a escrever a mesma coisa quando vi o seu código #
David David

3
a única coisa nesta solução: deve implementarPipeTransform
IRAS

@iRaS Bom argumento. Eu atualizei minha resposta. Também retorno indefinido em vez de nulo.
Frederik Aalund

9

Aqui está uma variação de algumas das respostas acima que oferecem suporte a várias transformações (keyval, key, value):

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

type Args = 'keyval'|'key'|'value';

@Pipe({
  name: 'mapToIterable',
  pure: false
})
export class MapToIterablePipe implements PipeTransform {
  transform(obj: {}, arg: Args = 'keyval') {
    return arg === 'keyval' ?
        Object.keys(obj).map(key => ({key: key, value: obj[key]})) :
      arg === 'key' ?
        Object.keys(obj) :
      arg === 'value' ?
        Object.keys(obj).map(key => obj[key]) :
      null;
  }
}

Uso

map = {
    'a': 'aee',
    'b': 'bee',
    'c': 'see'
}

<div *ngFor="let o of map | mapToIterable">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let o of map | mapToIterable:'keyval'">{{o.key}}: {{o.value}}</div>
  <div>a: aee</div>
  <div>b: bee</div>
  <div>c: see</div>

<div *ngFor="let k of map | mapToIterable:'key'">{{k}}</div>
  <div>a</div>
  <div>b</div>
  <div>c</div>

<div *ngFor="let v of map | mapToIterable:'value'">{{v}}</div>
  <div>aee</div>
  <div>bee</div>
  <div>see</div>

1
pure: falseé realmente importante para reflexões instantâneas.
Fırat Kucuk

4

Eu tive um problema semelhante, construí algo para objetos e mapas.

import { Pipe } from 'angular2/core.js';

/**
 * Map to Iteratble Pipe
 * 
 * It accepts Objects and [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
 * 
 * Example:
 * 
 *  <div *ngFor="#keyValuePair of someObject | mapToIterable">
 *    key {{keyValuePair.key}} and value {{keyValuePair.value}}
 *  </div>
 * 
 */
@Pipe({ name: 'mapToIterable' })
export class MapToIterable {
  transform(value) {
    let result = [];
    
    if(value.entries) {
      for (var [key, value] of value.entries()) {
        result.push({ key, value });
      }
    } else {
      for(let key in value) {
        result.push({ key, value: value[key] });
      }
    }

    return result;
  }
}


1
Este bom funciona, exceto que à máquina que você deve adicionar implements PipeTransformà definição de classe
GeorgDangl

3

Angular 2.x e& Angular 4.x não suportam isso imediatamente

Você pode usar esses dois canais para iterar por chave ou por valor .

Tubo de chaves:

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

@Pipe({
  name: 'keys',
  pure: false
})
export class KeysPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value)
  }
}

Tubo de valores:

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

@Pipe({
  name: 'values',
  pure: false
})
export class ValuesPipe implements PipeTransform {
  transform(value: any, args: any[] = null): any {
    return Object.keys(value).map(key => value[key])
  }
}

Como usar:

let data = {key1: 'value1', key2: 'value2'}

<div *ngFor="let key of data | keys"></div>
<div *ngFor="let value of data | values"></div>

2

Se alguém está se perguntando como trabalhar com objetos multidimensionais, aqui está a solução.

vamos assumir que temos o seguinte objeto em serviço

getChallenges() {
    var objects = {};
    objects['0'] = { 
        title: 'Angular2', 
        description : "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
    };

    objects['1'] = { 
        title: 'AngularJS', 
        description : "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
    };

    objects['2'] = { 
        title: 'Bootstrap',
        description : "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
    };
    return objects;
}

no componente adicione a seguinte função

challenges;

constructor(testService : TestService){
    this.challenges = testService.getChallenges();
}
keys() : Array<string> {
    return Object.keys(this.challenges);
}

finalmente, tendo em vista o seguinte

<div *ngFor="#key of keys();">
    <h4 class="heading">{{challenges[key].title}}</h4>
    <p class="description">{{challenges[key].description}}</p>
</div>

2

Tenho arrancado os cabelos tentando analisar e usar os dados retornados de uma chamada JSON query / api. Não sei exatamente onde estava errado, sinto que tenho circulado a resposta por dias, perseguindo vários códigos de erro como:

"Não foi possível encontrar o objeto de suporte do tubo 'iterableDiff'"

"Matriz TYpe genérica requer um ou mais argumentos"

Erros de análise de JSON e tenho certeza que outros

Estou assumindo que apenas tive a combinação errada de correções.

Então, aqui está um resumo de dicas e coisas para procurar.

Em primeiro lugar, verifique o resultado de suas chamadas de API, seus resultados podem estar na forma de um objeto, uma matriz ou uma matriz de objetos.

eu não vou falar muito sobre isso, basta dizer que o erro original do OP de não ser iterável geralmente é causado por você tentar iterar um objeto, não uma matriz.

Heres alguns dos meus resultados de depuração mostrando variáveis ​​de matrizes e objetos

Portanto, como geralmente queremos iterar sobre nosso resultado JSON, precisamos garantir que ele esteja na forma de uma matriz. Tentei vários exemplos e, talvez, sabendo o que sei agora, alguns deles realmente funcionariam, mas a abordagem com que eu fui foi realmente implementar um pipe e o código que usei foi o postado por t.888

   transform(obj: {[key: string]: any}, arg: string) {
if (!obj)
        return undefined;

return arg === 'keyval' ?
    Object.keys(obj).map((key) => ({ 'key': key, 'value': obj[key] })) :
  arg === 'key' ?
    Object.keys(obj) :
  arg === 'value' ?
    Object.keys(obj).map(key => obj[key]) :
  null;

Sinceramente, acho que uma das coisas que me motivou foi a falta de tratamento de erros. Ao adicionar a chamada 'return undefined', acredito que agora estamos permitindo que dados não esperados sejam enviados ao canal, o que obviamente estava ocorrendo no meu caso .

se você não quiser lidar com argumentos para o canal (e veja, eu acho que não é necessário na maioria dos casos), basta retornar o seguinte

       if (!obj)
          return undefined;
       return Object.keys(obj);

Algumas notas sobre como criar seu canal e página ou componente que usa esse canal

é que eu estava recebendo erros sobre 'nome_do_meu_pipe' não sendo encontrado

Use o comando 'ionic generate pipe' da CLI para garantir que os pipes.ts sejam criados e referenciados corretamente. verifique se você adicionou o seguinte à página mypage.module.ts.

import { PipesModule } from ‘…/…/pipes/pipes.module’;

(não tenho certeza se isso muda se você também tiver seu próprio custom_module, talvez seja necessário adicioná-lo ao custommodule.module.ts)

se você usou o comando 'ionic generate page' para criar sua página, mas decide usar essa página como sua página principal, lembre-se de remover a referência da página do app.module.ts (aqui está outra resposta que eu postei sobre esse https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

Na minha busca por respostas, há várias maneiras de exibir os dados no arquivo html, e eu não entendo o suficiente para explicar as diferenças. Você pode achar melhor usar um sobre o outro em certos cenários.

            <ion-item *ngFor="let myPost of posts">
                  <img src="https://somwhereOnTheInternet/{{myPost.ImageUrl}}"/>
                  <img src="https://somwhereOnTheInternet/{{posts[myPost].ImageUrl}}"/>
                  <img [src]="'https://somwhereOnTheInternet/' + myPost.ImageUrl" />
            </ion-item>

No entanto, o que funcionou que me permitiu exibir o valor e a chave foi o seguinte:

    <ion-list>  
      <ion-item *ngFor="let myPost of posts  | name_of_pip:'optional_Str_Varible'">

        <h2>Key Value = {{posts[myPost]}} 

        <h2>Key Name = {{myPost}} </h2>

      </ion-item>
   </ion-list>  

para fazer a chamada da API, parece que você precisa importar o HttpModule para app.module.ts

import { HttpModule } from '@angular/http';
 .
 .  
 imports: [
BrowserModule,
HttpModule,

e você precisa de Http na página da qual faz a ligação

import {Http} from '@angular/http';

Ao fazer a chamada da API, parece que você consegue acessar os dados filhos (os objetos ou as matrizes dentro da matriz) de 2 maneiras diferentes, que parecem funcionar

durante a chamada

this.http.get('https://SomeWebsiteWithAPI').map(res => res.json().anyChildren.OrSubChildren).subscribe(
        myData => {

ou quando você atribui os dados à sua variável local

posts: Array<String>;    
this.posts = myData['anyChildren'];

(não tenho certeza se essa variável precisa ser uma String de matriz, mas é isso que eu tenho agora. Pode funcionar como uma variável mais genérica)

E nota final, não foi necessário usar a biblioteca JSON embutida, no entanto, você pode encontrar essas 2 chamadas úteis para converter de um objeto em uma string e vice-versa

        var stringifiedData = JSON.stringify(this.movies);                  
        console.log("**mResults in Stringify");
        console.log(stringifiedData);

        var mResults = JSON.parse(<string>stringifiedData);
        console.log("**mResults in a JSON");
        console.log(mResults);

Espero que esta compilação de informações ajude alguém.


2
//Get solution for ng-repeat    
//Add variable and assign with Object.key

    export class TestComponent implements OnInit{
      objectKeys = Object.keys;
      obj: object = {
        "test": "value"
        "test1": "value1"
        }
    }
    //HTML
      <div *ngFor="let key of objectKeys(obj)">
        <div>
          <div class="content">{{key}}</div>
          <div class="content">{{obj[key]}}</div>
        </div>

1

Em JavaScript, isso se traduzirá em um objeto que, com dados, pode se parecer com isso

As interfaces no TypeScript são uma construção de tempo de desenvolvimento (apenas para ferramentas ... 0 impacto no tempo de execução). Você deve escrever o mesmo TypeScript que o seu JavaScript.


isso não é verdade, sry. O script type força você a escrever um código mais limpo. é muito mais fácil abstrair classes. que você simplesmente não tem. C ++ compila para alguns asm - o asm também não tem classes nem tipos, no entanto, você escreve c ++ diferente do seu código asm: P
YAMM

1

O dicionário é um objeto, não uma matriz. Acredito que ng-repeat requer uma matriz no Angular 2.

A solução mais simples seria criar um tubo / filtro que converta o objeto em uma matriz em tempo real. Dito isto, você provavelmente deseja usar uma matriz como o @basarat diz.


1

Se você tem es6-shimou seu tsconfig.jsondestino es6, você pode usar o ES6 Map para fazê-lo.

var myDict = new Map();
myDict.set('key1','value1');
myDict.set('key2','value2');

<div *ngFor="let keyVal of myDict.entries()">
    key:{{keyVal[0]}}, val:{{keyVal[1]}}
</div>

0

Defina MapValuesPipee implemente o PipeTransform :

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

@Pipe({name: 'mapValuesPipe'})
export class MapValuesPipe implements PipeTransform {
    transform(value: any, args?: any[]): Object[] {
        let mArray: 
        value.forEach((key, val) => {
            mArray.push({
                mKey: key,
                mValue: val
            });
        });

        return mArray;
    }
}

Adicione seu tubo no seu módulo de tubos. Isso é importante se você precisar usar o mesmo tubo em mais de um componente :

@NgModule({
  imports: [
    CommonModule
  ],
  exports: [
    ...
    MapValuesPipe
  ],
  declarations: [..., MapValuesPipe, ...]
})
export class PipesAggrModule {}

Em seguida, basta usar o pipe no seu html com *ngFor:

<tr *ngFor="let attribute of mMap | mapValuesPipe">

Lembre-se, você precisará declarar seu PipesModule no componente em que deseja usar o pipe:

@NgModule({
  imports: [
    CommonModule,
    PipesAggrModule
  ],
...
}
export class MyModule {}

0

Então, eu iria implementar minha própria função auxiliar, objLength (obj), que retorna apenas Object (obj) .keys.length. Mas quando o adicionei à função template * ngIf, meu IDE sugeriu objectKeys (). Eu tentei e funcionou. Após a declaração, ela parece ser oferecida pelo lib.es5.d.ts, então pronto!

Aqui está como eu o implementei (eu tenho um objeto personalizado que usa chaves geradas no servidor como um índice para arquivos que eu enviei):

        <div *ngIf="fileList !== undefined && objectKeys(fileList).length > 0">
          <h6>Attached Files</h6>
          <table cellpadding="0" cellspacing="0">
            <tr *ngFor="let file of fileList | keyvalue">
              <td><a href="#">{{file.value['fileName']}}</a></td>
              <td class="actions">
                <a title="Delete File" (click)="deleteAFile(file.key);">
                </a>
              </td>
            </tr>
          </table>
        </div>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.