Itérer sur un objet dans Angular


130

J'essaie de faire certaines choses dans Angular 2 Alpha 28 et j'ai un problème avec les dictionnaires et NgFor.

J'ai une interface en TypeScript qui ressemble à ceci:

interface Dictionary {
    [ index: string ]: string
}

En JavaScript, cela se traduira par un objet qui, avec des données, pourrait ressembler à ceci:

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

Je veux répéter ceci et essayer ceci:

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

Mais en vain, aucun des éléments ci-dessous n'a fonctionné non plus:

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

Dans tous les cas, j'obtiens des erreurs telles que "Jeton inattendu" ou "Impossible de trouver l'objet de support du tube 'iterableDiff'"

Qu'est-ce que j'oublie ici? N'est-ce plus possible? (La première syntaxe fonctionne dans Angular 1.x) ou la syntaxe est-elle différente pour l'itération sur un objet?


Qu'est-ce qu'un «dictionnaire»? Je n'ai jamais vu ni entendu ce terme dans un contexte JavaScript, Angular ou TypeScript. Y

Dictionnaire signifie une carte je pense, le terme n'est pas du tout utilisé dans le contexte JS mais en Python ou Ruby, il est utilisé.
Cesar Jr Rodriguez

2
Je pense que la réponse @bersling est maintenant la bonne réponse à cette question.
Joshua Kissoon

1
Veuillez mieux marquer la bonne réponse. bersling est correct
activedecay

Réponses:


86

Il semble qu'ils ne souhaitent pas prendre en charge la syntaxe de ng1.

Selon Miško Hevery ( référence ):

Les cartes n'ont pas d'ordre dans les clés et par conséquent, leur itération est imprévisible. Cela a été pris en charge dans ng1, mais nous pensons que c'était une erreur et ne sera pas pris en charge dans NG2

Le plan est d'avoir un tube mapToIterable

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

Donc, pour parcourir votre objet, vous devrez utiliser un "tube". Actuellement, il n'y a aucun tuyau implémenté qui fait cela.

Pour contourner le problème, voici un petit exemple qui itère sur les clés:

Composant:

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
J'essaie la même chose sur l'objet avec au keyfur numberet à valuemesure, stringmais l'erreur de lancement angulaire expression has changed after it was checked? pourquoi donc une idée?
Pardeep Jain

Ouais, ça m'arrive aussi. Et même si j'utilise aussi la solution de @ obsur.
user2294382

1
S'il vous plaît voir la réponse de bersling car il existe une solution triviale sur le dernier angular 7
activedecay

156

Réponse angulaire 6.1.0+

Utilisez le keyvaluetuyau intégré comme ceci:

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

ou comme ça:

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

où se mySortingFunctiontrouve dans votre .tsfichier, par exemple:

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

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

Vous n'aurez pas besoin de l'enregistrer dans aucun module, car les tuyaux angulaires fonctionnent hors de la boîte dans n'importe quel modèle.

Cela fonctionne également pour Javascript-Maps .


Vous devriez ajouter implements PipeTransformdans la définition de la classe (voir angular.io/guide/pipes#custom-pipes )
toioski

1
@toioski merci, je l'ai ajouté et mis à jour avec la nouvelle syntaxe de la boucle for.
bersling

Excellente réponse, utilisé ceci pour ngFor mon dictionnaire. J'ai dû faire keyValuePair.val [0] car mes valeurs se sont terminées [{}] et non {}
jhhoff02

1
Y a-t-il un avantage à cela sur juste return Object.keys(dict).map(key => ({key, val: dict[key]}))?
Justin Morgan le

Je n'en vois pas, en fait j'utiliserais votre chemin!
bersling le

72

essayez d'utiliser ce tuyau

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
Génial, et si je veux conserver la référence à la clé, je mapperai simplement un objet avec la clé et la valeur à la place. J'aimerais pouvoir marquer plusieurs réponses comme réponse acceptée, car c'est la solution à mon problème tandis que la réponse marquée est la réponse à ma question.
Rickard Staaf

1
@obscur - Si je fais ce qui précède maintenant, j'obtiens une erreur "l'expression a changé après avoir été vérifiée" en utilisant angular2.beta.0.0. Des pensées?
user2294382

C'est parce que pur: faux nécessite une détection de changementStratégie à injecter
Judson Terrell

1
Pourquoi le définir comme impur?
tom10271

Cela a bien fonctionné pour moi. La seule chose était que je ne pouvais pas utiliser # dans le ngFor. Utilisé let à la place.
MartinJH

19

En plus de la réponse de @ obscur, voici un exemple de la façon dont vous pouvez accéder à la fois à keyet à valuepartir de @View.

Tuyau:

@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;
        });
    }

}

Vue:

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

Donc, si l'objet devait ressembler à:

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

Le résultat généré serait:

Prénom: Daario
Nom: Naharis

Prénom: Victarion
Nom: Greyjoy

Prénom: Quentyn
Nom: Ball


2
juste une chose à mentionner, vous devez changer la vue: comme <li *ngFor="let u of myObject | keyValueFilter">First Name: {{u.key}} <br> Last Name: {{u.value}}</li>. +1 de moi.
sib10

Le code à l'intérieur de votre fonction de carte pourrait être simplifié comme return { 'key' : key, 'value' : value[key] };
suit

17

Mise à jour: Angular fournit maintenant le tuyau pour couper l'objet json via keyvalue:

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

DEMO DE TRAVAIL , et pour plus de détails Lire


Auparavant (pour les versions plus anciennes): Jusqu'à présent, la réponse la meilleure / la plus courte que j'ai trouvée est (sans filtre de canalisation ni fonction personnalisée du côté composant)

Côté composant:

objectKeys = Object.keys;

Côté modèle:

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

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

</div>

DÉMO DE TRAVAIL


1
let item of myDict | keyvaluecela a résolu mon problème.
Silambarasan RD

13

Ajout à l' excellente réponse de SimonHawesome . J'ai fait une version succincte qui utilise certaines des nouvelles fonctionnalités dactylographiées. Je me rends compte que la version de SimonHawesome est intentionnellement verbeuse pour expliquer les détails sous-jacents. J'ai également ajouté une vérification anticipée afin que le tuyau fonctionne pour les valeurs fausses . Par exemple, si la carte est null.

Notez que l'utilisation d'une transformation d'itérateur (comme cela est fait ici) peut être plus efficace car nous n'avons pas besoin d'allouer de la mémoire pour un tableau temporaire (comme cela est fait dans certaines des autres réponses).

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
J'adore ce fil, avec un commentaire qui se construit sur un autre! J'étais sur le point d'écrire la même chose quand j'ai vu votre code
David

3
la seule chose dans cette solution: elle devrait implémenterPipeTransform
iRaS

@iRaS Bon point. J'ai mis à jour ma réponse. Je renvoie également undefined au lieu de null.
Frederik Aalund

9

Voici une variante de certaines des réponses ci-dessus qui prend en charge plusieurs transformations (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;
  }
}

Usage

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: falseest vraiment important pour les réflexions instantanées.
Fırat KÜÇÜK

4

J'ai eu un problème similaire, j'ai construit quelque chose pour les objets et les cartes.

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
Cela fonctionne bien, sauf que dans TypeScript, vous devez ajouter implements PipeTransformà la définition de classe
GeorgDangl

3

Angular 2.x && Angular 4.x ne le prennent pas en charge par défaut

Vous pouvez utiliser ces deux canaux pour itérer soit par clé, soit par valeur .

Tuyau de clés:

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)
  }
}

Tube de valeurs:

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])
  }
}

Comment utiliser:

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

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

2

Si quelqu'un se demande comment travailler avec un objet multidimensionnel, voici la solution.

Supposons que nous ayons l'objet suivant en service

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;
}

dans le composant ajouter la fonction suivante

challenges;

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

enfin en vue faire suivre

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

2

Je me suis déchiré les cheveux en essayant d'analyser et d'utiliser les données renvoyées par une requête JSON / un appel API. Je ne sais pas exactement où je me suis trompé, j'ai l'impression d'avoir encerclé la réponse pendant des jours, chassant divers codes d'erreur tels que:

"Impossible de trouver l'objet de prise en charge du tube 'iterableDiff'"

"Le tableau TYpe générique nécessite un ou plusieurs arguments"

Erreurs d'analyse JSON, et je suis sûr que d'autres

Je suppose que j'ai juste eu la mauvaise combinaison de correctifs.

Voici donc un résumé des pièges et des choses à rechercher.

Tout d'abord, vérifiez le résultat de vos appels API, vos résultats peuvent être sous la forme d'un objet, d'un tableau ou d'un tableau d'objets.

Je ne vais pas trop y entrer, il suffit de dire que l'erreur d'origine de l'OP de ne pas être itérable est généralement due au fait que vous essayez d'itérer un objet, pas un tableau.

Voici certains de mes résultats de débogage montrant les variables des tableaux et des objets

Ainsi, comme nous souhaitons généralement itérer sur notre résultat JSON, nous devons nous assurer qu'il se présente sous la forme d'un tableau. J'ai essayé de nombreux exemples, et sachant peut-être ce que je sais maintenant, certains d'entre eux fonctionneraient en fait, mais l'approche que j'ai suivie était en effet d'implémenter un tube et le code que j'ai utilisé était celui publié par 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;

Honnêtement, je pense que l'une des choses qui m'obtenait était le manque de gestion des erreurs, en ajoutant l'appel `` return undefined '', je pense que nous permettons maintenant d'envoyer des données non attendues au tuyau, ce qui se produisait évidemment dans mon cas. .

si vous ne voulez pas traiter d'argument dans le tube (et regardez, je ne pense pas que ce soit nécessaire dans la plupart des cas), vous pouvez simplement retourner ce qui suit

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

Quelques notes sur la création de votre tube et de votre page ou composant qui utilise ce tube

est-ce que je recevais des erreurs indiquant que 'name_of_my_pipe' était introuvable

Utilisez la commande 'ionic generate pipe' de l'interface de ligne de commande pour vous assurer que les modules de canalisation.ts sont créés et référencés correctement. assurez-vous d'ajouter ce qui suit à la page mypage.module.ts.

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

(je ne sais pas si cela change si vous avez également votre propre custom_module, vous devrez peut-être également l'ajouter à custommodule.module.ts)

si vous avez utilisé la commande 'ionic generate page' pour créer votre page, mais que vous décidez d'utiliser cette page comme page principale, n'oubliez pas de supprimer la référence de page de app.module.ts (voici une autre réponse que j'ai publiée concernant ce https: / /forum.ionicframework.com/t/solved-pipe-not-found-in-custom-component/95179/13?u=dreaser

Dans ma recherche de réponses, il existe un certain nombre de façons d'afficher les données dans le fichier html, et je ne comprends pas assez pour expliquer les différences. Vous trouverez peut-être préférable de les utiliser les uns par rapport aux autres dans certains scénarios.

            <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>

Cependant, ce qui a fonctionné qui m'a permis d'afficher à la fois la valeur et la clé était le suivant:

    <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>  

pour faire l'appel d'API, il semble que vous deviez importer HttpModule dans app.module.ts

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

et vous avez besoin de Http dans la page à partir de laquelle vous passez l'appel

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

Lors de l'appel à l'API, vous semblez être en mesure d'accéder aux données enfants (les objets ou les tableaux dans le tableau) de 2 manières différentes, qui semblent fonctionner

soit pendant l'appel

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

ou lorsque vous affectez les données à votre variable locale

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

(je ne sais pas si cette variable doit être une chaîne de tableau, mais c'est ce que je l'ai actuellement. Cela peut fonctionner comme une variable plus générique)

Et note finale, il n'était pas nécessaire d'utiliser la bibliothèque JSON intégrée, mais vous pouvez trouver ces 2 appels pratiques pour la conversion d'un objet en une chaîne et 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);

J'espère que cette compilation d'informations aidera quelqu'un.


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

En JavaScript, cela se traduira par un objet qui, avec des données, pourrait ressembler à ceci

Les interfaces dans TypeScript sont une construction de temps de développement (purement pour l'outillage ... 0 impact d'exécution). Vous devez écrire le même TypeScript que votre JavaScript.


ce n'est pas vrai, sry. type script vous oblige à écrire du code plus propre. il est beaucoup plus facile d'abstraire des classes. ce que vous n'avez tout simplement pas. C ++ compile à certains asm - asm n'a pas non plus de classes ou même de types, néanmoins vous écrivez différents c ++ puis votre code asm: P
YAMM

1

Le dictionnaire est un objet, pas un tableau. Je crois que ng-repeat nécessite un tableau dans Angular 2.

La solution la plus simple serait de créer un tube / filtre qui convertit l'objet en un tableau à la volée. Cela dit, vous voudrez probablement utiliser un tableau comme le dit @basarat.


1

Si vous avez es6-shimou votre tsconfig.jsoncible es6, vous pouvez utiliser ES6 Map pour le faire.

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

Définissez MapValuesPipeet implémentez 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;
    }
}

Ajoutez votre pipe dans votre module pipes. Ceci est important si vous devez utiliser le même tuyau dans plusieurs composants :

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

Ensuite, utilisez simplement le tube dans votre html avec *ngFor:

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

N'oubliez pas que vous devrez déclarer votre PipesModule dans le composant où vous souhaitez utiliser le tube:

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

0

J'allais donc implémenter ma propre fonction d'assistance, objLength (obj), qui ne renvoie que Object (obj) .keys.length. Mais ensuite, quand je l'ajoutais à ma fonction template * ngIf, mon IDE a suggéré objectKeys (). Je l'ai essayé et cela a fonctionné. Suite à sa déclaration, il semble être proposé par lib.es5.d.ts, alors voilà!

Voici comment je l'ai implémenté (j'ai un objet personnalisé qui utilise des clés générées côté serveur comme index pour les fichiers que j'ai téléchargés):

        <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>
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.