简体   繁体   中英

Only first match from *ngIf directive returns ExpressionChangedAfterItHasBeenCheckedError

I have a nested ngif inside ngfor:

<ion-content>
    <ng-container *ngFor="let item of (results | async)">
      <ng-container *ngFor="let elements of item.bc; first as isFirst; index as i">
        <ng-container *ngIf="elements.Event.StartTime >= '2019-12-11T15:00:00' ?func():false">
          <ion-item>{{elements.Event.StartTime | date:'shortTime'}}</ion-item>
        </ng-container>
      </ng-container>
    </ng-container>
</ion-content>

I saw the resolution with a function from here: Show only first match from *ngIf expression

export class Tab4Page implements OnInit {

  results: Observable<any>;
  isFirstMatch = false;

  constructor(private channel: Service) {
  }

func() {
    if (this.isFirstMatch === false) {
      this.isFirstMatch = true;
      return true;
    } else {
      return false;
    }
  }

  ngOnInit() {

    this.results = this.channel.searchData();
  }

}

But it did not work for me. I get this error:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'.

Is there a better way to output only the first match? Or if not, can you please tell me how should I fix the ExpressionChanged error.

You can check for the first occurence inside your ts :

html

<ion-content>
    <ng-container *ngFor="let item of (results | async)">
      <ng-container *ngIf="findFirstElement(item.bc) as element">
          <ion-item>{{element.Event.StartTime | date:'shortTime'}}</ion-item>
        </ng-container>
    </ng-container>
</ion-content>

ts

  findFirstElement(item : any[]){
    if (!this.isFirstMatch && item.some(el => el.Event.StartTime >= '2019-12-11T15:00:00')){
      this.isFirstMatch = true ;
      return item.find(el => el.Event.StartTime >= '2019-12-11T15:00:00') ;
    }

    return null ;
  }

update

I think that isFirstMatch flag is no more needed because you want to render the first occurencce for each iteration (item) :

  findFirstElement(item : any[]){
      return item.find(el => el.Event.StartTime >= '2019-12-11T15:00:00') ;
  }

just do it in code:

this.firstMatches$ = this.results.pipe(
  map(results => // rx map operator
    results.map(item => // array map operator to transform
      item.bc.find(el => // into first found match
        el.Event.StartTime >= '2019-12-11T15:00:00')))
);


<ion-content>
  <ng-container *ngFor="let item of (firstMatches$ | async)">
    <ion-item>{{item.Event.StartTime | date:'shortTime'}}</ion-item>
  </ng-container>
</ion-content>

OR:

this.firstMatch$ = this.results.pipe(
  map(results => // rx map operator
    results.find(item => // find first item
      !!item.bc.find(el => // that contains a match
        el.Event.StartTime >= '2019-12-11T15:00:00'))),
  map(item => item.bc.find(el => el.Event.StartTime >= '2019-12-11T15:00:00')) // map into that element
);


<ion-content>
  <ng-container *ngIf="firstMatch$ | async as item">
    <ion-item>{{item.Event.StartTime | date:'shortTime'}}</ion-item>
  </ng-container>
</ion-content>

code is a lot more powerful than template, use it.

template is hard to read and understand. it's unclear to me if you were even trying to show only ONE ion-item (for the first matching element across all items) or multiple (for the first matching element in EACH item). shown both answers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM