Why am I getting a NG0901 error when I am not performing any data change?

Viewed 4214

In the following code segment, I am iterating over ardene.section1.items. I have logged this array to the console to verify that it does exist. The "BEFORE FOR LOOP" test is printed to the screen but the "INSIDE FOR LOOP" test is never printed. And I get and NG0901 error which Angular tells me is a ngDoCheck error.

<div class="row mt-4">
BEFORE FOR LOOP
<div class="col text-center col-12 col-md-3 mb-4 mb-md-0"
    *ngFor="let item of ardene.section1.items">
    INSIDE FOR LOOP
    <img class="img-fluid" 
        src=`{{ardene.assetPath}}{{item.img}}`>
    <div>
        <h5 class="text-center fw-bold mt-2">{{item.name}}</h5>
        <h6 class="text-center">{{item.description}}<br></h6>
        <a>
            <h6 class="fw-bold">Shop Now</h6>
        </a>
    </div>
</div>    

The component code that produces ardene.section1.items is shown below.

  constructor(
    private store:Store<{ardeneState}>
  ) { }

  ngOnInit(): void {
    this.subscribeToReduxStores();
  }

private subscribeToReduxStores = () => {
const ardene$ = this.store.select(state => {
  return state.ardeneState
})

ardene$.subscribe(ardene => {
  this.ardene = ardene;

  console.log('ardene.section1.heading', this.ardene.section1.heading)
  console.log('ardene.section1.items', this.ardene.section1.items)
  console.log('ardene', this.ardene)
})
  }

The data originates from the redux store:

const initialState = {
  assetPath: 'app/category/ladies-apparel/ardene/assets/img/',

  section1: {
    heading: 'New Denim Guide',
    subheading: "EVERYBODY'S OBSESSING OVER THESE STYLES",
    items: {
      img: 'ardene-6.jpg',
      name: 'Mom Jeans',
      description: 'The vintage silhouette',
      link: '',
    },
  },
};

Error:

ERROR Error: NG0901
at e.find (main.10edf50a2ef1763e.js:1:146206)
at h.ngDoCheck (main.10edf50a2ef1763e.js:1:43158)
at Vs (main.10edf50a2ef1763e.js:1:56860)
at Bs (main.10edf50a2ef1763e.js:1:56661)
at yr (main.10edf50a2ef1763e.js:1:56381)
at ro (main.10edf50a2ef1763e.js:1:76106)
at Gy (main.10edf50a2ef1763e.js:1:82951)
at _y (main.10edf50a2ef1763e.js:1:76845)
at ro (main.10edf50a2ef1763e.js:1:76856)
at Hy (main.10edf50a2ef1763e.js:1:76404)
2 Answers

You are getting error because you are trying to iterate over an object instead of an array when using *ngFor.

// ardene.section1.items is an object and not an array
*ngFor="let item of ardene.section1.items"

NgFor only supports binding to Iterables such as Arrays. If you want to iterate over an object, you can make use of KeyValuePipe which

Transforms Object or Map into an array of key value pairs.

Looking at your data structure I don't see any reason for you to use *ngFor, you can simply access the object properties as:

{{ardene?.section1?.items?.img}}
{{ardene?.section1?.items?.name}}
{{ardene?.section1?.items?.description}}

If you want to avoid repetition of ardene?.section1?.items, you can try to do something as below:

<ng-container *ngIf="ardene?.section1?.items as item">
  <img class="img-fluid" src=`{{ardene.assetPath}}{{item?.img}}`>
  <div>
    <h5 class="text-center fw-bold mt-2">{{item?.name}}</h5>
    <h6 class="text-center">{{item?.description}}<br></h6>
    <a>
      <h6 class="fw-bold">Shop Now</h6>
    </a>
  </div>
</ng-container>

If subscription really does return some items for you then syntax could go something more like this:

<ng-container *ngIf="(ardene$ | async)?.section1 as item">
  <div class="row mt-4">
    BEFORE FOR LOOP
    <br />
    heading: {{ item?.heading }}
    <br />
    subheadting: {{ item?.subheading }}
    <div class="col text-center col-12 col-md-3 mb-4 mb-md-0">
      <img
        class="img-fluid"
        src="`{{ (ardene$ | async).assetPath }}{{ item?.items?.img }}`"
      />
      <div>
        <h5 class="text-center fw-bold mt-2">{{ item?.items?.name }}</h5>
        <h6 class="text-center">{{ item?.items?.description }}<br /></h6>
        <a>
          <h6 class="fw-bold">Shop Now</h6>
        </a>
      </div>
    </div>
  </div>
</ng-container>
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, AfterViewInit {

  ardene$: Observable<any> | undefined;
  ardeneObservable = new ReplaySubject<any>(1);


  ngAfterViewInit() {
    const ardene = {
      assetPath: 'app/category/ladies-apparel/ardene/assets/img/',

      section1: {
        heading: 'New Denim Guide',
        subheading: "EVERYBODY'S OBSESSING OVER THESE STYLES",
        items: {
          img: 'ardene-6.jpg',
          name: 'Mom Jeans',
          description: 'The vintage silhouette',
          link: '',
        },
      },
    };

    this.ardeneObservable.next(ardene);
  }

  ngOnInit() {
    this.ardene$ = this.ardeneObservable.pipe((data) => {
      return data;
    });
  }
}

Here is a working example: https://stackblitz.com/edit/angular-ivy-i872hc?file=src%2Fapp%2Fapp.component.ts

Related