Sonny Hansen
Sonny Hansen

Reputation: 630

My view does not update when i change my array in *ngFor

I'm trying to create some expansion panels with mat-table's inside, my problem is i have to resize my windows before my view will change. My data is loading fine and all but somehow my view does not update. My view where my expansion panels should be, is just all blank. Untill i click a button or resize my window. What can cause something like this?

In my ngOnInit() i call

this.getSale1(); 

.HTML:

<mat-accordion>
    <mat-expansion-panel *ngFor="let data of mySaleModelArray2 ">
        <mat-expansion-panel-header>
            <mat-panel-title>
                <h6 class="salepanelheadtext">Bar:</h6>{{data.name}}
            </mat-panel-title>
        <mat-panel-description>
            <h6 class="salepanelheadtext2">Total:</h6> {{data.total_sales}}
        </mat-panel-description>
        </mat-expansion-panel-header>
            <div class="example-container mat-elevation-z8">                                
                <mat-table #table [dataSource]="data.sales"  >
                <!-- PLU Column -->                                 
                <ng-container matColumnDef="pluNo">
                    <mat-header-cell *matHeaderCellDef >
                        #
                    </mat-header-cell>
                    <mat-cell *matCellDef="let salesdata"> 
                        {{salesdata.beerline}} 
                    </mat-cell>
                </ng-container>
                <!-- Name Column -->
                <ng-container matColumnDef="name">
                    <mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
                    <mat-cell *matCellDef="let salesdata"> 
                         {{salesdata.pluName}} 
                    </mat-cell>
                </ng-container>
                <!-- Sold_Count Column -->
                <ng-container matColumnDef="sold_count">
                    <mat-header-cell *matHeaderCellDef> 
                        QTY 
                    </mat-header-cell>
                    <mat-cell *matCellDef="let salesdata"> 
                        {{salesdata.sold_count}} 
                    </mat-cell>
                </ng-container>
                <!-- PLU Price Column -->
                <ng-container matColumnDef="pluPrice">
                    <mat-header-cell *matHeaderCellDef> Price </mat-header-cell>
                    <mat-cell *matCellDef="let salesdata"> 
                        {{salesdata.pluPrice}} 
                    </mat-cell>
                </ng-container>
                <!---->
                <ng-container matColumnDef="total_amount">
                    <mat-header-cell *matHeaderCellDef> 
                        Total 
                    </mat-header-cell>
                    <mat-cell *matCellDef="let salesdata"> 
                        {{salesdata.pluPrice * salesdata.sold_count}} 
                    </mat-cell>
                </ng-container>
    <mat-header-row *matHeaderRowDef="displayedColumns2"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns2;"></mat-row>
            </mat-table>
        </div>
    </mat-expansion-panel>
</mat-accordion>

.TS:

//Get data from Sale1List
getSale1() {        
    this.customersService.getSale1()
    .subscribe(
        dataList => {                    
                this.updateDataTable(dataList);
         }               
     )       
 }


updateDataTable(dataList) { 
for(var i = 0;i < dataList.length; i++){
    var saleData = <SaleDataModel>dataList[i];

   var mySaleModelTest = this.mySaleModelArray2.find(x => x.name == dataList[i].name);
   if(mySaleModelTest == null){
       //first time creating the object with the bar name
       var tempArray = Array();
       tempArray.push(saleData);

       this.mySaleModelArray2.push(new Sale1Model(dataList[i].name,dataList[i].pluPrice * dataList[i].sold_count,tempArray));

   }else{                           
       //changing the object with the bar name because it already exist
       mySaleModelTest.total_sales = mySaleModelTest.total_sales + dataList[i].pluPrice * dataList[i].sold_count;
       mySaleModelTest.sales.push(saleData);
   }                    
   }    
}

Upvotes: 6

Views: 20122

Answers (3)

Jacopo Sciampi
Jacopo Sciampi

Reputation: 3443

ChangeDetectorRef will do the trick.

Inject him in the constructor.

constructor(
  ... 
  private cdr: ChangeDetectorRef,
  ...
) { }

edit getSale1 like this in order to use the cdr:

getSale1() {        
    this.customersService.getSale1()
    .subscribe(
        dataList => {                    
                this.updateDataTable(dataList);
                this.cdr.detectChanges();
         }               
     )       
 }

But why I have to use the ChangeDetectorRef?

Angular, by default, use the ChangeDetectionStrategy.default that use its logic to "wake-up" the component for the render. More spec here: https://angular.io/api/core/ChangeDetectionStrategy

There are certain cases where this isn't enough. One case could be a very big nested *ngFor.

So why use the cdr?

As I said, there are some cases when Angular does not wake up its renderer. Since every situation is not the same, it's quite impossibile to define an absolute answer to this. What cdr.detectChanges() does, is to allow the method to inform the Angular's rendered to force the render of its component.html. In this way, no matter which strategy are you using (even if it's .onPush) the component will be re-rendered.

But be careful. you have to think what you are doing before implementing this. For example, re-render the html fire the ngOnChanges event. So you could enter an endless loop.

More info about cdr: https://angular.io/api/core/ChangeDetectorRef

Hope that this cleared out some doubts.

Upvotes: 11

user4676340
user4676340

Reputation:

Use a custom trackby function with a unique return statement (for instance, IDs are supposed to be unique, or you can track on the property you change)

*ngFor="let data of mySaleModelArray2; trackBy: customTB"
customTB(item, index) {
  return `${item.id}-${index}`;
}

Upvotes: 4

Amit Chigadani
Amit Chigadani

Reputation: 29795

If you are simply pushing object or data into an array, angular wont detect it as changed property (which is bad). Because you did not completely re-assign to it.

What you could do is :

  1. You can manually perform change detection
  2. Reassign entire array mySaleModelArray2 using temp array (not a good solution though).

Upvotes: 0

Related Questions