Compare commits

...

2 Commits

Author SHA1 Message Date
Thomas Kaul 2e01c121e4
Task/remove unused OnDestroy hook in resources page component (#6367) 5 days ago
Kenrick Tandrian 90cc7af87c
Task/improve type safety in fire calculator component (#6385) 5 days ago
  1. 8
      apps/client/src/app/pages/resources/resources-page.component.ts
  2. 128
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts

8
apps/client/src/app/pages/resources/resources-page.component.ts

@ -13,7 +13,6 @@ import {
readerOutline readerOutline
} from 'ionicons/icons'; } from 'ionicons/icons';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
@Component({ @Component({
host: { class: 'page has-tabs' }, host: { class: 'page has-tabs' },
@ -47,8 +46,6 @@ export class ResourcesPageComponent implements OnInit {
} }
]; ];
private unsubscribeSubject = new Subject<void>();
public constructor(private deviceService: DeviceDetectorService) { public constructor(private deviceService: DeviceDetectorService) {
addIcons({ bookOutline, libraryOutline, newspaperOutline, readerOutline }); addIcons({ bookOutline, libraryOutline, newspaperOutline, readerOutline });
} }
@ -56,9 +53,4 @@ export class ResourcesPageComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

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

@ -13,13 +13,13 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
Output, ViewChild,
ViewChild output
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { import {
FormBuilder, FormBuilder,
FormControl, FormControl,
@ -55,9 +55,9 @@ import {
startOfMonth, startOfMonth,
sub sub
} from 'date-fns'; } from 'date-fns';
import { isNumber } from 'lodash'; import { isNil, isNumber } from 'lodash';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject, debounceTime, takeUntil } from 'rxjs'; import { debounceTime } from 'rxjs';
import { FireCalculatorService } from './fire-calculator.service'; import { FireCalculatorService } from './fire-calculator.service';
@ -90,32 +90,31 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
@Input() retirementDate: Date; @Input() retirementDate: Date;
@Input() savingsRate = 0; @Input() savingsRate = 0;
@Output() annualInterestRateChanged = new EventEmitter<number>();
@Output() calculationCompleted =
new EventEmitter<FireCalculationCompleteEvent>();
@Output() projectedTotalAmountChanged = new EventEmitter<number>();
@Output() retirementDateChanged = new EventEmitter<Date>();
@Output() savingsRateChanged = new EventEmitter<number>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>; @ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
public calculatorForm = this.formBuilder.group({ public calculatorForm = this.formBuilder.group({
annualInterestRate: new FormControl<number>(undefined), annualInterestRate: new FormControl<number | null>(null),
paymentPerPeriod: new FormControl<number>(undefined), paymentPerPeriod: new FormControl<number | null>(null),
principalInvestmentAmount: new FormControl<number>(undefined), principalInvestmentAmount: new FormControl<number | null>(null),
projectedTotalAmount: new FormControl<number>(undefined), projectedTotalAmount: new FormControl<number | null>(null),
retirementDate: new FormControl<Date>(undefined) retirementDate: new FormControl<Date | null>(null)
}); });
public chart: Chart<'bar'>; public chart: Chart<'bar'>;
public isLoading = true; public isLoading = true;
public minDate = addDays(new Date(), 1); public minDate = addDays(new Date(), 1);
public periodsToRetire = 0; public periodsToRetire = 0;
protected readonly annualInterestRateChanged = output<number>();
protected readonly calculationCompleted =
output<FireCalculationCompleteEvent>();
protected readonly projectedTotalAmountChanged = output<number>();
protected readonly retirementDateChanged = output<Date>();
protected readonly savingsRateChanged = output<number>();
private readonly CONTRIBUTION_PERIOD = 12; private readonly CONTRIBUTION_PERIOD = 12;
private readonly DEFAULT_RETIREMENT_DATE = startOfMonth( private readonly DEFAULT_RETIREMENT_DATE = startOfMonth(
addYears(new Date(), 10) addYears(new Date(), 10)
); );
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@ -131,46 +130,56 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
); );
this.calculatorForm.valueChanges this.calculatorForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed())
.subscribe(() => { .subscribe(() => {
this.initialize(); this.initialize();
}); });
this.calculatorForm.valueChanges this.calculatorForm.valueChanges
.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) .pipe(debounceTime(500), takeUntilDestroyed())
.subscribe(() => { .subscribe(() => {
const { projectedTotalAmount, retirementDate } = const { projectedTotalAmount, retirementDate } =
this.calculatorForm.getRawValue(); this.calculatorForm.getRawValue();
this.calculationCompleted.emit({ if (projectedTotalAmount !== null && retirementDate !== null) {
projectedTotalAmount, this.calculationCompleted.emit({
retirementDate projectedTotalAmount,
}); retirementDate
});
}
}); });
this.calculatorForm this.calculatorForm
.get('annualInterestRate') .get('annualInterestRate')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed())
.subscribe((annualInterestRate) => { .subscribe((annualInterestRate) => {
this.annualInterestRateChanged.emit(annualInterestRate); if (annualInterestRate !== null) {
this.annualInterestRateChanged.emit(annualInterestRate);
}
}); });
this.calculatorForm this.calculatorForm
.get('paymentPerPeriod') .get('paymentPerPeriod')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed())
.subscribe((savingsRate) => { .subscribe((savingsRate) => {
this.savingsRateChanged.emit(savingsRate); if (savingsRate !== null) {
this.savingsRateChanged.emit(savingsRate);
}
}); });
this.calculatorForm this.calculatorForm
.get('projectedTotalAmount') .get('projectedTotalAmount')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed())
.subscribe((projectedTotalAmount) => { .subscribe((projectedTotalAmount) => {
this.projectedTotalAmountChanged.emit(projectedTotalAmount); if (projectedTotalAmount !== null) {
this.projectedTotalAmountChanged.emit(projectedTotalAmount);
}
}); });
this.calculatorForm this.calculatorForm
.get('retirementDate') .get('retirementDate')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed())
.subscribe((retirementDate) => { .subscribe((retirementDate) => {
this.retirementDateChanged.emit(retirementDate); if (retirementDate !== null) {
this.retirementDateChanged.emit(retirementDate);
}
}); });
} }
@ -196,11 +205,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
this.calculatorForm.patchValue( this.calculatorForm.patchValue(
{ {
annualInterestRate: annualInterestRate:
this.calculatorForm.get('annualInterestRate').value, this.calculatorForm.get('annualInterestRate')?.value,
paymentPerPeriod: this.getPMT(), paymentPerPeriod: this.getPMT(),
principalInvestmentAmount: this.calculatorForm.get( principalInvestmentAmount: this.calculatorForm.get(
'principalInvestmentAmount' 'principalInvestmentAmount'
).value, )?.value,
projectedTotalAmount: projectedTotalAmount:
Math.round(this.getProjectedTotalAmount()) || 0, Math.round(this.getProjectedTotalAmount()) || 0,
retirementDate: retirementDate:
@ -210,7 +219,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
emitEvent: false emitEvent: false
} }
); );
this.calculatorForm.get('principalInvestmentAmount').disable(); this.calculatorForm.get('principalInvestmentAmount')?.disable();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
@ -219,42 +228,43 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
if (this.hasPermissionToUpdateUserSettings === true) { if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm this.calculatorForm
.get('annualInterestRate') .get('annualInterestRate')
.enable({ emitEvent: false }); ?.enable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('projectedTotalAmount') .get('projectedTotalAmount')
.enable({ emitEvent: false }); ?.enable({ emitEvent: false });
} else { } else {
this.calculatorForm this.calculatorForm
.get('annualInterestRate') .get('annualInterestRate')
.disable({ emitEvent: false }); ?.disable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); this.calculatorForm
.get('paymentPerPeriod')
?.disable({ emitEvent: false });
this.calculatorForm this.calculatorForm
.get('projectedTotalAmount') .get('projectedTotalAmount')
.disable({ emitEvent: false }); ?.disable({ emitEvent: false });
} }
this.calculatorForm.get('retirementDate').disable({ emitEvent: false }); this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false });
} }
public setMonthAndYear( public setMonthAndYear(
normalizedMonthAndYear: Date, normalizedMonthAndYear: Date,
datepicker: MatDatepicker<Date> datepicker: MatDatepicker<Date>
) { ) {
const retirementDate = this.calculatorForm.get('retirementDate').value; const retirementDate =
this.calculatorForm.get('retirementDate')?.value ??
this.DEFAULT_RETIREMENT_DATE;
const newRetirementDate = setMonth( const newRetirementDate = setMonth(
setYear(retirementDate, normalizedMonthAndYear.getFullYear()), setYear(retirementDate, normalizedMonthAndYear.getFullYear()),
normalizedMonthAndYear.getMonth() normalizedMonthAndYear.getMonth()
); );
this.calculatorForm.get('retirementDate').setValue(newRetirementDate); this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate);
datepicker.close(); datepicker.close();
} }
public ngOnDestroy() { public ngOnDestroy() {
this.chart?.destroy(); this.chart?.destroy();
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
} }
private initialize() { private initialize() {
@ -288,8 +298,6 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
return `Total: ${new Intl.NumberFormat(this.locale, { return `Total: ${new Intl.NumberFormat(this.locale, {
currency: this.currency, currency: this.currency,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Only supported from ES2020 or later
currencyDisplay: 'code', currencyDisplay: 'code',
style: 'currency' style: 'currency'
}).format(totalAmount)}`; }).format(totalAmount)}`;
@ -426,12 +434,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
} }
private getPeriodsToRetire(): number { private getPeriodsToRetire(): number {
if (this.calculatorForm.get('projectedTotalAmount').value) { const projectedTotalAmount = this.calculatorForm.get(
'projectedTotalAmount'
)?.value;
if (projectedTotalAmount) {
let periods = this.fireCalculatorService.calculatePeriodsToRetire({ let periods = this.fireCalculatorService.calculatePeriodsToRetire({
P: this.getP(), P: this.getP(),
PMT: this.getPMT(), PMT: this.getPMT(),
r: this.getR(), r: this.getR(),
totalAmount: this.calculatorForm.get('projectedTotalAmount').value totalAmount: projectedTotalAmount
}); });
if (periods === Infinity) { if (periods === Infinity) {
@ -453,12 +465,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
} }
private getPMT() { private getPMT() {
return this.calculatorForm.get('paymentPerPeriod').value; return this.calculatorForm.get('paymentPerPeriod')?.value ?? 0;
} }
private getProjectedTotalAmount() { private getProjectedTotalAmount() {
if (this.calculatorForm.get('projectedTotalAmount').value) { const projectedTotalAmount = this.calculatorForm.get(
return this.calculatorForm.get('projectedTotalAmount').value; 'projectedTotalAmount'
)?.value;
if (!isNil(projectedTotalAmount)) {
return projectedTotalAmount;
} }
const { totalAmount } = const { totalAmount } =
@ -473,12 +489,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
} }
private getR() { private getR() {
return this.calculatorForm.get('annualInterestRate').value / 100; return (this.calculatorForm.get('annualInterestRate')?.value ?? 0) / 100;
} }
private getRetirementDate(): Date { private getRetirementDate(): Date {
if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) {
return undefined; return this.DEFAULT_RETIREMENT_DATE;
} }
const monthsToRetire = this.periodsToRetire % 12; const monthsToRetire = this.periodsToRetire % 12;

Loading…
Cancel
Save