Skip to content

WEB-954: Migrate Subscription management : loans#3621

Open
alberto-art3ch wants to merge 1 commit into
openMF:devfrom
alberto-art3ch:WEB-954/migrate-subscription-management-loans
Open

WEB-954: Migrate Subscription management : loans#3621
alberto-art3ch wants to merge 1 commit into
openMF:devfrom
alberto-art3ch:WEB-954/migrate-subscription-management-loans

Conversation

@alberto-art3ch
Copy link
Copy Markdown
Collaborator

@alberto-art3ch alberto-art3ch commented May 27, 2026

Migrate subscription management from Subject / takeUntil to DestroyRef / takeUntilDestroyed across the application

Refactor to replace the manual subscription cleanup pattern (Subject + takeUntil(this.destroy$) + ngOnDestroy) with Angular's built-in DestroyRef token and the takeUntilDestroyed() operator.

Context
We were having valueChanges.subscribe() calls were piped. Now we are ensuring with takeUntilDestroyed(this.destroyRef), on every form subscription automatically cancels when its component is destroyed. Before this change, navigating away from a form left active listeners running in the background, leaking memory on every route visit
Related issues and discussion

WEB-954

Screenshots, if any

Checklist

Please make sure these boxes are checked before submitting your pull request - thanks!

  • If you have multiple commits please combine them into one commit by squashing them.

  • Read and understood the contribution guidelines at web-app/.github/CONTRIBUTING.md.

Summary by CodeRabbit

  • New Features

    • Enhanced loan action routing based on loan product type for improved repayment processing.
  • Bug Fixes

    • Improved subscription lifecycle management across loan components to prevent memory leaks.
  • UI/UX Updates

    • Simplified the Make Repayment form by removing penalty waiving functionality.
    • Payment detail fields now conditionally display based on payment method requirements.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "pre_merge_checks"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

This PR systematically migrates RxJS subscription lifecycle management across 40+ loan module components from unmanaged subscriptions to Angular's DestroyRef with takeUntilDestroyed operator, while also refactoring the Make Repayment component to use typed reactive forms and removing penalty-waiving UI in favor of payment details collection.

Changes

Subscription lifecycle migration to DestroyRef/takeUntilDestroyed

Layer / File(s) Summary
Core loan account and management components
src/app/loans/common-resolvers/loan-action-button.resolver.ts, src/app/loans/create-loans-account/create-loans-account.component.ts, src/app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component.ts, src/app/loans/edit-loans-account/edit-loans-account.component.ts, src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts, src/app/loans/glim-account/glim-account.component.ts
Add DestroyRef injection and convert all route data and form control valueChanges subscriptions from direct .subscribe() to .pipe(takeUntilDestroyed(this.destroyRef)).subscribe() for automatic cleanup on component destruction.
Form stepper and validation components
src/app/loans/loans-account-stepper/loans-account-details-step/loans-account-details-step.component.ts, src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts
Replace OnDestroy lifecycle and _onDestroy subject with DestroyRef + takeUntilDestroyed for managing multiple form control valueChanges subscriptions in validation and term-setting methods; remove ngOnDestroy() and _onDestroy field.
Loans view components and information tabs
src/app/loans/loans-view/account-details/account-details.component.ts, src/app/loans/loans-view/charges-tab/charges-tab.component.ts, src/app/loans/loans-view/datatable-tab/datatable-tab.component.ts, src/app/loans/loans-view/external-asset-owner-tab/external-asset-owner-tab.component.ts, src/app/loans/loans-view/floating-interest-rates/floating-interest-rates.component.ts, src/app/loans/loans-view/general-tab/general-tab.component.ts, src/app/loans/loans-view/loan-collateral-tab/loan-collateral-tab.component.ts, src/app/loans/loans-view/loan-deferred-income-tab/loan-deferred-income-tab.component.ts, src/app/loans/loans-view/loan-delinquency-tags-tab/loan-delinquency-tags-tab.component.ts, src/app/loans/loans-view/loan-documents-tab/loan-documents-tab.component.ts, src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts, src/app/loans/loans-view/loan-term-variations-tab/loan-term-variations-tab.component.ts, src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts, src/app/loans/loans-view/loans-view.component.ts, src/app/loans/loans-view/notes-tab/notes-tab.component.ts, src/app/loans/loans-view/original-schedule-tab/original-schedule-tab.component.ts, src/app/loans/loans-view/overdue-charges-tab/overdue-charges-tab.component.ts, src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.ts, src/app/loans/loans-view/standing-instructions-tab/standing-instructions-tab.component.ts
Add DestroyRef injection and pipe all route data subscriptions (both route.data and route.parent.data) through takeUntilDestroyed(this.destroyRef) to bind subscription lifetime to component destruction.
Loan action and transaction components
src/app/loans/loans-view/loan-account-actions/add-loan-charge/add-loan-charge.component.ts, src/app/loans/loans-view/loan-account-actions/adjust-loan-charge/adjust-loan-charge.component.ts, src/app/loans/loans-view/loan-account-actions/approve-loan/approve-loan.component.ts, src/app/loans/loans-view/loan-account-actions/create-guarantor/create-guarantor.component.ts, src/app/loans/loans-view/loan-account-actions/foreclosure/foreclosure.component.ts, src/app/loans/loans-view/loan-account-actions/loan-account-actions.component.ts, src/app/loans/loans-view/loan-account-actions/prepay-loan/prepay-loan.component.ts, src/app/loans/loans-view/transactions-tab/transactions-tab.component.ts, src/app/loans/loans-view/transactions/edit-transaction/edit-transaction.component.ts, src/app/loans/loans-view/transactions/export-transactions/export-transactions.component.ts, src/app/loans/loans-view/transactions/view-reciept/view-reciept.component.ts, src/app/loans/loans-view/transactions/view-transaction/view-transaction.component.ts, src/app/loans/loans-view/view-charge/view-charge.component.ts
Inject DestroyRef and apply takeUntilDestroyed to both route data subscriptions and reactive form valueChanges subscriptions across charge handling, guarantor creation, transaction views, and prepayment actions.
Dashboard and working-capital components
src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts, src/app/loans/loans-view/working-capital/loan-balances-tab/loan-balances-tab.component.ts, src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts
Replace manual Subscription tracking and ngOnDestroy unsubscribe calls with DestroyRef + takeUntilDestroyed, removing import of rxjs Subscription class.

Make Repayment form typing and template updates

Layer / File(s) Summary
RepaymentForm typed model
src/app/loans/models/loan-form.model.ts
Add RepaymentForm interface defining typed FormControl fields: required fields (transactionDate, externalId, paymentTypeId, note, skipInterestRefund, transactionAmount), optional fields (classificationId, accountNumber, checkNumber, routingCode, receiptNumber, bankNumber).
Make repayment component form typing and methods
src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.ts
Update repaymentLoanForm from UntypedFormGroup to FormGroup<RepaymentForm> | null; switch formBuilder injection to typed FormBuilder; build form using typed FormControl instances with conditional classificationId field; remove public methods for penalty waiving, selection, and display (toggleWaivePenalties, toggleSelectAllPenalties, togglePenaltySelection, isPenaltySelected, getPenaltyDisplayKey, recalculateTransactionAmount); add null-safety checks in updateTransactionAmountValidators and submit.
Make repayment template and resolver routing
src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.html, src/app/loans/common-resolvers/loan-action-button.resolver.ts
Replace penalties waiver toggle/list UI with showPaymentDetails toggle that conditionally renders banking/payment fields (account number, cheque/check number, routing code, receipt number, bank number); remove skip-interest-refund checkbox; update LoanActionButtonResolver.resolve to route 'Make Repayment' action to loansService.getLoanActionTemplate for loan products or loansService.getWorkingCapitalLoanActionTemplate otherwise.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • openMF/web-app#3095: Modifies LoanActionButtonResolver.resolve for different action branches; this PR changes the 'Make Repayment' routing logic.
  • openMF/web-app#3380: Also modifies LoanActionButtonResolver action-branch logic (for 'View Guarantors' action).

Suggested reviewers

  • IOhacker
  • adamsaghy
  • shubhamkumar9199
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'WEB-954: Migrate Subscription management : loans' directly corresponds to the PR's main objective of migrating subscription management patterns across loan components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts (1)

111-123: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Nested subscriptions lack lifecycle management.

The addPaymentRate() method contains three nested subscriptions without takeUntilDestroyed(). If the component is destroyed while the dialog is open or while API calls are pending, these subscriptions will leak—exactly the issue this PR aims to fix.

🔧 Proposed fix to add lifecycle management
-    dialogRef.afterClosed().subscribe((response: any) => {
+    dialogRef.afterClosed()
+      .pipe(takeUntilDestroyed(this.destroyRef))
+      .subscribe((response: any) => {
       if (response?.data) {
         const { periodPaymentRate, note } = response.data.value;
         const payload = {
           periodPaymentRate: periodPaymentRate,
           note: note || '',
           locale: this.settingsService.language.code
         };
-        this.loanService.addWorkingCapitalPeriodPaymentRate(this.loanId, payload).subscribe((response: any) => {
-          this.loanService.getWorkingCapitalPeriodPaymentRates(this.loanId).subscribe((data: any) => {
+        this.loanService.addWorkingCapitalPeriodPaymentRate(this.loanId, payload)
+          .pipe(takeUntilDestroyed(this.destroyRef))
+          .subscribe((response: any) => {
+          this.loanService.getWorkingCapitalPeriodPaymentRates(this.loanId)
+            .pipe(takeUntilDestroyed(this.destroyRef))
+            .subscribe((data: any) => {
             this.loanPaymentRatesData = data;
           });
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`
around lines 111 - 123, The nested subscriptions in dialogRef.afterClosed()
(response handling), loanService.addWorkingCapitalPeriodPaymentRate and
loanService.getWorkingCapitalPeriodPaymentRates are not tied to component
lifecycle and can leak; refactor to a single observable chain by piping
dialogRef.afterClosed() through RxJS operators (e.g., filter/map and
switchMap/concatMap) to call addWorkingCapitalPeriodPaymentRate and then
getWorkingCapitalPeriodPaymentRates, and apply lifecycle teardown (e.g.,
takeUntilDestroyed(this) or untilDestroyed(this) in the pipe) so the
subscription is automatically cleaned up when the component is destroyed.
🧹 Nitpick comments (3)
src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.ts (1)

118-124: 💤 Low value

Add null guard for consistency with other methods.

repaymentLoanForm is typed as FormGroup<RepaymentForm> | null, but setRepaymentLoanDetails() accesses it without a null check. While the call order in ngOnInit ensures the form exists, adding a guard maintains consistency with updateTransactionAmountValidators and submit().

♻️ Suggested fix
 setRepaymentLoanDetails() {
+  if (!this.repaymentLoanForm) {
+    return;
+  }
   this.paymentTypes = this.dataObject.paymentTypeOptions;
   this.classificationOptions = this.dataObject.classificationOptions;
   this.repaymentLoanForm.patchValue({
     transactionAmount: this.dataObject.amount
   });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.ts`
around lines 118 - 124, Add a null guard in setRepaymentLoanDetails so it checks
repaymentLoanForm before accessing it; specifically, inside the
setRepaymentLoanDetails method, return early if this.repaymentLoanForm is null
(mirroring patterns used in updateTransactionAmountValidators and submit) and
only call this.repaymentLoanForm.patchValue(...) when the form exists, leaving
assignment of this.paymentTypes and this.classificationOptions unchanged.
src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts (1)

111-111: ⚖️ Poor tradeoff

Consider typing Observable responses instead of any.

The subscriptions use any for their response types. Introducing specific interfaces for dialog responses and service method return types would improve type safety and catch potential errors at compile time.

Based on learnings, avoid using Observable<any> as a pattern. When you encounter API responses, introduce specific interfaces/types for the response shapes and use proper typing instead of any.

Also applies to: 119-120

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`
at line 111, The subscription to dialogRef.afterClosed().subscribe((response:
any) => ...) (and the other subscribe at lines 119-120) uses any; replace it
with a properly named interface (e.g., LoanPeriodDialogResult) describing the
dialog payload and use that type in the Observable and subscribe callback
signature; update the dialog open call to use
MatDialogRef<LoanPeriodDialogComponent, LoanPeriodDialogResult> (or equivalent)
and change any service return types from Observable<any> to
Observable<ConcreteResponseType> so the callbacks and service methods are
strongly typed throughout.
src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts (1)

60-60: ⚖️ Poor tradeoff

Consider typing loanData and loanDetailsData instead of any.

The component uses any type for loan data throughout. Introducing a specific interface for the loan details response would improve type safety and enable better IDE support.

Based on learnings, avoid using Observable<any> as a pattern. When you encounter API responses, introduce specific interfaces/types for the response shapes and use proper typing instead of any.

Also applies to: 74-74

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts`
at line 60, Define a concrete TypeScript interface (e.g., LoanDetails or
LoanAccountData) that models the API shape returned to this component and
replace the two any usages (loanData and loanDetailsData) with that interface;
update any Observable<any> types to Observable<LoanDetails> (or
Observable<LoanDetails[]>, as appropriate) and adjust method signatures in
LoanAccountDashboardComponent (and any service method return types it consumes)
to return the strongly typed response so the component properties and
subscriptions use the new type instead of any.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component.ts`:
- Around line 93-99: The subscription in
addCollateralForm.controls.quantity.valueChanges is dereferencing
this.collateralData (used to compute totalValue and totalCollateralValue)
without guarding for null/undefined; update the callback in
loans-account-add-collateral-dialog.component.ts to first check that
this.collateralData exists (or provide safe defaults) before using its
properties — e.g., use a truthy check or optional chaining on
this.collateralData and fallback 0 for basePrice and pctToBase so patchValue
only writes computed numbers and never throws when quantity is entered before a
collateral is selected.

In
`@src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts`:
- Line 74: The code uses a non-null assertion on this.route.parent!.data which
can throw if parent is null; change to a null-safe guard: capture const parent =
this.route.parent (or use this.route.parent?) and only call
parent.data.pipe(...).subscribe(...) when parent is truthy (or use optional
chaining like this.route.parent?.data?.pipe(...)). Update the subscription to
use the existing takeUntilDestroyed(this.destroyRef) and the same callback
(data: { loanDetailsData: any }) so behavior stays identical but without the
unsafe non-null assertion.

---

Outside diff comments:
In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`:
- Around line 111-123: The nested subscriptions in dialogRef.afterClosed()
(response handling), loanService.addWorkingCapitalPeriodPaymentRate and
loanService.getWorkingCapitalPeriodPaymentRates are not tied to component
lifecycle and can leak; refactor to a single observable chain by piping
dialogRef.afterClosed() through RxJS operators (e.g., filter/map and
switchMap/concatMap) to call addWorkingCapitalPeriodPaymentRate and then
getWorkingCapitalPeriodPaymentRates, and apply lifecycle teardown (e.g.,
takeUntilDestroyed(this) or untilDestroyed(this) in the pipe) so the
subscription is automatically cleaned up when the component is destroyed.

---

Nitpick comments:
In
`@src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.ts`:
- Around line 118-124: Add a null guard in setRepaymentLoanDetails so it checks
repaymentLoanForm before accessing it; specifically, inside the
setRepaymentLoanDetails method, return early if this.repaymentLoanForm is null
(mirroring patterns used in updateTransactionAmountValidators and submit) and
only call this.repaymentLoanForm.patchValue(...) when the form exists, leaving
assignment of this.paymentTypes and this.classificationOptions unchanged.

In
`@src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts`:
- Line 60: Define a concrete TypeScript interface (e.g., LoanDetails or
LoanAccountData) that models the API shape returned to this component and
replace the two any usages (loanData and loanDetailsData) with that interface;
update any Observable<any> types to Observable<LoanDetails> (or
Observable<LoanDetails[]>, as appropriate) and adjust method signatures in
LoanAccountDashboardComponent (and any service method return types it consumes)
to return the strongly typed response so the component properties and
subscriptions use the new type instead of any.

In
`@src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts`:
- Line 111: The subscription to dialogRef.afterClosed().subscribe((response:
any) => ...) (and the other subscribe at lines 119-120) uses any; replace it
with a properly named interface (e.g., LoanPeriodDialogResult) describing the
dialog payload and use that type in the Observable and subscribe callback
signature; update the dialog open call to use
MatDialogRef<LoanPeriodDialogComponent, LoanPeriodDialogResult> (or equivalent)
and change any service return types from Observable<any> to
Observable<ConcreteResponseType> so the callbacks and service methods are
strongly typed throughout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b0912a1b-5e19-47f0-90bf-6ae30af02b8d

📥 Commits

Reviewing files that changed from the base of the PR and between 92b9984 and 9dfe33c.

📒 Files selected for processing (46)
  • src/app/loans/common-resolvers/loan-action-button.resolver.ts
  • src/app/loans/create-loans-account/create-loans-account.component.ts
  • src/app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component.ts
  • src/app/loans/edit-loans-account/edit-loans-account.component.ts
  • src/app/loans/glim-account/create-glim-account/create-glim-account.component.ts
  • src/app/loans/glim-account/glim-account.component.ts
  • src/app/loans/loans-account-stepper/loans-account-details-step/loans-account-details-step.component.ts
  • src/app/loans/loans-account-stepper/loans-account-terms-step/loans-account-terms-step.component.ts
  • src/app/loans/loans-view/account-details/account-details.component.ts
  • src/app/loans/loans-view/charges-tab/charges-tab.component.ts
  • src/app/loans/loans-view/datatable-tab/datatable-tab.component.ts
  • src/app/loans/loans-view/external-asset-owner-tab/external-asset-owner-tab.component.ts
  • src/app/loans/loans-view/floating-interest-rates/floating-interest-rates.component.ts
  • src/app/loans/loans-view/general-tab/general-tab.component.ts
  • src/app/loans/loans-view/loan-account-actions/add-loan-charge/add-loan-charge.component.ts
  • src/app/loans/loans-view/loan-account-actions/adjust-loan-charge/adjust-loan-charge.component.ts
  • src/app/loans/loans-view/loan-account-actions/approve-loan/approve-loan.component.ts
  • src/app/loans/loans-view/loan-account-actions/create-guarantor/create-guarantor.component.ts
  • src/app/loans/loans-view/loan-account-actions/foreclosure/foreclosure.component.ts
  • src/app/loans/loans-view/loan-account-actions/loan-account-actions.component.ts
  • src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.html
  • src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.ts
  • src/app/loans/loans-view/loan-account-actions/prepay-loan/prepay-loan.component.ts
  • src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts
  • src/app/loans/loans-view/loan-collateral-tab/loan-collateral-tab.component.ts
  • src/app/loans/loans-view/loan-deferred-income-tab/loan-deferred-income-tab.component.ts
  • src/app/loans/loans-view/loan-delinquency-tags-tab/loan-delinquency-tags-tab.component.ts
  • src/app/loans/loans-view/loan-documents-tab/loan-documents-tab.component.ts
  • src/app/loans/loans-view/loan-originators-tab/loan-originators-tab.component.ts
  • src/app/loans/loans-view/loan-term-variations-tab/loan-term-variations-tab.component.ts
  • src/app/loans/loans-view/loan-tranche-details/loan-tranche-details.component.ts
  • src/app/loans/loans-view/loans-view.component.ts
  • src/app/loans/loans-view/notes-tab/notes-tab.component.ts
  • src/app/loans/loans-view/original-schedule-tab/original-schedule-tab.component.ts
  • src/app/loans/loans-view/overdue-charges-tab/overdue-charges-tab.component.ts
  • src/app/loans/loans-view/reschedule-loan-tab/reschedule-loan-tab.component.ts
  • src/app/loans/loans-view/standing-instructions-tab/standing-instructions-tab.component.ts
  • src/app/loans/loans-view/transactions-tab/transactions-tab.component.ts
  • src/app/loans/loans-view/transactions/edit-transaction/edit-transaction.component.ts
  • src/app/loans/loans-view/transactions/export-transactions/export-transactions.component.ts
  • src/app/loans/loans-view/transactions/view-reciept/view-reciept.component.ts
  • src/app/loans/loans-view/transactions/view-transaction/view-transaction.component.ts
  • src/app/loans/loans-view/view-charge/view-charge.component.ts
  • src/app/loans/loans-view/working-capital/loan-balances-tab/loan-balances-tab.component.ts
  • src/app/loans/loans-view/working-capital/loan-period-payment-rates/loan-period-payment-rates.component.ts
  • src/app/loans/models/loan-form.model.ts
💤 Files with no reviewable changes (1)
  • src/app/loans/loans-view/loan-account-actions/make-repayment/make-repayment.component.html

Comment on lines +93 to +99
this.addCollateralForm.controls.quantity.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((quantity: any) => {
this.addCollateralForm.patchValue({
totalValue: this.collateralData.basePrice * quantity,
totalCollateralValue: (this.collateralData.basePrice * this.collateralData.pctToBase * quantity) / 100
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard quantity calculations when no collateral is selected.

At Line 97 and Line 98, this.collateralData is dereferenced without a guard. Entering quantity first can throw at runtime.

Proposed fix
     this.addCollateralForm.controls.quantity.valueChanges
       .pipe(takeUntilDestroyed(this.destroyRef))
       .subscribe((quantity: any) => {
+        if (!this.collateralData || quantity === null || quantity === '') {
+          this.addCollateralForm.patchValue(
+            { totalValue: '', totalCollateralValue: '' },
+            { emitEvent: false }
+          );
+          return;
+        }
+
+        const basePrice = Number(this.collateralData.basePrice) || 0;
+        const pctToBase = Number(this.collateralData.pctToBase) || 0;
+        const qty = Number(quantity) || 0;
         this.addCollateralForm.patchValue({
-          totalValue: this.collateralData.basePrice * quantity,
-          totalCollateralValue: (this.collateralData.basePrice * this.collateralData.pctToBase * quantity) / 100
+          totalValue: basePrice * qty,
+          totalCollateralValue: (basePrice * pctToBase * qty) / 100
         });
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/custom-dialog/loans-account-add-collateral-dialog/loans-account-add-collateral-dialog.component.ts`
around lines 93 - 99, The subscription in
addCollateralForm.controls.quantity.valueChanges is dereferencing
this.collateralData (used to compute totalValue and totalCollateralValue)
without guarding for null/undefined; update the callback in
loans-account-add-collateral-dialog.component.ts to first check that
this.collateralData exists (or provide safe defaults) before using its
properties — e.g., use a truthy check or optional chaining on
this.collateralData and fallback 0 for basePrice and pctToBase so patchValue
only writes computed numbers and never throws when quantity is entered before a
collateral is selected.

this.loanId = this.route.parent?.snapshot.paramMap.get('loanId') || '';

this.routeDataSubscription = this.route.parent!.data.subscribe((data: { loanDetailsData: any }) => {
this.route.parent!.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanDetailsData: any }) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Non-null assertion on route.parent may cause runtime error.

Line 74 uses this.route.parent!.data with a non-null assertion, but line 72 uses optional chaining (this.route.parent?.snapshot), suggesting parent may be nullable. If parent is null, this will throw a runtime error.

🛡️ Proposed fix to add null safety
-    this.route.parent!.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanDetailsData: any }) => {
+    this.route.parent?.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { loanDetailsData: any }) => {
       if (data.loanDetailsData) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/app/loans/loans-view/loan-account-dashboard/loan-account-dashboard.component.ts`
at line 74, The code uses a non-null assertion on this.route.parent!.data which
can throw if parent is null; change to a null-safe guard: capture const parent =
this.route.parent (or use this.route.parent?) and only call
parent.data.pipe(...).subscribe(...) when parent is truthy (or use optional
chaining like this.route.parent?.data?.pipe(...)). Update the subscription to
use the existing takeUntilDestroyed(this.destroyRef) and the same callback
(data: { loanDetailsData: any }) so behavior stays identical but without the
unsafe non-null assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant