12

I am trying to iterate over the properties of an object using *ngFor but using in. When I try to do this

@Controller({
  selector: 'sample-controller',
  template: `
    <ul>
      <li *ngFor="let i in obj">
        <b>{{i}}</b>: {{obj[i]}}
      </li>
    </ul>`
})
class SampleController {
  obj = {a: 1, b: 2}
}

I get the error message:

Can't bind to 'ngForIn' since it isn't a known property of 'li'.

I have included FormsModule and BrowserModule in the imports section of the @NgModule for this component.

Is it possible to use ngForIn on li and if not is there an idiomatic alternative?

Daniel
  • 3,541
  • 3
  • 33
  • 46
Eli Sadoff
  • 7,173
  • 6
  • 33
  • 61
  • Is it [this problem](https://stackoverflow.com/a/34561169/2908576)? – HaveSpacesuit Jul 17 '17 at 18:42
  • @HaveSpacesuit No, I want to iterate over object properties explicitly, not iterate over an iterable like an array. – Eli Sadoff Jul 17 '17 at 18:43
  • 1
    https://medium.com/@jsayol/having-fun-with-angular-extending-ngfor-to-support-for-in-f30c724967ed – AT82 Jul 17 '17 at 18:50
  • @AJT_82 Ah ok, I was hoping I could get this done without writing a directive, but I'll just do that then. Thank you! – Eli Sadoff Jul 17 '17 at 18:52
  • 1
    Yeah, it is not included in the maaaagical Angular box unfortunately, you need to tinker a bit. Good luck and have fun with that :) One option is to use a custom pipe of course, just to mention that (more common) option. – AT82 Jul 17 '17 at 18:58
  • you want `let i of obj` NOT `let i in obj` – Rick Mar 13 '23 at 17:29

5 Answers5

9

As AJT_82 mentioned in comment you can create special directive for such purposes. It will be based on NgForOf<T> directive:

interface NgForInChanges extends SimpleChanges {
  ngForIn?: SimpleChange;
  ngForOf?: SimpleChange;
}

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

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • 1
    Those who stumble across this and have upgraded angular `ngForOf` no longer implements `OnChanges`. The trick is to just set the value of your `forOf` into the `super.ngForOf`. Full directive here https://stackoverflow.com/a/62086663/2103767 – bhantol May 29 '20 at 13:06
3

The easiest approach would be to turn your object into an array using Object.values() and Object.keys(). Check out this plunker for an example.

If you want access to the keys as well as the value you can include an index in your *ngFor.

Template:

<ul>
  <li *ngFor="let item of array; let index = index;">
    <b>{{keys[index]}}</b> value: {{item}}, index: {{index}}
  </li>
</ul>

Component TypeScript:

export class App {
  obj = {a: 1, b: 2}
  array = [];
  keys = [];
  constructor() {
  }

  ngOnInit() {
  this.array = Object.values(this.obj);
  this.keys = Object.keys(this.obj);
  }
}
Z. Bagley
  • 8,942
  • 1
  • 40
  • 52
1

Things have changed a bit (at Angular 9) so the answer by yurzi wont work but on the similar lines setting your 'forOf' value into 'super.ngForOf' will do the trick

import {   ContentChildren,   Directive,   Input,   IterableDiffers,   NgIterable,   OnChanges,   SimpleChanges,   TemplateRef,   ViewChildren,   ViewContainerRef } from "@angular/core";

import { NgForOf } from "@angular/common";

export interface Selectable<T> {   selected: boolean;   isActive: boolean; }

@Directive({   
 selector: "[optionsFor][[optionsForOf]]" }) 
 export class OptionsForDirective<T extends Selectable<any>,U extends NgIterable<T> = NgIterable<T>> 
     extends NgForOf<T, U> implements OnChanges {   

   _editMode = false;   
   _forOf: Array<Selectable<any>>;

  @Input()   
  set iefOptionsForOf(value: any) {
    this._forOf = value;
    console.log("set of: ", value);   
  }

  @Input()   
  set iefOptionsForEditMode(value: boolean) {
    this._editMode = value;
    console.log("set edit mode: ", value);   
  }

  constructor(
    public _viewContainer2: ViewContainerRef,
    public _templateRef2: TemplateRef<any>,
    public _differs2: IterableDiffers   ) {
    super(_viewContainer2, _templateRef2, _differs2);   
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes.optionsForOf) {
      const origNgForOf: Array<Selectable<any>> = this._forOf;
      let filtered = origNgForOf;
      if (origNgForOf) {
        filtered = origNgForOf.filter(s => {
          return this._editMode || s.isActive !== false;
        });
      }
      console.log("filtered", filtered);

      super.ngForOf = filtered as any;
    }   
  } 
}
bhantol
  • 9,368
  • 7
  • 44
  • 81
0

Object.keys and Object.values can be assigned to public properties on the component and then they are available as functions in a template. eg

//somewhere in the component
public objectKeys = Object.keys;
public objectValues = Object.values;

//somewhere in template
...*ngFor="let key of objectKeys(someObject)"...
przemo_li
  • 3,932
  • 4
  • 35
  • 60
0

An alternative would be to use the keyValue pipe.

<span *ngFor="let keyValue of myObject | keyValue">
    {{ keyValue.key }} - {{ keyValue.value }}
</span>
Robouste
  • 3,020
  • 4
  • 33
  • 55