Change detection doesnt trigger

Hi, in my ionic 5 app, i encountered strange problem with change detection. In my Cart page, i display list of products, and user can send order with fab button. Everything works, but when i hit the button, i want to reset all items, that are displayed. I do this in processTransacton method.
Problem is, that it doesnt trigger change detection, so old items doesnt disappear until i hit another button on the page. Any ideas what can cause this? My code looks like this:

<ion-content class="ion-padding">
  <ion-button expand="block" (click)="openProductList()">Přidat položku</ion-button>
  <div *ngFor="let product of selectedProducts">
    <app-cart-item [cartItem]="product" (remove)="removeProduct($event)">
    </app-cart-item>
  </div>
  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button color="success" (click)="processTransaction()">
      <ion-icon name="checkmark-outline"></ion-icon>
    </ion-fab-button>
  </ion-fab>
</ion-content>
  processTransaction(){
    this.productService.sendOrderCash([...this.selectedProducts]).subscribe(result=>{
      this.lastSelection=[...this.selectedProducts];
      this.selectedProducts=[];
      console.log(this.selectedProducts);
    });

  }

Can you provide minimal repo to reproduce

use below code
import import { ChangeDetectorRef} from ‘@angular/core’;
constructor(private cdr: ChangeDetectorRef)

processTransaction(){
this.productService.sendOrderCash([…this.selectedProducts]).subscribe(result=>{
this.lastSelection=[this.selectedProducts];
this.selectedProducts=[ ];
console.log(this.selectedProducts);
});
this.cdr.detectChanges()
}

Even if the code in the previous post “solves” the immediate issue, I recommend against deploying it until the actual underlying cause has been thoroughly investigated and found to be unresolvable in app code.

Ï agree with rapropos, but i cant think of reason why this happens - why ngFor doesnt rerender.

So any chance you can do as @indraraj26 is suggesting and post a minimal public repo that reproduces the issue?

The problem is its hard to reproduce this, i tried it in a clean project, but i was unable to do this. how can i post a minimal public repo?

whole code of my cart.ts looks like this:

import { Component, OnInit } from '@angular/core';
import { ProductService } from '../providers/product.service';

import { SettingsService } from '../providers/settings.service';
import { Settings } from '../models/settings.model';
import { ModalController, NavController, LoadingController, AlertController } from '@ionic/angular';
import { ProductListModalPage } from '../product-list-modal/product-list-modal.page';
import { RecentModalComponent } from '../recent-modal/recent-modal.component';
import { Storage } from '@ionic/storage';
import { from } from 'rxjs';

@Component({
  selector: 'app-cart',
  templateUrl: './cart.page.html',
  styleUrls: ['./cart.page.scss'],
})
export class CartPage implements OnInit {
  settings:Settings
  productList=[];
  selectedProducts=[];
  recentProducts=[];
  lastSelection=[];

  constructor(private productService:ProductService, private storage:Storage ,private alertCtrl:AlertController,private loadingCtrl:LoadingController,private settingsService:SettingsService,private navCtrl:NavController, private modalCtrl:ModalController) {
    this.settingsService.getSettings().subscribe(settings=>{
      this.settings=settings;
    });
  }

  ngOnInit() {
    this.productService.getProducts(this.settings.url).subscribe((productList)=>{
      this.productList=productList;
    });
    this.storage.get("lastSelection").then(lastSelectionString=>{
      if(lastSelectionString){
        this.lastSelection=JSON.parse(lastSelectionString);
      }
    });
    this.storage.get("recentList").then(recentProductsString=>{
      if(recentProductsString){
        this.recentProducts=JSON.parse(recentProductsString);
      }
    });
    console.log("onInit");
  }

  openRecentList(){
    this.modalCtrl.create({
      component:RecentModalComponent,componentProps:{recentList:this.recentProducts}
    }).then(modalEl=>{
      modalEl.onDidDismiss().then(selectedProduct=>{
        if(selectedProduct.data){
          if(selectedProduct.data="lastSelection"){
            this.selectedProducts=[...this.lastSelection].map(product=>({ ...product}));
            return;
          }
          this.addItemToArray(selectedProduct.data,this.selectedProducts);
        }
      });
      modalEl.present();
    });
  }

  openProductList(){
    this.modalCtrl.create({
      component:ProductListModalPage,componentProps:{productList:this.productList}
    }).then(modalEl=>{
      modalEl.onDidDismiss().then(selectedProduct=>{
        if(selectedProduct.data){
          this.addItemToArray(selectedProduct.data,this.selectedProducts);
          this.addItemToArray(selectedProduct.data,this.recentProducts);
          this.storage.set("recentList",JSON.stringify(this.recentProducts));
        }
      });
      modalEl.present();
    });
  }

  addItemToArray(item,array){
    if (!(array.find(selectedItem => selectedItem.ServiceID == item.ServiceID))) {
      item.quantity = 1;
      array.unshift(item);
      console.log(array);
    }
  }

  goBack(){
    this.navCtrl.navigateBack("/home");
  }

  processTransaction(){
    this.loadingCtrl.create({
      message:"Odesílání transakce"
    }).then(loadingEl=>{
      loadingEl.present();
      this.productService.sendOrderCash(this.settings.url,this.selectedProducts).subscribe(result=>{
        console.log(result);
        this.lastSelection=[...this.selectedProducts];
        this.selectedProducts.splice(0,1);
        loadingEl.dismiss();
        this.storage.set("lastSelection",JSON.stringify(this.selectedProducts));
        console.log(this.selectedProducts);
      },err=>{
        loadingEl.dismiss();
        this.alertCtrl.create({message:err,buttons:["OK"]}).then((alertEl)=>{
          alertEl.present();
        });
      });
    })
  }

  getTotalCost(){
    return this.selectedProducts.reduce((a,b)=>{
      return a+(b.Price*b.quantity);
    },0)
  }

  removeProduct(product){
    const index = this.selectedProducts.indexOf(product);
    console.log(index);
    this.selectedProducts.splice(index, 1);
  }

}

ant template file:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-button (click)="goBack()">
        <ion-icon name="arrow-back-outline"></ion-icon>
      </ion-button>
    </ion-buttons>
    <ion-title>Celkem: {{this.getTotalCost()}}Kč</ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="openRecentList()">
        <ion-icon name="flash"></ion-icon>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <ion-button expand="block" (click)="openProductList()">Přidat položku</ion-button>
  <div *ngFor="let product of selectedProducts">
    <app-cart-item [cartItem]="product" (remove)="removeProduct($event)">
    </app-cart-item>
  </div>

  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button color="success" (click)="processTransaction()" [disabled]="this.selectedProducts.length==0">
      <ion-icon name="checkmark-outline"></ion-icon>
    </ion-fab-button>
  </ion-fab>
</ion-content>

weird thing is, that removing and adding items works…

I have the same problem in my project.
In a list page the first loading has no rendering problems, when I go to filter the list the system takes several seconds to activate the change.
Forcing detectionChange is an interim solution, it seems absurd that it could be the only solution because this is not a standard angular procedure

Sadly, I’m going to say the same thing. I don’t see any way anybody can say anything constructive without access to a buildable repo that reliably reproduces the problem.

I’m sorry but I can’t distribute my client’s code.
this I share a section of the page:

<ion-content>
    <div class="news-list-header">
        <ion-toolbar *isLogged>
            <ion-segment (ionChange)="changeTab($event)" value="all" mode="md">
                <ion-segment-button value="all">{{ 'newsList.allSegment' | translate }}
                </ion-segment-button>
                <ion-segment-button value="favorites">{{ 'newsList.favorites' | translate }}
                </ion-segment-button>
                <hr>
            </ion-segment>
        </ion-toolbar>
        <ion-toolbar>
            <div class="filters">
                <form [formGroup]="filtersForm">
                    <ion-searchbar name="searchValue"
                                   formControlName="searchValue"
                                   inputmode="search"
                                   mode="ios"
                                   placeholder="{{ 'newsList.search' | translate }}"
                                   (ionClear)="removeKeyboard()"
                                   (ionCancel)="removeKeyboard()"
                                   (keyup)="onKey($event)">
                    </ion-searchbar>
                </form>
                <div *isLogged class="filter-btn">
                    <button class="lea-btn btn-primary ion-activatable ripple-parent" (click)="presentFilterModal()">
                        <span>
                            <ion-icon name="options-outline"></ion-icon>
                        </span>
                        <ion-ripple-effect></ion-ripple-effect>
                    </button>
                    <ion-badge *ngIf="filtersForm.get('topics').value.length > 0">
                        {{ filtersForm.get('topics').value.length }}</ion-badge>
                </div>
            </div>
        </ion-toolbar>
    </div>
    <ion-refresher slot="fixed"
                   pullFactor="0.5" pullMin="100"
                   (ionRefresh)="doRefresh($event)">
        <ion-refresher-content></ion-refresher-content>
    </ion-refresher>

    <div></div>
    <!-- Lista news -->
    <ion-list class="news-list"
              *ngIf="(favourite ? newsFavourite : news).length && !isSearching"
              lines="none">
        <ion-card *ngFor="let news1 of (favourite ? newsFavourite : news); index as i"
                  (click)="forward(news1)"
                  class="news lamboNews ion-activatable ripple-parent"
                  [ngClass]="{'top-news': news1.topNews, 'blue': news1?.codTopic?.includes('CORPORATE'), 'green': news1?.codTopic?.includes('PEOPLE')}">
            <div class="image" [style.background-image]="getSafeURL(news1.rollupURL)"></div>
            <ion-card-content>
                <div *ngIf="news1.topNews" class="date ion-text-left">
                    {{ news1.publishDate | date: 'dd MMMM yyyy'}}
                </div>
                <ion-chip *ngIf="!news1.topNews"
                          [ngClass]="{'blue': news1?.codTopic?.includes('CORPORATE'), 'green': news1?.codTopic?.includes('PEOPLE')}">
                    <ion-label>{{ news1.topic }}</ion-label>
                </ion-chip>
                <div class="title ion-text-left">
                    {{ news1.title }}
                </div>
                <div>

                </div>
            </ion-card-content>
            <ion-toolbar>
                <ion-chip slot="start"
                          *ngIf="news1.topNews"
                          [ngClass]="{'blue': news1?.codTopic?.includes('CORPORATE'), 'green': news1?.codTopic?.includes('PEOPLE')}">
                    <ion-label>{{ news1.topic }}</ion-label>
                </ion-chip>
                <div slot="start" *ngIf="!news1.topNews" class="date">
                    {{ news1.publishDate | date: 'dd MMMM yyyy'}}
                </div>
                <ion-buttons slot="end" *isLogged>
                    <ion-button (click)="onSetFavourite(news1)">
                        <ion-icon *ngIf="news1.favourite" name="star"></ion-icon>
                        <ion-icon *ngIf="!news1.favourite" name="star-outline"></ion-icon>
                    </ion-button>
                </ion-buttons>
            </ion-toolbar>
            <ion-ripple-effect></ion-ripple-effect>
        </ion-card>
    </ion-list>
    <!-- Skeleton per le news -->
    <ion-list *ngIf="isSearching && !(favourite ? newsFavourite : news).length">
        <ion-item *ngFor="let skeleton of [1, 2, 3]" class="skeleton">
            <ion-label>
                <p class="lea-skeleton-img" style="height: 150px;">
                    <ion-skeleton-text animated></ion-skeleton-text>
                </p>
                <p class="lea-skeleton-date" style="
                    position: absolute;
                    bottom: 60px;
                    height: 30px;
                    width: 100px;
                    left: 10px;
                ">
                    <ion-skeleton-text animated></ion-skeleton-text>
                </p>
                <p class="lea-skeleton-line">
                    <ion-skeleton-text animated></ion-skeleton-text>
                </p>
                <p class="lea-skeleton-line">
                    <ion-skeleton-text animated></ion-skeleton-text>
                </p>
            </ion-label>
        </ion-item>
    </ion-list>
    <!-- Se non ci sono news pubbliche -->
    <div class="ion-text-center ion-margin-top" *ngIf="noPublicNews && !news.length && !filtersForm.value.searchValue">
        <ion-chip>
            <ion-label>{{ 'newsList.noPublicNews' | translate }}</ion-label>
        </ion-chip>
    </div>
    <!-- No more public news -->
    <div class="ion-text-center" *ngIf="noMorePublicNews">
        <ion-chip>
            <ion-label>{{ 'newsList.noMorePublicNews' | translate }}</ion-label>
        </ion-chip>
    </div>
    <!-- Non ci sono news -->
    <div class="ion-text-center ion-margin-top"
         *ngIf="!isSearching && !(favourite ? newsFavourite : news).length && !filtersForm.value.searchValue">
        <ion-chip>
            <ion-label>{{ 'newsList.noNews' | translate }}</ion-label>
        </ion-chip>
    </div>
    <!-- Non ci sono news che soddisfano i criteri di ricerca -->
    <div class="ion-text-center ion-margin-top"
         *ngIf="!isSearching && !(favourite ? newsFavourite : news).length && filtersForm.value.searchValue">
        <ion-chip>
            <ion-label>{{ 'newsList.noNewsSatisfySearch' | translate }}</ion-label>
        </ion-chip>
    </div>
    <!-- Non ci sono più vecchie news -->
    <div class="ion-text-center"
         *ngIf="!isSearching && (favourite ? newsFavourite : news).length && noMoreOldNews && !filtersForm.value.searchValue">
        <ion-chip>
            <ion-label>{{ 'newsList.noMoreOldNews' | translate }}</ion-label>
        </ion-chip>
    </div>
    <!-- Infinite scroll -->
    <ion-infinite-scroll threshold="50px" (ionInfinite)="moreNews($event)">
        <ion-infinite-scroll-content
                loadingSpinner="bubbles"
                [loadingText]="'infiniteScroll.moreDaraLoading' | translate">
        </ion-infinite-scroll-content>
    </ion-infinite-scroll>
</ion-content>

A function where I have to force the changeDetection

moreNews(event?) {
    if (event) {
        event.target.complete();
    }
    if (this.isAuthenticated) {
        if (!this.favourite && this.news.length < this.newsCounter) {
            this.page++;
            this.getNews(this.favourite).pipe(
                map(news => news.filter((localNews: any) => !this.news.map(n => n.newsId).includes(localNews.newsId))),
                map(news => ([...this.news.concat(news)]))
            ).subscribe(data => {
                this.news = data;
                this.cd.detectChanges();
            });
        } else if (this.favourite && this.newsFavourite.length < this.newsFavouriteCounter) {
            this.pageFavourite++;
            this.getNews(this.favourite).subscribe(data => {
                this.setNews(data, this.favourite);
                this.cd.detectChanges();
            });
        } else {
            if (this.online) {
                this.getOldNews();
            } else {
                this.offlineChip = true;
                setTimeout(() => {
                    this.offlineChip = false;
                }, 3000);
            }
        }
    } else {
        if (this.online) {
            if (this.totalPublicNews === this.news.length) {
                this.noMorePublicNews = true;
            } else {
                this.getPublicNews(event);
            }
        } else {
            this.offlineChip = true;
        }
    }
}

Well, there are things like change detection and routing that I simply generally can’t say anything meaningful about without being able to actually run something that demonstrates the problematic behavior.

Can you extract enough into a complete runnable repository that you can provide public access to, like on GitHub or Gitlab?