Search code examples
javascriptangulartypescriptlifecycle

Angular - Expression Changed After It Has Been Checked


Well, i know there are a ton of questions about this exact problem, but even after reading some of them, i can't solve my problem. I tried the solution with the changeDetectionRef, but either i am doing it wrong, or it just doesn't work in my case.

To my problem: I am making a budget book, adding transactions and stuff, everything was doing great until i added a balance overview, which shows the overall balance on the account and the change that is going to be applied in the month. The balance is sent from the parent(budget-book.component) to the child (day.component) in a ngFor directive. I see, that the problem i got is, that the same value is sent to each child, received, changed and sent to the next child afterwards. I tried making a array in the parent, where each balanceChange is saved and instead sending the sum of the previous days, but this didn't work out either. I was also thinking about making an observable.

budget-book.component.html

[...]
<ul class="days">
      <app-day (addEvent)="addTransaction($event)" (removeEvent)="removeTransaction($event)" (balanceEvent)="updateBalance($event)"
               *ngFor="let day of days; index as i" [date]="sendDate(this.date.getFullYear(), this.date.getMonth(), i + 1)"
               [transactions]="getTransactions(i)" [balance]="balance"></app-day>
</ul>
[...]

budget-book.component.ts

[...]
balance: number;

ngOnInit(): void {
    [...]

    this.balance = 0;
}

[...]

updateBalance(balanceChange: number) {
    this.balance += balanceChange;
}

day.component.html

<div class="header-balance-container">
      <span [class.positive-number]="balance + balanceChange >= 0" [class.negative-number]="balance + balanceChange < 0" class="balance">{{balance + balanceChange | currency: 'EUR'}}</span>
      <span [class.positive-number]="balanceChange >= 0" [class.negative-number]="balanceChange < 0 " class="difference" >{{balanceChange | currency: 'EUR'}}</span>
</div>

day.component.ts

@Input() balance: number;
balanceChange: number;
@Output() balanceEvent = new EventEmitter<number>();

ngOnChanges(): void {
    this.balanceChange = 0;
    if (this.transactions) {
      this.transactions.forEach((transaction: Transaction) => {
        this.balanceChange += transaction.value;
      });
    }
    this.pushBalance();
    // here is where i tried adding the changeDetectionRef
}

[...]

pushBalance() {
    this.balanceEvent.emit(this.balanceChange);
}

I am on this problem since days and can't figure out, how to resolve. I'd be happy, if someone here could explain me, what i am missing and what my solution perspectives are.

EDIT:

A Service did it for me, for those interested in my stupidity, something simple as this solved the problem, which bothered me for days.

import {Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class BalanceService {
  balanceActivities: number[];

  constructor() {
  }

  setBalances(dayCount: number) {
    this.balanceActivities = new Array(dayCount);
  }

  addBalance(day: number, balanceActivity: number) {
    this.balanceActivities[day] = balanceActivity;
  }

  getBalanceToDate(day: number) {
    let balance = 0;
    this.balanceActivities.forEach((balanceActivity, index) => {
      balance += balanceActivity;
      if (index >= day) {
        return balance;
      }
    });
    return balance;
  }

  getBalance() {
    let balance = 0;
    this.balanceActivities.forEach((balanceActivity, index) => {
      balance += balanceActivity;
    });
    return balance;
  }
}

Solution

  • You can try to use DoCheck:

    class MyComponent implements OnInit, DoCheck {
      private _balance: number = 0;
      public balance: number;
    
      ngOnInit(): void {
      }
    
      ngDoCheck(): void {
        this.balance = this._balance;
      }
    
      updateBalance(balanceChange: number) {
        this._balance += balanceChange;
      }
    }
    

    But it doesn't seem like a very good solution. Depending on the task, you can use a service that will store the total balance, and you will not need to add it to the input / output.