Browse Source

feat(retirement-date): Use retirementDate and projectedTotalAmount both as input fields

pull/1748/head
Robbert Coeckelbergh 3 years ago
committed by Thomas
parent
commit
191ef6ec1a
  1. 10
      apps/api/src/app/user/update-user-setting.dto.ts
  2. 30
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  3. 7
      apps/client/src/app/pages/portfolio/fire/fire-page.html
  4. 4
      apps/client/src/locales/messages.de.xlf
  5. 6
      apps/client/src/locales/messages.nl.xlf
  6. 4
      apps/client/src/locales/messages.xlf
  7. 4
      libs/common/src/lib/interfaces/user-settings.interface.ts
  8. 44
      libs/ui/src/lib/fire-calculator/fire-calculator.component.html
  9. 225
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
  10. 2
      libs/ui/src/lib/fire-calculator/fire-calculator.module.ts
  11. 2
      libs/ui/src/lib/fire-calculator/fire-calculator.service.ts
  12. 1
      package.json
  13. 1
      tsconfig.base.json

10
apps/api/src/app/user/update-user-setting.dto.ts

@ -48,13 +48,21 @@ export class UpdateUserSettingDto {
@IsOptional() @IsOptional()
locale?: string; locale?: string;
@IsString()
@IsOptional()
retirementDate?: string;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
savingsRate?: number; savingsRate?: number;
@IsNumber() @IsNumber()
@IsOptional() @IsOptional()
targetNetWorth?: number; projectedTotalAmount?: number;
@IsBoolean()
@IsOptional()
projectedTotalAmountSet?: boolean;
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN']) @IsIn(<ViewMode[]>['DEFAULT', 'ZEN'])
@IsOptional() @IsOptional()

30
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts

@ -109,9 +109,35 @@ export class FirePageComponent implements OnDestroy, OnInit {
}); });
} }
public onTargetNetWorthChanged(targetNetWorth: number) { public onRetirementDateChange(retirementDate: Date) {
this.dataService this.dataService
.putUserSetting({ targetNetWorth }) .putUserSetting({
projectedTotalAmount: null,
projectedTotalAmountSet: false,
retirementDate: retirementDate.toISOString()
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.userService.remove();
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
this.changeDetectorRef.markForCheck();
});
});
}
public onProjectedTotalAmountChanged(projectedTotalAmount: number) {
this.dataService
.putUserSetting({
projectedTotalAmount,
projectedTotalAmountSet: true,
retirementDate: null
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.userService.remove(); this.userService.remove();

7
apps/client/src/app/pages/portfolio/fire/fire-page.html

@ -17,10 +17,13 @@
[fireWealth]="fireWealth?.toNumber()" [fireWealth]="fireWealth?.toNumber()"
[hasPermissionToUpdateUserSettings]="hasPermissionToUpdateUserSettings" [hasPermissionToUpdateUserSettings]="hasPermissionToUpdateUserSettings"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[projectedTotalAmount]="user?.settings?.projectedTotalAmount"
[projectedTotalAmountSet]="user?.settings?.projectedTotalAmountSet"
[retirementDate]="user?.settings?.retirementDate"
[savingsRate]="user?.settings?.savingsRate" [savingsRate]="user?.settings?.savingsRate"
[targetNetWorth]="user?.settings?.targetNetWorth" (projectedTotalAmountChanged)="onProjectedTotalAmountChanged($event)"
(retirementDateChanged)="onRetirementDateChange($event)"
(savingsRateChanged)="onSavingsRateChange($event)" (savingsRateChanged)="onSavingsRateChange($event)"
(targetNetWorthChanged)="onTargetNetWorthChanged($event)"
></gf-fire-calculator> ></gf-fire-calculator>
</div> </div>
</div> </div>

4
apps/client/src/locales/messages.de.xlf

@ -3515,10 +3515,10 @@
</trans-unit> </trans-unit>
<trans-unit id="7383cd391b1967e03f0636c231d20f036d5c37ee" datatype="html"> <trans-unit id="7383cd391b1967e03f0636c231d20f036d5c37ee" datatype="html">
<source>Retirement Date</source> <source>Retirement Date</source>
<target state="new">Pensionierungsdatum</target> <target state="translated">Pensionierungsdatum</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
</body> </body>

6
apps/client/src/locales/messages.nl.xlf

@ -2434,7 +2434,7 @@
<target state="translated">Verwacht totaalbedrag</target> <target state="translated">Verwacht totaalbedrag</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">52</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1054498214311181686" datatype="html"> <trans-unit id="1054498214311181686" datatype="html">
@ -3515,10 +3515,10 @@
</trans-unit> </trans-unit>
<trans-unit id="7383cd391b1967e03f0636c231d20f036d5c37ee" datatype="html"> <trans-unit id="7383cd391b1967e03f0636c231d20f036d5c37ee" datatype="html">
<source>Retirement Date</source> <source>Retirement Date</source>
<target state="new">Pensioen Datum</target> <target state="translated">Pensioen Datum</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
</body> </body>

4
apps/client/src/locales/messages.xlf

@ -2193,7 +2193,7 @@
<source>Projected Total Amount</source> <source>Projected Total Amount</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">52</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1054498214311181686" datatype="html"> <trans-unit id="1054498214311181686" datatype="html">
@ -3157,7 +3157,7 @@
<source>Retirement Date</source> <source>Retirement Date</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">65</context> <context context-type="linenumber">32</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
</body> </body>

4
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -11,6 +11,8 @@ export interface UserSettings {
language?: string; language?: string;
locale?: string; locale?: string;
savingsRate?: number; savingsRate?: number;
targetNetWorth?: number; retirementDate?: string;
projectedTotalAmount?: number;
projectedTotalAmountSet?: boolean;
viewMode?: ViewMode; viewMode?: ViewMode;
} }

44
libs/ui/src/lib/fire-calculator/fire-calculator.component.html

@ -29,41 +29,35 @@
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Investment Horizon</mat-label> <mat-label i18n>Retirement Date</mat-label>
<input formControlName="time" matInput type="number" /> <input
<span class="ml-2" i18n matTextSuffix>years</span> formControlName="retirementDate"
matInput
[matDatepicker]="dp"
/>
<mat-datepicker-toggle
matIconSuffix
[for]="dp"
></mat-datepicker-toggle>
<mat-datepicker
#dp
panelClass="example-month-picker"
startView="multi-year"
(monthSelected)="setMonthAndYear($event, dp)"
>
</mat-datepicker>
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Target Net Worth</mat-label> <mat-label i18n>Projected Total Amount</mat-label>
<input <input
formControlName="retirementNetWorth" formControlName="projectedTotalAmount"
matInput matInput
step="100" step="100"
type="number" type="number"
/> />
<span class="ml-2" matTextSuffix>{{ currency }}</span> <span class="ml-2" matTextSuffix>{{ currency }}</span>
</mat-form-field> </mat-form-field>
<gf-value
*ngIf="!targetNetWorth"
i18n
size="large"
[currency]="currency"
[isCurrency]="true"
[locale]="locale"
[value]="projectedTotalAmount"
>Projected Total Amount</gf-value
>
<gf-value
*ngIf="targetNetWorth"
i18n
size="large"
[isCurrency]="false"
[locale]="locale"
[value]="retirementDate"
>Retirement Date</gf-value
>
</form> </form>
</div> </div>
<div class="col-md-9 text-center"> <div class="col-md-9 text-center">

225
libs/ui/src/lib/fire-calculator/fire-calculator.component.ts

@ -13,6 +13,7 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms'; import { FormBuilder, FormControl } from '@angular/forms';
import { MatDatepicker } from '@angular/material/datepicker';
import { import {
getTooltipOptions, getTooltipOptions,
transformTickToAbbreviation transformTickToAbbreviation
@ -28,18 +29,62 @@ import {
Tooltip Tooltip
} from 'chart.js'; } from 'chart.js';
import * as Color from 'color'; import * as Color from 'color';
import { add, format, getMonth } from 'date-fns'; import { add, getMonth, sub } from 'date-fns';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { FireCalculatorService } from './fire-calculator.service'; import { FireCalculatorService } from './fire-calculator.service';
import { getDateFormatString } from '@ghostfolio/common/helper'; import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE
} from '@angular/material/core';
import {
MomentDateAdapter,
MAT_MOMENT_DATE_ADAPTER_OPTIONS
} from '@angular/material-moment-adapter';
// Depending on whether rollup is used, moment needs to be imported differently.
// Since Moment.js doesn't have a default export, we normally need to import using the `* as`
// syntax. However, rollup creates a synthetic default module and we thus need to import it using
// the `default as` syntax.
import * as _moment from 'moment';
// tslint:disable-next-line:no-duplicate-imports
import { default as _rollupMoment, Moment } from 'moment';
const moment = _rollupMoment || _moment;
// See the Moment.js docs for the meaning of these formats:
// https://momentjs.com/docs/#/displaying/format/
export const MY_FORMATS = {
parse: {
dateInput: 'MM/YYYY'
},
display: {
dateInput: 'MM/YYYY',
monthYearLabel: 'MMM YYYY',
dateA11yLabel: 'LL',
monthYearA11yLabel: 'MMMM YYYY'
}
};
export const DEFAULT_RETIREMENT_DATE = add(new Date(), { years: 10 });
@Component({ @Component({
selector: 'gf-fire-calculator', selector: 'gf-fire-calculator',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './fire-calculator.component.html', templateUrl: './fire-calculator.component.html',
styleUrls: ['./fire-calculator.component.scss'] styleUrls: ['./fire-calculator.component.scss'],
providers: [
// `MomentDateAdapter` can be automatically provided by importing `MomentDateModule` in your
// application's root module. We provide it at the component level here, due to limitations of
// our example generation script.
{
provide: DateAdapter,
useClass: MomentDateAdapter,
deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS]
},
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS }
]
}) })
export class FireCalculatorComponent export class FireCalculatorComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
@ -50,11 +95,14 @@ export class FireCalculatorComponent
@Input() fireWealth: number; @Input() fireWealth: number;
@Input() hasPermissionToUpdateUserSettings: boolean; @Input() hasPermissionToUpdateUserSettings: boolean;
@Input() locale: string; @Input() locale: string;
@Input() retirementDate: string;
@Input() savingsRate = 0; @Input() savingsRate = 0;
@Input() targetNetWorth = 0; @Input() projectedTotalAmount = 0;
@Input() projectedTotalAmountSet: boolean;
@Output() retirementDateChanged = new EventEmitter<Date>();
@Output() savingsRateChanged = new EventEmitter<number>(); @Output() savingsRateChanged = new EventEmitter<number>();
@Output() targetNetWorthChanged = new EventEmitter<number>(); @Output() projectedTotalAmountChanged = new EventEmitter<number>();
@ViewChild('chartCanvas') chartCanvas; @ViewChild('chartCanvas') chartCanvas;
@ -62,14 +110,12 @@ export class FireCalculatorComponent
annualInterestRate: new FormControl<number>(undefined), annualInterestRate: new FormControl<number>(undefined),
paymentPerPeriod: new FormControl<number>(undefined), paymentPerPeriod: new FormControl<number>(undefined),
principalInvestmentAmount: new FormControl<number>(undefined), principalInvestmentAmount: new FormControl<number>(undefined),
retirementNetWorth: new FormControl<number>(undefined), projectedTotalAmount: new FormControl<number>(undefined),
time: new FormControl<number>(undefined) retirementDate: new FormControl(moment())
}); });
public chart: Chart<'bar'>; public chart: Chart<'bar'>;
public isLoading = true; public isLoading = true;
public projectedTotalAmount: number; public periodsToRetire = 0;
public retirementDate: string;
private readonly CONTRIBUTION_PERIOD = 12; private readonly CONTRIBUTION_PERIOD = 12;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -91,8 +137,8 @@ export class FireCalculatorComponent
annualInterestRate: 5, annualInterestRate: 5,
paymentPerPeriod: this.savingsRate, paymentPerPeriod: this.savingsRate,
principalInvestmentAmount: 0, principalInvestmentAmount: 0,
retirementNetWorth: this.targetNetWorth, projectedTotalAmount: this.projectedTotalAmount,
time: 10 retirementDate: moment(this.retirementDate)
}, },
{ {
emitEvent: false emitEvent: false
@ -112,10 +158,16 @@ export class FireCalculatorComponent
this.savingsRateChanged.emit(savingsRate); this.savingsRateChanged.emit(savingsRate);
}); });
this.calculatorForm this.calculatorForm
.get('retirementNetWorth') .get('retirementDate')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject)) .valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((targetNetWorth) => { .subscribe((retirementDate) => {
this.targetNetWorthChanged.emit(targetNetWorth); this.retirementDateChanged.emit(retirementDate.toDate());
});
this.calculatorForm
.get('projectedTotalAmount')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((projectedTotalAmount) => {
this.projectedTotalAmountChanged.emit(projectedTotalAmount);
}); });
} }
@ -125,9 +177,13 @@ export class FireCalculatorComponent
// Wait for the chartCanvas // Wait for the chartCanvas
this.calculatorForm.patchValue( this.calculatorForm.patchValue(
{ {
principalInvestmentAmount: this.fireWealth, principalInvestmentAmount: this.getP(),
paymentPerPeriod: this.savingsRate ?? 0, paymentPerPeriod: this.getPMT(),
retirementNetWorth: this.targetNetWorth ?? 0 retirementDate: moment(
this.getRetirementDate() || DEFAULT_RETIREMENT_DATE
),
projectedTotalAmount:
Number(this.getProjectedTotalAmount().toFixed(2)) ?? 0
}, },
{ {
emitEvent: false emitEvent: false
@ -141,18 +197,22 @@ export class FireCalculatorComponent
if (this.hasPermissionToUpdateUserSettings === true) { if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false });
this.calculatorForm.get('retirementDate').enable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('retirementNetWorth') .get('projectedTotalAmount')
.enable({ emitEvent: false }); .enable({ emitEvent: false });
} else { } else {
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false });
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('retirementNetWorth') .get('projectedTotalAmount')
.disable({ emitEvent: false }); .disable({ emitEvent: false });
} }
} }
public ngOnChanges() { public ngOnChanges() {
this.periodsToRetire = this.getPeriodsToRetire();
if (isNumber(this.fireWealth) && this.fireWealth >= 0) { if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
setTimeout(() => { setTimeout(() => {
// Wait for the chartCanvas // Wait for the chartCanvas
@ -160,7 +220,11 @@ export class FireCalculatorComponent
{ {
principalInvestmentAmount: this.fireWealth, principalInvestmentAmount: this.fireWealth,
paymentPerPeriod: this.savingsRate ?? 0, paymentPerPeriod: this.savingsRate ?? 0,
retirementNetWorth: this.targetNetWorth ?? 0 retirementDate: moment(
this.getRetirementDate() || DEFAULT_RETIREMENT_DATE
),
projectedTotalAmount:
Number(this.getProjectedTotalAmount().toFixed(2)) ?? 0
}, },
{ {
emitEvent: false emitEvent: false
@ -174,13 +238,15 @@ export class FireCalculatorComponent
if (this.hasPermissionToUpdateUserSettings === true) { if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false });
this.calculatorForm.get('retirementDate').enable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('retirementNetWorth') .get('projectedTotalAmount')
.enable({ emitEvent: false }); .enable({ emitEvent: false });
} else { } else {
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false });
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('retirementNetWorth') .get('projectedTotalAmount')
.disable({ emitEvent: false }); .disable({ emitEvent: false });
} }
} }
@ -282,43 +348,89 @@ export class FireCalculatorComponent
this.isLoading = false; this.isLoading = false;
} }
private getP() {
return this.fireWealth || 0;
}
private getPMT() {
return this.savingsRate ?? 0;
}
private getR() {
return this.calculatorForm.get('annualInterestRate').value / 100;
}
private getProjectedTotalAmount() {
if (this.projectedTotalAmountSet) {
return this.projectedTotalAmount || 0;
} else {
const { totalAmount } =
this.fireCalculatorService.calculateCompoundInterest({
P: this.getP(),
periodInMonths: this.periodsToRetire,
PMT: this.getPMT(),
r: this.getR()
});
return totalAmount.toNumber();
}
}
private getPeriodsToRetire(): number {
if (this.projectedTotalAmountSet) {
const periods = this.fireCalculatorService.calculatePeriodsToRetire({
P: this.getP(),
totalAmount: this.projectedTotalAmount,
PMT: this.getPMT(),
r: this.getR()
});
return periods;
} else {
const today = new Date();
const retirementDate = this.retirementDate
? new Date(this.retirementDate)
: DEFAULT_RETIREMENT_DATE;
return (
12 * (retirementDate.getFullYear() - today.getFullYear()) +
retirementDate.getMonth() -
today.getMonth()
);
}
}
private getRetirementDate(): Date {
const yearsToRetire = Math.floor(this.periodsToRetire / 12);
const monthsToRetire = this.periodsToRetire % 12;
return add(new Date(), {
years: yearsToRetire,
months: monthsToRetire
});
}
private getChartData() { private getChartData() {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const labels = []; const labels = [];
let t: number;
// Principal investment amount // Principal investment amount
const P: number = const P: number = this.getP();
this.calculatorForm.get('principalInvestmentAmount').value || 0;
// Payment per period // Payment per period
const PMT = this.calculatorForm.get('paymentPerPeriod').value; const PMT = this.getPMT();
// Annual interest rate // Annual interest rate
const r: number = this.calculatorForm.get('annualInterestRate').value / 100; const r: number = this.getR();
// Target net worth
const targetNetWorth = this.calculatorForm.get('retirementNetWorth').value;
// Calculate retirement date // Calculate retirement date
const periodsToRetire = this.fireCalculatorService.calculatePeriodsToRetire( // if we want to retire at month x, we need the projectedTotalAmount at month x-1
{ const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 });
P, const yearsToRetire = lastPeriodDate.getFullYear() - currentYear;
totalAmount: targetNetWorth,
PMT,
r
}
);
const yearsToRetire = Math.floor(periodsToRetire / 12);
const monthsToRetire = periodsToRetire % 12;
// Time // Time
if (targetNetWorth) {
// +1 to take into account the current year // +1 to take into account the current year
t = yearsToRetire + 1; const t = yearsToRetire + 1;
} else {
t = this.calculatorForm.get('time').value;
}
for (let year = currentYear; year < currentYear + t; year++) { for (let year = currentYear; year < currentYear + t; year++) {
labels.push(year); labels.push(year);
@ -366,21 +478,22 @@ export class FireCalculatorComponent
datasetDeposit.data.push(this.fireWealth); datasetDeposit.data.push(this.fireWealth);
datasetInterest.data.push(interest.toNumber()); datasetInterest.data.push(interest.toNumber());
datasetSavings.data.push(principal.minus(this.fireWealth).toNumber()); datasetSavings.data.push(principal.minus(this.fireWealth).toNumber());
if (period === t) {
this.projectedTotalAmount = totalAmount.toNumber();
}
} }
const retirementDate = add(new Date(), {
years: yearsToRetire,
months: monthsToRetire
});
this.retirementDate = format(retirementDate, 'MMMM yyyy');
return { return {
labels, labels,
datasets: [datasetDeposit, datasetSavings, datasetInterest] datasets: [datasetDeposit, datasetSavings, datasetInterest]
}; };
} }
setMonthAndYear(
normalizedMonthAndYear: Moment,
datepicker: MatDatepicker<Moment>
) {
const ctrlValue = this.calculatorForm.get('retirementDate').value!;
ctrlValue.month(normalizedMonthAndYear.month());
ctrlValue.year(normalizedMonthAndYear.year());
this.calculatorForm.get('retirementDate').setValue(ctrlValue);
datepicker.close();
}
} }

2
libs/ui/src/lib/fire-calculator/fire-calculator.module.ts

@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -18,6 +19,7 @@ import { FireCalculatorService } from './fire-calculator.service';
FormsModule, FormsModule,
GfValueModule, GfValueModule,
MatButtonModule, MatButtonModule,
MatDatepickerModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
NgxSkeletonLoaderModule, NgxSkeletonLoaderModule,

2
libs/ui/src/lib/fire-calculator/fire-calculator.service.ts

@ -55,7 +55,7 @@ export class FireCalculatorService {
if (r == 0) { if (r == 0) {
// No compound interest // No compound interest
return (totalAmount - P) / PMT; return (totalAmount - P) / PMT;
} else if (totalAmount <= 0) { } else if (totalAmount <= P) {
return 0; return 0;
} }

1
package.json

@ -59,6 +59,7 @@
"@angular/core": "15.1.5", "@angular/core": "15.1.5",
"@angular/forms": "15.1.5", "@angular/forms": "15.1.5",
"@angular/material": "15.1.5", "@angular/material": "15.1.5",
"@angular/material-moment-adapter": "15.2.1",
"@angular/platform-browser": "15.1.5", "@angular/platform-browser": "15.1.5",
"@angular/platform-browser-dynamic": "15.1.5", "@angular/platform-browser-dynamic": "15.1.5",
"@angular/router": "15.1.5", "@angular/router": "15.1.5",

1
tsconfig.base.json

@ -1,6 +1,7 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"allowSyntheticDefaultImports": true,
"rootDir": ".", "rootDir": ".",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,

Loading…
Cancel
Save