From 12070c4e9c6c6a2d8e0ea11459abb35e0df35d81 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Thu, 18 Jul 2024 16:05:19 -0600
Subject: [PATCH] switch to signals and reactive forms

---
 .../aws/check-haz-jobs/state/app.facade.ts    |   2 +-
 .../terminate-haz-jobs/state/app.facade.ts    |   2 +-
 .../hanging-wall-effects/state/app.facade.ts  |  12 ++
 .../math/exceedance-explorer/app.component.ts |  22 +--
 .../control-panel.component.html              | 174 +++++++++---------
 .../control-panel/control-panel.component.ts  |  13 +-
 .../components/plot/plot.component.html       |   8 +-
 .../components/plot/plot.component.ts         |   7 +-
 .../settings/settings.component.html          |  18 +-
 .../components/settings/settings.component.ts |  34 +++-
 .../exceedance-explorer/state/app.actions.ts  |  13 --
 .../exceedance-explorer/state/app.effects.ts  |  40 ----
 .../exceedance-explorer/state/app.facade.ts   |  78 ++++----
 .../exceedance-explorer/state/app.reducer.ts  |  84 ---------
 .../exceedance-explorer/state/app.state.ts    |  55 +++---
 .../utils/app.default-values.ts               |  13 +-
 .../exceedance-explorer/utils/app.utils.ts    |  11 +-
 .../utils/control-panel.validators.ts         |  33 +++-
 .../src/app/dev/math/math.routes.ts           |   9 -
 19 files changed, 248 insertions(+), 380 deletions(-)
 delete mode 100644 projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.actions.ts
 delete mode 100644 projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.effects.ts
 delete mode 100644 projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.reducer.ts

diff --git a/projects/nshmp-apps/src/app/dev/aws/check-haz-jobs/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/aws/check-haz-jobs/state/app.facade.ts
index e2ca964f1..c400e0df2 100644
--- a/projects/nshmp-apps/src/app/dev/aws/check-haz-jobs/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/dev/aws/check-haz-jobs/state/app.facade.ts
@@ -158,7 +158,7 @@ export class AppFacade {
    */
   private callTerminateJobService(): void {
     this.awsService
-      .callTerminateJobServiceEffect(
+      .callTerminateJobService(
         `${this.baseUrl}/${this.terminateService}`,
         this.formGroup.controls.id
       )
diff --git a/projects/nshmp-apps/src/app/dev/aws/terminate-haz-jobs/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/aws/terminate-haz-jobs/state/app.facade.ts
index 99aad0774..ee00158d1 100644
--- a/projects/nshmp-apps/src/app/dev/aws/terminate-haz-jobs/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/dev/aws/terminate-haz-jobs/state/app.facade.ts
@@ -86,7 +86,7 @@ export class AppFacade {
    */
   callTerminateJobService(): void {
     this.awsService
-      .callTerminateJobServiceEffect(
+      .callTerminateJobService(
         `${this.baseUrl}/${this.terminateService}`,
         this.formGroup.controls.id
       )
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
index e1788aeb6..af9bb86db 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
@@ -24,6 +24,7 @@ import {
   initialState,
   usageFormValues,
 } from './app.state';
+import {redrawPlots} from 'projects/nshmp-apps/src/shared/utils/facade.utils';
 
 @Injectable({providedIn: 'root'})
 export class AppFacade {
@@ -107,6 +108,15 @@ export class AppFacade {
       .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
+  /**
+   * Redraw the plot.
+   */
+  plotRedraw(): void {
+    this.updateState({
+      plots: redrawPlots(this.state().plots),
+    });
+  }
+
   /**
    * Reset the control panel.
    */
@@ -122,6 +132,8 @@ export class AppFacade {
   resetPlotSettings(): void {
     this.state().plots.forEach((plot, id) => {
       const defaultSettings = defaultPlots().get(id).settingsForm;
+      plot.settingsForm.markAsPristine();
+      plot.settingsForm.markAsUntouched();
       plot.settingsForm.patchValue(defaultSettings.getRawValue());
     });
   }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/app.component.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/app.component.ts
index 338c9b088..2beeaa47f 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/app.component.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/app.component.ts
@@ -1,11 +1,10 @@
-import {Component, OnDestroy, OnInit} from '@angular/core';
+import {Component} from '@angular/core';
 import {NshmpLibNgAboutPageComponent} from '@ghsc/nshmp-lib-ng/about';
 import {NshmpLibNgTemplateComponent} from '@ghsc/nshmp-lib-ng/nshmp';
 import {
   NshmpTemplateContentContainerComponent,
   NshmpTemplateControlPanelComponent,
   NshmpTemplatePlotContentComponent,
-  NshmpTemplateService,
   NshmpTemplateSettingsComponent,
 } from '@ghsc/nshmp-template';
 import {devApps} from 'projects/nshmp-apps/src/shared/utils/applications.utils';
@@ -16,7 +15,6 @@ import {AboutComponent} from './components/about/about.component';
 import {ControlPanelComponent} from './components/control-panel/control-panel.component';
 import {PlotComponent} from './components/plot/plot.component';
 import {SettingsComponent} from './components/settings/settings.component';
-import {ExceedanceExplorerFacade} from './state/app.facade';
 
 /**
  * Entrypoint for exceedance explorer development application.
@@ -41,27 +39,11 @@ import {ExceedanceExplorerFacade} from './state/app.facade';
   styleUrl: './app.component.scss',
   templateUrl: './app.component.html',
 })
-export class AppComponent implements OnInit, OnDestroy {
+export class AppComponent {
   /** Navigation list for menu */
   navigationList = devNavigation();
   /** Small screen subscription */
   smallScreenSubscription: Subscription;
   /** Application title */
   title = devApps().exceedanceExplorer.display;
-
-  constructor(
-    private facade: ExceedanceExplorerFacade,
-    private nshmpTemplateService: NshmpTemplateService
-  ) {}
-
-  ngOnInit(): void {
-    this.smallScreenSubscription =
-      this.nshmpTemplateService.isSmallScreen$.subscribe(() =>
-        this.facade.plotRedraw()
-      );
-  }
-
-  ngOnDestroy(): void {
-    this.smallScreenSubscription.unsubscribe();
-  }
 }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
index 31a8c63cc..f5494ebb2 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.html
@@ -1,94 +1,92 @@
 <!-- Exceedance Control Panel -->
-@if (form$ | async; as form) {
-  <form
-    class="height-full overflow-auto"
-    [ngrxFormState]="form"
-    (submit)="onSubmit()"
-  >
-    <!-- Median -->
-    <mat-form-field class="grid-col-12 median-input padding-top-1">
-      <mat-label> Median (g) </mat-label>
-      <input
-        type="number"
-        matInput
-        [ngrxFormControlState]="form?.controls?.median"
-        [step]="formBounds.median.step"
-        [min]="formBounds.median.min"
-        [max]="formBounds.median.max"
-      />
-      <mat-error>
-        [ {{ formBounds.median.min }}, {{ formBounds.median.max }} ]
-      </mat-error>
-    </mat-form-field>
-
-    <!-- Sigma -->
-    <mat-form-field class="grid-col-12 sigma-input">
-      <mat-label> Sigma (natural log units) </mat-label>
-      <input
-        type="number"
-        matInput
-        [ngrxFormControlState]="form?.controls?.sigma"
-        [step]="formBounds.sigma.step"
-        [min]="formBounds.sigma.min"
-        [max]="formBounds.sigma.max"
-      />
-      <mat-error>
-        [ {{ formBounds.sigma.min }}, {{ formBounds.sigma.max }} ]
-      </mat-error>
-    </mat-form-field>
+<form
+  class="height-full overflow-auto"
+  [formGroup]="form"
+  (submit)="onSubmit()"
+>
+  <!-- Median -->
+  <mat-form-field class="grid-col-12 median-input padding-top-1">
+    <mat-label> Median (g) </mat-label>
+    <input
+      type="number"
+      matInput
+      [formControl]="form.controls.median"
+      [step]="formBounds.median.step"
+      [min]="formBounds.median.min"
+      [max]="formBounds.median.max"
+    />
+    <mat-error>
+      [ {{ formBounds.median.min }}, {{ formBounds.median.max }} ]
+    </mat-error>
+  </mat-form-field>
 
-    <!-- Rate -->
-    <mat-form-field class="grid-col-12 rate-input">
-      <mat-label> Rate </mat-label>
-      <input
-        type="number"
-        matInput
-        [ngrxFormControlState]="form?.controls?.rate"
-        [step]="formBounds.rate.step"
-        [min]="formBounds.rate.min"
-        [max]="formBounds.rate.max"
-      />
-      <mat-error>
-        [ {{ formBounds.rate.min }}, {{ formBounds.rate.max }} ]
-      </mat-error>
-    </mat-form-field>
+  <!-- Sigma -->
+  <mat-form-field class="grid-col-12 sigma-input">
+    <mat-label> Sigma (natural log units) </mat-label>
+    <input
+      type="number"
+      matInput
+      [formControl]="form.controls.sigma"
+      [step]="formBounds.sigma.step"
+      [min]="formBounds.sigma.min"
+      [max]="formBounds.sigma.max"
+    />
+    <mat-error>
+      [ {{ formBounds.sigma.min }}, {{ formBounds.sigma.max }} ]
+    </mat-error>
+  </mat-form-field>
 
-    <!-- Truncation -->
-    <mat-checkbox
-      color="primary"
-      class="margin-left-1 truncation-checkbox"
-      [ngrxFormControlState]="form?.controls?.truncation"
-    >
-      Truncation
-    </mat-checkbox>
+  <!-- Rate -->
+  <mat-form-field class="grid-col-12 rate-input">
+    <mat-label> Rate </mat-label>
+    <input
+      type="number"
+      matInput
+      [formControl]="form.controls.rate"
+      [step]="formBounds.rate.step"
+      [min]="formBounds.rate.min"
+      [max]="formBounds.rate.max"
+    />
+    <mat-error>
+      [ {{ formBounds.rate.min }}, {{ formBounds.rate.max }} ]
+    </mat-error>
+  </mat-form-field>
 
-    <!-- Truncation Level -->
-    <mat-form-field class="grid-col-12 truncation-level-input">
-      <mat-label> Truncation Level (n) </mat-label>
-      <input
-        [disabled]="form.value.truncation === false"
-        type="number"
-        matInput
-        [ngrxFormControlState]="form?.controls?.truncationLevel"
-        [step]="formBounds.truncationLevel.step"
-        [min]="formBounds.truncationLevel.min"
-        [max]="formBounds.truncationLevel.max"
-      />
-      <mat-error>
-        [ {{ formBounds.truncationLevel.min }},
-        {{ formBounds.truncationLevel.max }} ]
-      </mat-error>
-    </mat-form-field>
+  <!-- Truncation -->
+  <mat-checkbox
+    color="primary"
+    class="margin-left-1 truncation-checkbox"
+    [formControl]="form.controls.truncation"
+  >
+    Truncation
+  </mat-checkbox>
 
-    <nshmp-lib-ng-control-panel-buttons
-      resetButtonSize="grid-col-5"
-      [resetDisabled]="(data$ | async)?.length === 0"
-      resetLabel="Clear Plot"
-      plotButtonSize="grid-col-5"
-      [plotDisabled]="form?.isInvalid"
-      plotLabel="Add Curve"
-      [showServiceCallInfo]="false"
-      (resetClick)="facade.clearPlot()"
+  <!-- Truncation Level -->
+  <mat-form-field class="grid-col-12 truncation-level-input">
+    <mat-label> Truncation Level (n) </mat-label>
+    <input
+      [disabled]="form.value.truncation === false"
+      type="number"
+      matInput
+      [formControl]="form.controls.truncationLevel"
+      [step]="formBounds.truncationLevel.step"
+      [min]="formBounds.truncationLevel.min"
+      [max]="formBounds.truncationLevel.max"
     />
-  </form>
-}
+    <mat-error>
+      [ {{ formBounds.truncationLevel.min }},
+      {{ formBounds.truncationLevel.max }} ]
+    </mat-error>
+  </mat-form-field>
+
+  <nshmp-lib-no-ngrx-control-panel-buttons
+    resetButtonSize="grid-col-5"
+    [resetDisabled]="data()?.length === 0"
+    resetLabel="Clear Plot"
+    plotButtonSize="grid-col-5"
+    [plotDisabled]="form.invalid"
+    plotLabel="Add Curve"
+    [showServiceCallInfo]="false"
+    (resetClick)="facade.clearPlot()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.ts
index bc088b2df..af462349c 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/control-panel/control-panel.component.ts
@@ -1,14 +1,13 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatCheckbox} from '@angular/material/checkbox';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
 import {
   NshmpLibNgControlPanelButtonsComponent,
-  NshmpNgrxFormsModule,
   NshmpService,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {ExceedanceExplorerFacade} from '../../state/app.facade';
 import {EXCEEDANCE_FORM_BOUNDS} from '../../utils/app.utils';
@@ -25,7 +24,7 @@ import {EXCEEDANCE_FORM_BOUNDS} from '../../utils/app.utils';
     MatCheckbox,
     AsyncPipe,
     NshmpLibNgControlPanelButtonsComponent,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-control-panel',
   standalone: true,
@@ -37,9 +36,9 @@ export class ControlPanelComponent {
   formBounds = EXCEEDANCE_FORM_BOUNDS;
 
   /** Plot data state */
-  data$ = this.facade.plot$.pipe(map(plot => plot.plotData.data));
+  data = computed(() => this.facade.plot().plotData.data);
   /** Form field state */
-  form$ = this.facade.form$;
+  form = this.facade.formGroup;
 
   constructor(
     public facade: ExceedanceExplorerFacade,
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
index ab081d26a..a797ea077 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.html
@@ -1,5 +1,5 @@
 <!-- Exceedance Plot -->
-<nshmp-lib-ng-plots-container>
+<nshmp-lib-no-ngrx-plots-container>
   <mat-accordion multi>
     <!-- Plot -->
     <mat-expansion-panel expanded>
@@ -9,9 +9,7 @@
 
       <mat-divider />
 
-      @if (plotData$ | async; as plot) {
-        <nshmp-lib-ng-plot class="exceedance-plot" [plot]="plot" />
-      }
+      <nshmp-lib-no-ngrx-plot class="exceedance-plot" [plot]="plotData()" />
     </mat-expansion-panel>
   </mat-accordion>
-</nshmp-lib-ng-plots-container>
+</nshmp-lib-no-ngrx-plots-container>
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.ts
index 0bf1217e4..0b35ab6af 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/plot/plot.component.ts
@@ -1,5 +1,5 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatDivider} from '@angular/material/divider';
 import {
   MatAccordion,
@@ -10,8 +10,7 @@ import {
 import {
   NshmpLibNgPlotComponent,
   NshmpLibNgPlotsContainerComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {ExceedanceExplorerFacade} from '../../state/app.facade';
 
@@ -37,7 +36,7 @@ import {ExceedanceExplorerFacade} from '../../state/app.facade';
   templateUrl: './plot.component.html',
 })
 export class PlotComponent {
-  plotData$ = this.facade.plot$.pipe(map(plot => plot.plotData));
+  plotData = computed(() => this.facade.plot().plotData);
 
   constructor(private facade: ExceedanceExplorerFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
index 45866e00f..a912ed2ee 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.html
@@ -1,20 +1,16 @@
 <div class="height-full overflow-auto">
   <!-- Exceedance plot settings -->
-  @if (plot$ | async) {
-    <nshmp-lib-ng-plot-settings
-      class="exceedance-settings"
-      [plot]="plot$ | async"
-    />
-  }
+  <nshmp-lib-no-ngrx-plot-settings
+    class="exceedance-settings"
+    [plot]="plot()"
+    (updatedPlot)="updatePlot($event)"
+  />
 
   <div class="padding-y-3"></div>
 
   <!-- Reset button -->
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetSettings()"
-    [resetDisabled]="
-      (plot$ | async)?.settingsForm?.isPristine &&
-      (plot$ | async)?.settingsForm?.isUntouched
-    "
+    [resetDisabled]="resetDisabled"
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.ts
index a73f05868..bdb3cab65 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/components/settings/settings.component.ts
@@ -1,10 +1,13 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy} from '@angular/core';
 import {
   NshmpLibNgPlotResetPlotSettingsComponent,
   NshmpLibNgPlotSettingsComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
+  NshmpPlot,
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
+import {Subscription} from 'rxjs';
 
+import {Plots} from '../../models/plots.model';
 import {ExceedanceExplorerFacade} from '../../state/app.facade';
 
 /**
@@ -21,8 +24,31 @@ import {ExceedanceExplorerFacade} from '../../state/app.facade';
   styleUrl: './settings.component.scss',
   templateUrl: './settings.component.html',
 })
-export class SettingsComponent {
-  plot$ = this.facade.plot$;
+export class SettingsComponent implements OnDestroy {
+  plot = computed(() => {
+    this.settingsFormSubscription?.unsubscribe();
+    const plot = this.facade.plot();
+
+    this.settingsFormSubscription = plot.settingsForm.valueChanges.subscribe(
+      () =>
+        (this.resetDisabled =
+          plot.settingsForm.pristine && plot.settingsForm.untouched)
+    );
+
+    return plot;
+  });
+
+  resetDisabled = true;
+
+  private settingsFormSubscription = new Subscription();
 
   constructor(public facade: ExceedanceExplorerFacade) {}
+
+  ngOnDestroy(): void {
+    this.settingsFormSubscription.unsubscribe();
+  }
+
+  updatePlot(plot: NshmpPlot): void {
+    this.facade.updatePlot(plot, Plots.EXCEEDANCE);
+  }
 }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.actions.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.actions.ts
deleted file mode 100644
index 0aeb3b54e..000000000
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.actions.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import {createActionGroup, emptyProps} from '@ngrx/store';
-import {sharedActions} from 'projects/nshmp-apps/src/shared/state/shared';
-
-export const actions = createActionGroup({
-  events: {
-    ...sharedActions,
-    /** Calculate exceedance action */
-    'Calculate Exceedance': emptyProps(),
-    /** Clear plots action */
-    'Clear Plot': emptyProps(),
-  },
-  source: 'Math Exceedance Explorer Development App',
-});
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.effects.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.effects.ts
deleted file mode 100644
index 8628ba989..000000000
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.effects.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import {Injectable} from '@angular/core';
-import {NshmpService} from '@ghsc/nshmp-lib-ng/nshmp';
-import {Actions, createEffect, ofType} from '@ngrx/effects';
-import {concatLatestFrom} from '@ngrx/operators';
-import {Store} from '@ngrx/store';
-import {catchError, map} from 'rxjs/operators';
-
-import {exceedanceAddPlotData} from '../utils/app.utils';
-import {actions} from './app.actions';
-import {exceedanceExplorerAppFeature} from './app.reducer';
-
-/**
- * NGRX effects for exceedance explorer application.
- */
-@Injectable()
-export class ExceedanceExplorerAppEffects {
-  /**
-   * Calculate exceedance and add new plot data.
-   */
-  calculateExceedance$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.calculateExceedance),
-      concatLatestFrom(() =>
-        this.store.select(
-          exceedanceExplorerAppFeature.selectExceedanceExplorerState
-        )
-      ),
-      map(([, state]) => {
-        return actions.plots({plots: exceedanceAddPlotData(state)});
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  constructor(
-    private actions$: Actions,
-    private store: Store,
-    private nshmpService: NshmpService
-  ) {}
-}
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.facade.ts
index ecf9d0606..6c8fb6f5f 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.facade.ts
@@ -1,12 +1,16 @@
-import {Injectable} from '@angular/core';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
-import {select, Store} from '@ngrx/store';
-import {FormGroupState} from 'ngrx-forms';
-import {Observable} from 'rxjs';
+import {computed, Injectable, Signal, signal} from '@angular/core';
+import {FormBuilder} from '@angular/forms';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {ControlForm} from '../models/control-form.model';
-import {actions} from './app.actions';
-import {exceedanceExplorerAppFeature} from './app.reducer';
+import {Plots} from '../models/plots.model';
+import {
+  EXCEEDANCE_EXPLORER_DEFAULT_FORM_VALUES,
+  exceedanceExplorerDefaultPlots,
+} from '../utils/app.default-values';
+import {exceedanceAddPlotData} from '../utils/app.utils';
+import {addValidators} from '../utils/control-panel.validators';
+import {AppState, EXCEEDANCE_EXPLORER_INTIAL_STATE} from './app.state';
 
 /**
  * Entrypoint for accessing NGRX store.
@@ -15,58 +19,64 @@ import {exceedanceExplorerAppFeature} from './app.reducer';
   providedIn: 'root',
 })
 export class ExceedanceExplorerFacade {
-  constructor(private store: Store) {}
+  /** Control panels forms */
+  readonly formGroup = this.formBuilder.group<ControlForm>(
+    EXCEEDANCE_EXPLORER_DEFAULT_FORM_VALUES
+  );
 
-  /**
-   * Returns the control panel form.
-   */
-  get form$(): Observable<FormGroupState<ControlForm>> {
-    return this.store.pipe(
-      select(exceedanceExplorerAppFeature.selectControlForm)
-    );
+  private state = signal<AppState>(EXCEEDANCE_EXPLORER_INTIAL_STATE);
+
+  constructor(private formBuilder: FormBuilder) {
+    addValidators(this.formGroup);
   }
 
   /**
    * Returns the exceedance plot.
    */
-  get plot$(): Observable<NshmpPlot> {
-    return this.store.pipe(
-      select(exceedanceExplorerAppFeature.selectExceedancePlot)
-    );
+  get plot(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots.get(Plots.EXCEEDANCE));
   }
 
   /**
    * Calculate exceedance.
    */
   calculateExceedance(): void {
-    this.store.dispatch(actions.calculateExceedance());
+    this.state.set({
+      plots: exceedanceAddPlotData(this.state(), this.formGroup),
+    });
   }
 
   /**
    * Clear the plot.
    */
   clearPlot(): void {
-    this.store.dispatch(actions.clearPlot());
+    this.state.set(EXCEEDANCE_EXPLORER_INTIAL_STATE);
   }
 
   /**
-   * Initialize exceedance application.
-   */
-  init(): void {
-    this.store.dispatch(actions.init());
-  }
-
-  /**
-   * Redraw the plot.
+   * Reset the plot settings.
    */
-  plotRedraw(): void {
-    this.store.dispatch(actions.plotRedraw());
+  resetSettings(): void {
+    this.state().plots.forEach((plot, id) => {
+      const defaultSettings =
+        exceedanceExplorerDefaultPlots().get(id).settingsForm;
+      plot.settingsForm.markAsUntouched();
+      plot.settingsForm.markAsPristine();
+      plot.settingsForm.patchValue(defaultSettings.getRawValue());
+    });
   }
 
   /**
-   * Reset the plot settings.
+   * Update a single plot in the state.
+   *
+   * @param plot Updated plot
+   * @param id Id of plot to update
    */
-  resetSettings(): void {
-    this.store.dispatch(actions.resetSettings());
+  updatePlot(plot: NshmpPlot, id: string): void {
+    const plots = this.state().plots;
+    plots.set(id, plot);
+    this.state.set({
+      plots,
+    });
   }
 }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.reducer.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.reducer.ts
deleted file mode 100644
index 993bc6411..000000000
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.reducer.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import {createFeature, createReducer, createSelector, on} from '@ngrx/store';
-import {
-  onNgrxForms,
-  onNgrxFormsAction,
-  SetValueAction,
-  wrapReducerWithFormStateUpdate,
-} from 'ngrx-forms';
-import {
-  onPlotRedraw,
-  onPlots,
-  onPlotSettingsForm,
-  onResetSetting,
-} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {Plots} from '../models/plots.model';
-import {
-  EXCEEDANCE_SETTINGS_ID,
-  exceedanceExplorerDefaultPlots,
-} from '../utils/app.default-values';
-import {validateControlPanelForm} from '../utils/control-panel.validators';
-import {actions} from './app.actions';
-import {EXCEEDANCE_EXPLORER_INTIAL_STATE} from './app.state';
-
-/**
- * Exceedance explorer NGRX feature.
- */
-export const exceedanceExplorerAppFeature = createFeature({
-  extraSelectors: ({selectPlots}) => ({
-    selectExceedancePlot: createSelector(selectPlots, plots =>
-      plots.get(Plots.EXCEEDANCE)
-    ),
-  }),
-  name: 'exceedanceExplorer',
-  reducer: createReducer(
-    /** Initial state */
-    EXCEEDANCE_EXPLORER_INTIAL_STATE,
-    /* On NGRX forms */
-    onNgrxForms(),
-    /* On NGRX form change */
-    onNgrxFormsAction(SetValueAction, (state, action) => {
-      if (action.controlId.includes(EXCEEDANCE_SETTINGS_ID)) {
-        return onPlotSettingsForm(state, action);
-      } else {
-        return {
-          ...state,
-        };
-      }
-    }),
-    /* On init action */
-    on(actions.init, () => {
-      return {
-        ...EXCEEDANCE_EXPLORER_INTIAL_STATE,
-      };
-    }),
-    /* On plots action */
-    on(actions.plots, (state, {plots}) => {
-      return onPlots(state, plots);
-    }),
-    /* On plot redraw action */
-    on(actions.plotRedraw, state => {
-      return onPlotRedraw(state);
-    }),
-    /* On reset settings action */
-    on(actions.resetSettings, state => {
-      return onResetSetting(state, exceedanceExplorerDefaultPlots());
-    }),
-    /* On clear plot action */
-    on(actions.clearPlot, state => {
-      return {
-        ...state,
-        plots: exceedanceExplorerDefaultPlots(),
-      };
-    })
-  ),
-});
-
-/**
- * Application NGRX reducer with validators.
- */
-exceedanceExplorerAppFeature.reducer = wrapReducerWithFormStateUpdate(
-  exceedanceExplorerAppFeature.reducer,
-  state => state.controlForm,
-  validateControlPanelForm
-);
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.state.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.state.ts
index f16a2fbef..96f22f248 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/state/app.state.ts
@@ -1,51 +1,38 @@
-import {
-  createFormGroupState,
-  FormGroupState,
-  setUserDefinedProperty,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
-import {ControlForm} from '../models/control-form.model';
-import {
-  EXCEEDANCE_EXPLORER_DEFAULT_FORM_VALUES,
-  exceedanceExplorerDefaultPlots,
-} from '../utils/app.default-values';
-import {EXCEEDANCE_FORM_BOUNDS} from '../utils/app.utils';
+import {exceedanceExplorerDefaultPlots} from '../utils/app.default-values';
 
 export const EXCEEDANCE_EXPLORER_FORM_ID =
   'ngrx-forms [Exceedance Explorer Control Form]';
 
-/**
- * Inital form state for exceedance control panel.
- */
-export const EXCEEDANCE_EXPLORER_INTITAL_FORM_STATE = updateGroup<ControlForm>(
-  createFormGroupState<ControlForm>(EXCEEDANCE_EXPLORER_FORM_ID, {
-    ...EXCEEDANCE_EXPLORER_DEFAULT_FORM_VALUES,
-  }),
-  {
-    median: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.median),
-    rate: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.rate),
-    sigma: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.sigma),
-    truncationLevel: setUserDefinedProperty(
-      'bounds',
-      EXCEEDANCE_FORM_BOUNDS.truncationLevel
-    ),
-  }
-);
+// /**
+//  * Inital form state for exceedance control panel.
+//  */
+// export const EXCEEDANCE_EXPLORER_INTITAL_FORM_STATE = updateGroup<ControlForm>(
+//   createFormGroupState<ControlForm>(EXCEEDANCE_EXPLORER_FORM_ID, {
+//     ...EXCEEDANCE_EXPLORER_DEFAULT_FORM_VALUES,
+//   }),
+//   {
+//     median: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.median),
+//     rate: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.rate),
+//     sigma: setUserDefinedProperty('bounds', EXCEEDANCE_FORM_BOUNDS.sigma),
+//     truncationLevel: setUserDefinedProperty(
+//       'bounds',
+//       EXCEEDANCE_FORM_BOUNDS.truncationLevel
+//     ),
+//   }
+// );
 
 /**
  * The exceedance app state.
  */
-export interface AppState extends SharedAppState {
-  /** Control form state */
-  controlForm: FormGroupState<ControlForm>;
+export interface AppState {
+  plots: Map<string, NshmpPlot>;
 }
 
 /**
  * Initial exceedance app state.
  */
 export const EXCEEDANCE_EXPLORER_INTIAL_STATE: AppState = {
-  controlForm: EXCEEDANCE_EXPLORER_INTITAL_FORM_STATE,
   plots: exceedanceExplorerDefaultPlots(),
 };
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.default-values.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.default-values.ts
index 170519b50..02c96828a 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.default-values.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.default-values.ts
@@ -3,15 +3,11 @@ import {
   NshmpPlotSettings,
   PlotOptions,
   plotUtils,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {createFormGroupState} from 'ngrx-forms';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {ControlForm} from '../models/control-form.model';
 import {Plots} from '../models/plots.model';
 
-export const EXCEEDANCE_SETTINGS_ID =
-  'ngrx-forms [Exceedance Explorer Settings Form]';
-
 /**
  * Exceedance default control panel form values.
  */
@@ -31,12 +27,7 @@ export function exceedanceExplorerDefaultPlots(): Map<string, NshmpPlot> {
   plots.set(Plots.EXCEEDANCE, {
     label: 'Exceedance',
     plotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      EXCEEDANCE_SETTINGS_ID,
-      {
-        ...settingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(settingsForm),
   });
   return plots;
 }
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.utils.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.utils.ts
index 7250c0e0c..ae0272f5a 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.utils.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/app.utils.ts
@@ -1,5 +1,5 @@
-import {NumberBounds} from '@ghsc/nshmp-lib-ng/nshmp';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {FormGroupControls, NumberBounds} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {ExceedanceModel, Maths} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/calc';
 import {UncertaintyModel} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data';
 import * as d3 from 'd3-array';
@@ -69,8 +69,11 @@ export const EXCEEDANCE_X_VALUES: number[] = d3.ticks(
  *
  * @param state The current state
  */
-export function exceedanceAddPlotData(state: AppState): Map<string, NshmpPlot> {
-  const exceedanceData = calculateExceedance(state.controlForm.value);
+export function exceedanceAddPlotData(
+  state: AppState,
+  formGroup: FormGroupControls<ControlForm>
+): Map<string, NshmpPlot> {
+  const exceedanceData = calculateExceedance(formGroup.getRawValue());
   const plots = new Map<string, NshmpPlot>();
   const plot = state.plots.get(Plots.EXCEEDANCE);
   const data = [...plot.plotData.data];
diff --git a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/control-panel.validators.ts b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/control-panel.validators.ts
index 34b866adc..7128d96f3 100644
--- a/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/control-panel.validators.ts
+++ b/projects/nshmp-apps/src/app/dev/math/exceedance-explorer/utils/control-panel.validators.ts
@@ -1,16 +1,29 @@
-import {nshmpUtils} from '@ghsc/nshmp-lib-ng/nshmp';
-import {updateGroup, validate} from 'ngrx-forms';
-import {required} from 'ngrx-forms/validation';
+import {FormControl, Validators} from '@angular/forms';
+import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {ControlForm} from '../models/control-form.model';
+import {EXCEEDANCE_FORM_BOUNDS, FormBounds} from './app.utils';
 
 /**
  * Validate control panel form.
  */
-export const validateControlPanelForm = updateGroup<ControlForm>({
-  median: nshmpUtils.validateBounds,
-  rate: nshmpUtils.validateBounds,
-  sigma: nshmpUtils.validateBounds,
-  truncation: validate([required]),
-  truncationLevel: nshmpUtils.validateBounds,
-});
+export function addValidators(formGroup: FormGroupControls<ControlForm>): void {
+  boundsValidators(formGroup.controls.median, EXCEEDANCE_FORM_BOUNDS.median);
+  boundsValidators(formGroup.controls.rate, EXCEEDANCE_FORM_BOUNDS.rate);
+  boundsValidators(formGroup.controls.sigma, EXCEEDANCE_FORM_BOUNDS.sigma);
+  boundsValidators(
+    formGroup.controls.truncationLevel,
+    EXCEEDANCE_FORM_BOUNDS.truncationLevel
+  );
+  formGroup.controls.truncation.addValidators(control =>
+    Validators.required(control)
+  );
+}
+
+function boundsValidators(control: FormControl, bounds: FormBounds): void {
+  control.addValidators([
+    Validators.min(bounds.min),
+    Validators.max(bounds.max),
+    c => Validators.required(c),
+  ]);
+}
diff --git a/projects/nshmp-apps/src/app/dev/math/math.routes.ts b/projects/nshmp-apps/src/app/dev/math/math.routes.ts
index 7568c2d8f..ce59f47f5 100644
--- a/projects/nshmp-apps/src/app/dev/math/math.routes.ts
+++ b/projects/nshmp-apps/src/app/dev/math/math.routes.ts
@@ -1,9 +1,4 @@
 import {Routes} from '@angular/router';
-import {provideEffects} from '@ngrx/effects';
-import {provideState} from '@ngrx/store';
-
-import {ExceedanceExplorerAppEffects} from './exceedance-explorer/state/app.effects';
-import {exceedanceExplorerAppFeature} from './exceedance-explorer/state/app.reducer';
 
 /** Routes for math applications */
 const routes: Routes = [
@@ -13,10 +8,6 @@ const routes: Routes = [
         com => com.AppComponent
       ),
     path: 'exceedance-explorer',
-    providers: [
-      provideState(exceedanceExplorerAppFeature),
-      provideEffects(ExceedanceExplorerAppEffects),
-    ],
   },
 ];
 
-- 
GitLab