6

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);
   }                    
   }    
}
5
  • Have you set the ChangeDetectionStrategy to OnPush maybe? Commented Nov 8, 2018 at 7:42
  • You can try using ChangeDetectorRef -> detectChanges Commented Nov 8, 2018 at 7:43
  • i Changed it from .OnPush till .Default. no change as it seems for me. Commented Nov 8, 2018 at 7:43
  • @JacopoSciampi this.updateDataTable(dataList); this.changeDetectorRef.detectChanges(); this will do the trick. still wondering why. but atleast the view worst so far Commented Nov 8, 2018 at 7:51
  • @SonnyHansen I'll post an asnwer for this. Just let me some minutes. Commented Nov 8, 2018 at 8:12

3 Answers 3

11

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.

Sign up to request clarification or add additional context in comments.

4 Comments

You are welcome. I struggled too for months understanding this back in the days. I'm really grateful that my answer helped you!
@JacopoSciampi appreciate your time and effort! Thanks for your answer
I know this has nothing to do with the problem and I really just need to let off some air. I've been struggeling for hours on this. For me this is the reason Angular is just so un-intuitive and I much rather use React. Time to change job I guess.... Good answer tho!
I have no idea on what your problem is, but a cool trick to always force angular to re-render is to assign a new object to the binded one. A Simple this.myBindedVariable = JSON.parse(JSON.stringify(myVar)); will do the trick!
4

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}`;
}

2 Comments

This should be the accepted answer. It solved my problem immediately and was quite simple.
Please note that the trackBy function interface seems to have been updated to: customTB(index, item) Source : angular.io/api/core/TrackByFunction
0

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).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.