diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 63187c05c..611344533 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -43,6 +43,10 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public user: User; public withdrawalRatePerMonth: Big; public withdrawalRatePerYear: Big; + public projectedWithdrawalRatePerMonth: Big; + public projectedWithdrawalRatePerYear: Big; + public projectedRetirementDate: Date; + public projectedAnnualInterestRate: number; private unsubscribeSubject = new Subject(); @@ -211,6 +215,21 @@ export class GfFirePageComponent implements OnDestroy, OnInit { }); } + public onCalculationComplete(event: { + projectedTotalAmount: number; + retirementDate?: Date; + annualInterestRate: number; + }) { + if (event) { + this.projectedRetirementDate = event.retirementDate; + this.projectedAnnualInterestRate = event.annualInterestRate; + + this.calculateProjectedWithdrawalRates(event.projectedTotalAmount); + + this.changeDetectorRef.markForCheck(); + } + } + public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); @@ -225,4 +244,18 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12); } } + + private calculateProjectedWithdrawalRates(projectedTotalAmount: number) { + if ( + projectedTotalAmount !== undefined && + projectedTotalAmount !== null && + this.user?.settings?.safeWithdrawalRate + ) { + this.projectedWithdrawalRatePerYear = new Big(projectedTotalAmount).mul( + this.user.settings.safeWithdrawalRate + ); + this.projectedWithdrawalRatePerMonth = + this.projectedWithdrawalRatePerYear.div(12); + } + } } diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index ce51717fa..69e8ca5cc 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -28,6 +28,7 @@ [retirementDate]="user?.settings?.retirementDate" [savingsRate]="user?.settings?.savingsRate" (annualInterestRateChanged)="onAnnualInterestRateChange($event)" + (calculationComplete)="onCalculationComplete($event)" (projectedTotalAmountChanged)="onProjectedTotalAmountChange($event)" (retirementDateChanged)="onRetirementDateChange($event)" (savingsRateChanged)="onSavingsRateChange($event)" @@ -131,6 +132,58 @@ [value]="user?.settings?.safeWithdrawalRate" />. } + @if (projectedRetirementDate) { +
+ By +   + {{ projectedRetirementDate | date: 'MMMM yyyy' }} + , this is projected to increase to +   + +   + per year +   + or +   + +   + per month + , +   + assuming a +   + +   + annual interest rate. +
+ } } diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index df7ca79fa..a4682f79f 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -17,6 +17,7 @@ import { OnChanges, OnDestroy, Output, + SimpleChanges, ViewChild } from '@angular/core'; import { @@ -91,6 +92,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @Output() projectedTotalAmountChanged = new EventEmitter(); @Output() retirementDateChanged = new EventEmitter(); @Output() savingsRateChanged = new EventEmitter(); + @Output() calculationComplete = new EventEmitter<{ + projectedTotalAmount: number; + retirementDate: Date | undefined; + annualInterestRate: number; + }>(); @ViewChild('chartCanvas') chartCanvas: ElementRef; @@ -157,7 +163,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); } - public ngOnChanges() { + public ngOnChanges(_changes?: SimpleChanges) { if (isNumber(this.fireWealth) && this.fireWealth >= 0) { this.calculatorForm.setValue( { @@ -195,6 +201,9 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ); this.calculatorForm.get('principalInvestmentAmount').disable(); + // Emit initial calculation payload + this.emitCalculationComplete(); + this.changeDetectorRef.markForCheck(); }); } @@ -328,6 +337,9 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } this.isLoading = false; + + // Emit after (re)initialization to keep consumers updated + this.emitCalculationComplete(); } private getChartData() { @@ -345,7 +357,8 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { // Calculate retirement date // if we want to retire at month x, we need the projectedTotalAmount at month x-1 - const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 }); + const safeRetirementDate = this.getRetirementDate() ?? this.DEFAULT_RETIREMENT_DATE; + const lastPeriodDate = sub(safeRetirementDate, { months: 1 }); const yearsToRetire = lastPeriodDate.getFullYear() - currentYear; // Time @@ -476,4 +489,18 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }) ); } + + private emitCalculationComplete() { + // Use current form values and calculated helpers + const projectedTotalAmount = Number(this.getProjectedTotalAmount()); + const retirementDate = this.getRetirementDate(); + const annualInterestRate = this.calculatorForm.get('annualInterestRate') + .value; + + this.calculationComplete.emit({ + projectedTotalAmount, + retirementDate, + annualInterestRate + }); + } }