From c5c600e9fb8f32d8d2b3a8c01d58c7c536049cb7 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Mon, 29 Jul 2024 12:42:57 -0600
Subject: [PATCH 01/10] remove state

---
 projects/nshmp-apps/src/app/gmm/gmm.routes.ts |   6 -
 .../app/gmm/magnitude/state/app.actions.ts    |  22 --
 .../app/gmm/magnitude/state/app.effects.ts    | 147 ---------
 .../app/gmm/magnitude/state/app.reducer.ts    | 296 ------------------
 4 files changed, 471 deletions(-)
 delete mode 100644 projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts
 delete mode 100644 projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts
 delete mode 100644 projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts

diff --git a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
index 52138a14c..b20f1a872 100644
--- a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
+++ b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
@@ -2,8 +2,6 @@ import {Routes} from '@angular/router';
 import {provideEffects} from '@ngrx/effects';
 import {provideState} from '@ngrx/store';
 
-import {MagnitudeAppEffects} from './magnitude/state/app.effects';
-import {magnitudeAppFeature} from './magnitude/state/app.reducer';
 import {SpectraAppEffects} from './spectra/state/app.effects';
 import {spectraAppFeature} from './spectra/state/app.reducer';
 
@@ -18,10 +16,6 @@ const routes: Routes = [
     loadComponent: () =>
       import('./magnitude/app.component').then(com => com.AppComponent),
     path: 'magnitude',
-    providers: [
-      provideState(magnitudeAppFeature),
-      provideEffects(MagnitudeAppEffects),
-    ],
   },
   {
     loadComponent: () =>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts
deleted file mode 100644
index 26382a0d2..000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import {
-  GmmMagnitudeResponse,
-  GmmMagnitudeUsage,
-} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {createActionGroup, emptyProps, props} from '@ngrx/store';
-import {sharedActions} from 'projects/nshmp-apps/src/shared/state/shared';
-
-/**
- * GMM vs magnitude NGRX actions.
- */
-export const actions = createActionGroup({
-  events: {
-    ...sharedActions,
-    /** Action to set the initial form values from query or default */
-    'Initial Form Set': emptyProps(),
-    /** Service response action */
-    'Service Response': props<{serviceResponses: GmmMagnitudeResponse[]}>(),
-    /** Usage response action */
-    'Usage Response': props<{usageResponses: GmmMagnitudeUsage}>(),
-  },
-  source: 'GMM vs Magnitude',
-});
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts
deleted file mode 100644
index a45bc5824..000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import {Injectable} from '@angular/core';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpService, SpinnerService} from '@ghsc/nshmp-lib-ng/nshmp';
-import {plotUtils} from '@ghsc/nshmp-lib-ng/plot';
-import {
-  GmmMagnitudeResponse,
-  GmmMagnitudeUsage,
-} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {Actions, createEffect, ofType} from '@ngrx/effects';
-import {concatLatestFrom} from '@ngrx/operators';
-import {Store} from '@ngrx/store';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {catchError, exhaustMap, map, mergeMap} from 'rxjs/operators';
-
-import {defaultFormValues} from '../utils/app.default-values';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {AppFacade} from './app.facade';
-import {magnitudeAppFeature} from './app.reducer';
-
-/**
- * NGRX application effects.
- */
-@Injectable()
-export class MagnitudeAppEffects {
-  /** nshmp-ws base URL */
-  baseUrl = environment.webServices.data.url;
-  /** GMM service URL */
-  serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmMagnitude}`;
-
-  /**
-   * Call the services.
-   */
-  callService$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.callServices),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectGmmMagnitudeAppState)
-      ),
-      exhaustMap(([, state]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
-
-        const urls = gmmUtils.serviceEndpoints(
-          this.serviceUrl,
-          state.controlForm.value,
-          state.controlForm.value.multiSelectableParam
-        );
-
-        return this.nshmpService.callServices$(urls).pipe(
-          mergeMap((serviceResponses: GmmMagnitudeResponse[]) => {
-            return [actions.serviceResponse({serviceResponses})];
-          }),
-          catchError((error: Error) => this.nshmpService.throwError$(error))
-        );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Transform the service responses to plot data.
-   */
-  createData$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.serviceResponse),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectGmmMagnitudeAppState)
-      ),
-      map(([, state]) => {
-        const plots = plotUtils.updateAppPlotSettings(
-          serviceResponseToPlotData(state)
-        );
-        this.spinnerService.remove();
-        return actions.plots({plots});
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Initialize the application.
-   */
-  init$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.init),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectControlForm)
-      ),
-      exhaustMap(([, form]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
-        return this.nshmpService
-          .callService$<GmmMagnitudeUsage>(this.serviceUrl)
-          .pipe(
-            mergeMap(usage => {
-              const parameters = usage.response.parameters;
-              const controls = form.controls;
-              const values = defaultFormValues(parameters);
-
-              const typedActions = gmmUtils.initialFormSetActions(
-                form,
-                values,
-                parameters
-              );
-              typedActions.push(actions.usageResponse({usageResponses: usage}));
-              gmmUtils.addFormAction(
-                parameters.distance,
-                controls.distance.id,
-                values.distance,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.mMax,
-                controls.mMax.id,
-                values.mMax,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.mMin,
-                controls.mMin.id,
-                values.mMin,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.step,
-                controls.step.id,
-                values.step,
-                typedActions
-              );
-
-              this.spinnerService.remove();
-              return typedActions;
-            }),
-            catchError((error: Error) => this.nshmpService.throwError$(error))
-          );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  constructor(
-    private actions$: Actions,
-    private facade: AppFacade,
-    private nshmpService: NshmpService,
-    private spinnerService: SpinnerService,
-    private store: Store
-  ) {}
-}
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts
deleted file mode 100644
index 137ee7d8a..000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
-import {createFeature, createReducer, createSelector, on} from '@ngrx/store';
-import {
-  box,
-  createFormGroupState,
-  disable,
-  enable,
-  onNgrxForms,
-  onNgrxFormsAction,
-  setValue,
-  SetValueAction,
-  unbox,
-  updateGroup,
-  wrapReducerWithFormStateUpdate,
-} from 'ngrx-forms';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {
-  onPlotRedraw,
-  onPlotSettingsForm,
-  onResetSetting,
-} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {
-  defaultFormValues,
-  defaultPlots,
-  MEAN_PLOT_SETTING_ID,
-  SIGMA_PLOT_SETTING_ID,
-} from '../utils/app.default-values';
-import {validateControlPanelForm} from '../utils/control-panel.validators';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {AppState, CONTROL_FORM_ID, INITIAL_STATE} from './app.state';
-
-/** Form state key ids */
-const formKeys = {
-  control: {
-    gmmSource: `${CONTROL_FORM_ID}.${GmmFormControlIds.GMM_SOURCE}`,
-    multiSelectable: `${CONTROL_FORM_ID}.${GmmFormControlIds.MULTI_SELECTABLE_PARAM}`,
-    Mw: `${CONTROL_FORM_ID}.${GmmFormControlIds.MW}`,
-    showEpistemicUncertainty: `${CONTROL_FORM_ID}.${GmmFormControlIds.SHOW_EPISTEMIC_UNCERTAINTY}`,
-    vs30: `${CONTROL_FORM_ID}.${GmmFormControlIds.VS30}`,
-  },
-  settings: {
-    mean: MEAN_PLOT_SETTING_ID,
-    sigma: SIGMA_PLOT_SETTING_ID,
-  },
-};
-
-const baseUrl = environment.webServices.data.url;
-const endpoint = environment.webServices.data.services.gmmMagnitude;
-const serviceUrl = `${baseUrl}${endpoint}`;
-
-/**
- * NGRX application raw reducer.
- */
-export const magnitudeAppFeature = createFeature({
-  extraSelectors: ({selectPlots}) => ({
-    selectMeanPlot: createSelector(selectPlots, plots =>
-      plots.get(gmmUtils.PlotType.MEANS)
-    ),
-    selectSigmaPlot: createSelector(selectPlots, plots =>
-      plots.get(gmmUtils.PlotType.SIGMA)
-    ),
-  }),
-  name: 'gmmMagnitudeApp',
-  reducer: createReducer(
-    /** Application intitial sate */
-    INITIAL_STATE,
-    /* On NGRX forms */
-    onNgrxForms(),
-    /* On form change */
-    onNgrxFormsAction(SetValueAction, (state, action) => {
-      const clearPlots = new Map<string, NshmpPlot>();
-
-      state.plots.forEach((plot, key) =>
-        clearPlots.set(key, {
-          ...plot,
-          plotData: {
-            ...plot.plotData,
-            data: [
-              ...plot.plotData.data.map(data => {
-                data = {...data};
-                data.x = [];
-                data.y = [];
-                return data;
-              }),
-            ],
-          },
-        })
-      );
-
-      const clearState: AppState = {
-        ...state,
-        controlForm: updateGroup<GmmMagnitudeFormControls>({
-          showEpistemicUncertainty: control =>
-            disable(setValue(control, false)),
-        })(state.controlForm),
-        plots: clearPlots,
-        serviceResponses: null,
-      };
-
-      switch (action.controlId) {
-        case formKeys.control.multiSelectable: {
-          let form = {...clearState.controlForm};
-          const parameters =
-            clearState.usageResponses?.response?.parameters || null;
-
-          if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-            form = updateGroup<GmmMagnitudeFormControls>({
-              gmmSource: gmm => setValue(gmm, box([])),
-              vs30: vs30 =>
-                setValue(vs30, (parameters.vs30.value as number) || null),
-              vs30Multi: vs30 => setValue(vs30, box([])),
-            })(clearState.controlForm);
-          }
-
-          return {
-            ...clearState,
-            controlForm: form,
-            serviceCallInfo: gmmUtils.serviceCallInfo({
-              multiSelectableParam: form.value.multiSelectableParam,
-              serviceName: clearState.serviceCallInfo.serviceName,
-              serviceResponses: clearState.serviceResponses,
-              serviceUrl,
-              values: form.value,
-            }),
-          };
-        }
-        case formKeys.control.gmmSource: {
-          const gmm = unbox(clearState.controlForm.value.gmmSource);
-          const serviceCallInfo = gmmUtils.serviceCallInfo({
-            multiSelectableParam:
-              clearState.controlForm.value.multiSelectableParam,
-            serviceName: clearState.serviceCallInfo.serviceName,
-            serviceResponses: clearState.serviceResponses,
-            serviceUrl,
-            values: clearState.controlForm.value,
-          });
-
-          if (
-            clearState.usageResponses &&
-            clearState.controlForm.controls.gmmSource &&
-            gmm.length > 0
-          ) {
-            const supportedImts = gmmUtils.getSupportedImts(
-              gmm,
-              clearState.usageResponses
-            );
-
-            const controlForm = gmmUtils.onGmmChange(
-              clearState.controlForm,
-              supportedImts
-            );
-
-            return {
-              ...clearState,
-              controlForm,
-              serviceCallInfo,
-              supportedImts,
-            };
-          }
-          return {
-            ...clearState,
-            serviceCallInfo,
-          };
-        }
-        case formKeys.control.showEpistemicUncertainty: {
-          return {
-            ...state,
-            plots: serviceResponseToPlotData(state),
-          };
-        }
-        default: {
-          if (
-            action.controlId.includes(formKeys.settings.mean) ||
-            action.controlId.includes(formKeys.settings.sigma)
-          ) {
-            return onPlotSettingsForm(state, action);
-          } else {
-            return {
-              ...clearState,
-              serviceCallInfo: gmmUtils.serviceCallInfo({
-                multiSelectableParam:
-                  clearState.controlForm.value.multiSelectableParam,
-                serviceName: clearState.serviceCallInfo.serviceName,
-                serviceResponses: clearState.serviceResponses,
-                serviceUrl,
-                values: clearState.controlForm.value,
-              }),
-            };
-          }
-        }
-      }
-    }),
-    /* On init action */
-    on(actions.init, () => {
-      return {
-        ...INITIAL_STATE,
-      };
-    }),
-    /* On reset control panel action */
-    on(actions.resetControlPanel, state => {
-      const controlForm = createFormGroupState<GmmMagnitudeFormControls>(
-        CONTROL_FORM_ID,
-        defaultFormValues(state.usageResponses.response.parameters)
-      );
-
-      state = {
-        ...state,
-        controlForm,
-        plots: INITIAL_STATE.plots,
-        serviceCallInfo: INITIAL_STATE.serviceCallInfo,
-        serviceResponses: INITIAL_STATE.serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On reset plot settings action */
-    on(actions.resetSettings, state => {
-      return onResetSetting(state, defaultPlots());
-    }),
-    /* On plots action */
-    on(actions.plots, (state, {plots}) => {
-      return {
-        ...state,
-        plots,
-      };
-    }),
-    /* On plot redraw action */
-    on(actions.plotRedraw, state => {
-      return onPlotRedraw(state);
-    }),
-    /* On service response action */
-    on(actions.serviceResponse, (state, {serviceResponses}) => {
-      const means = serviceResponses.map(s => s.response.means);
-      const sigmas = serviceResponses.map(s => s.response.sigmas);
-      const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
-
-      state = {
-        ...state,
-        controlForm: updateGroup<GmmMagnitudeFormControls>({
-          showEpistemicUncertainty: hasLogicTree ? enable : disable,
-        })(state.controlForm),
-        serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On service call info action */
-    on(actions.serviceCallInfo, (state, {serviceCallInfo}) => {
-      return {
-        ...state,
-        serviceCallInfo: {
-          ...state.serviceCallInfo,
-          ...serviceCallInfo,
-        },
-      };
-    }),
-    /* On usage response action */
-    on(actions.usageResponse, (state, {usageResponses}) => {
-      return {
-        ...state,
-        usageResponses,
-      };
-    })
-  ),
-});
-
-/**
- * Application NGRX reducer with validators.
- */
-magnitudeAppFeature.reducer = wrapReducerWithFormStateUpdate(
-  magnitudeAppFeature.reducer,
-  state => state.controlForm,
-  validateControlPanelForm
-);
-- 
GitLab


From 49fe02840d5a931573aafeacbbfeda4cd6c42b8a Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Mon, 29 Jul 2024 12:43:07 -0600
Subject: [PATCH 02/10] switch to signals and reactive forms

---
 .../control-panel/control-panel.component.ts  |   7 +-
 .../components/content/content.component.html |  16 +-
 .../components/content/content.component.ts   |  39 +--
 .../control-panel.component.html              | 113 +++++----
 .../control-panel/control-panel.component.ts  | 121 +++++++--
 .../event-parameters.component.html           | 118 +++++----
 .../event-parameters.component.ts             |  40 +--
 .../parameter-summary.component.html          | 157 ++++++------
 .../parameter-summary.component.ts            |  14 +-
 .../path-parameters.component.html            |  48 ++--
 .../path-parameters.component.ts              |  38 +--
 .../plots-settings.component.html             |  19 +-
 .../plots-settings.component.ts               |  31 ++-
 .../components/plots/plots.component.html     |  14 +-
 .../components/plots/plots.component.ts       |  15 +-
 .../site-parameters.component.html            | 142 ++++++-----
 .../site-parameters.component.ts              |  44 ++--
 .../source-parameters.component.html          | 120 +++++----
 .../source-parameters.component.ts            |  32 ++-
 .../gmm-magnitude-form-controls.model.ts      |   5 +-
 .../src/app/gmm/magnitude/state/app.facade.ts | 229 +++++++++++++++---
 .../src/app/gmm/magnitude/state/app.state.ts  |  44 +---
 .../gmm/magnitude/utils/app.default-values.ts |  25 +-
 .../gmm/magnitude/utils/response.handler.ts   |  17 +-
 24 files changed, 842 insertions(+), 606 deletions(-)

diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
index e4718bf16..de3142140 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
@@ -115,12 +115,6 @@ export class ControlPanelComponent implements OnInit, OnDestroy {
     this.subs.push(
       combineLatest([
         this.controls.dip.valueChanges,
-        this.controls.width.valueChanges,
-      ]).subscribe(() => this.updatePlots())
-    );
-
-    this.subs.push(
-      combineLatest([
         this.controls.imt.valueChanges,
         this.controls.Mw.valueChanges,
         this.controls.MwMulti.valueChanges,
@@ -129,6 +123,7 @@ export class ControlPanelComponent implements OnInit, OnDestroy {
         this.controls.z1p0.valueChanges,
         this.controls.z2p5.valueChanges,
         this.controls.zSed.valueChanges,
+        this.controls.width.valueChanges,
       ]).subscribe(() => this.facade.resetState())
     );
   }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
index 3ce3d99ca..045a08715 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
@@ -10,15 +10,15 @@
   <mat-tab
     labelClass="medians-tab"
     label="Medians"
-    [disabled]="(hasData$ | async) === false"
+    [disabled]="hasData() === false"
   >
     <ng-template matTabContent>
-      <nshmp-lib-ng-export-data-table
-        [table]="meanTable$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="meanTable()"
         filename="gmm-magnitude-means.csv"
         buttonText="Export Means as CSV"
       />
-      <nshmp-lib-ng-data-table [table]="meanTable$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="meanTable()" />
     </ng-template>
   </mat-tab>
 
@@ -26,15 +26,15 @@
   <mat-tab
     labelClass="sigmas-tab"
     label="Sigmas"
-    [disabled]="(hasData$ | async) === false"
+    [disabled]="hasData() === false"
   >
     <ng-template matTabContent>
-      <nshmp-lib-ng-export-data-table
-        [table]="sigmaTable$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="sigmaTable()"
         filename="gmm-magnitude-sigmas.csv"
         buttonText="Export Sigmas as CSV"
       />
-      <nshmp-lib-ng-data-table [table]="sigmaTable$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="sigmaTable()" />
     </ng-template>
   </mat-tab>
 </mat-tab-group>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
index b67a804fd..f8561f3c8 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
@@ -1,12 +1,11 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatTab, MatTabContent, MatTabGroup} from '@angular/material/tabs';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgDataTableComponent,
   NshmpLibNgExportDataTableComponent,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {AppFacade} from '../../state/app.facade';
 import {PlotsComponent} from '../plots/plots.component';
@@ -41,23 +40,29 @@ export class ContentComponent {
   sigmaExp = false;
 
   /** Whether service has been called and data exists */
-  hasData$ = this.facade.serviceResponse$.pipe(
-    map(responseSpectra => responseSpectra?.length > 0)
-  );
+  hasData = computed(() => this.facade.serviceResponse()?.length > 0);
 
   /** Table data for mean data */
-  meanTable$ = this.facade.meanPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(plot.plotData, this.medianExp, this.commonXValues)
-    )
-  );
+  meanTable = computed(() => {
+    const plot = this.facade.meanPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.medianExp,
+      this.commonXValues
+    );
+  });
 
   /** Table data for sigma table */
-  sigmaTable$ = this.facade.sigmaPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(plot.plotData, this.sigmaExp, this.commonXValues)
-    )
-  );
+  sigmaTable = computed(() => {
+    const plot = this.facade.sigmaPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.medianExp,
+      this.commonXValues
+    );
+  });
 
   constructor(public facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
index f9fb679e7..4fa3ff227 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
@@ -1,70 +1,65 @@
 <!-- Ground Motion vs. Magnitude Conrol Panel -->
-@if (form$ | async) {
-  <form
-    class="settings-section control-panel height-full overflow-auto"
-    [ngrxFormState]="form$ | async"
-    (submit)="onSubmit()"
-    novalidate
-  >
-    <mat-form-field
-      class="grid-col-12 multi-select-parameter-menu padding-top-1"
-    >
-      <mat-label>Multi-Selectable Parameter</mat-label>
-      <mat-select
-        [ngrxFormControlState]="(controls$ | async)?.multiSelectableParam"
-      >
-        <mat-option value="gmm">Ground Motion Models</mat-option>
-        <mat-option value="vs30">V30</mat-option>
-      </mat-select>
-    </mat-form-field>
+<form
+  class="settings-section control-panel height-full overflow-auto"
+  [formGroup]="form"
+  (submit)="onSubmit()"
+  novalidate
+>
+  <mat-form-field class="grid-col-12 multi-select-parameter-menu padding-top-1">
+    <mat-label>Multi-Selectable Parameter</mat-label>
+    <mat-select [formControl]="controls.multiSelectableParam">
+      <mat-option value="gmm">Ground Motion Models</mat-option>
+      <mat-option value="vs30">V30</mat-option>
+    </mat-select>
+  </mat-form-field>
 
-    <!-- GMM menu -->
-    <nshmp-lib-ng-gmm-menu
-      [gmmControlState]="(controls$ | async)?.gmmSource"
-      [gmmGroupTypeState]="(controls$ | async)?.gmmGroupType"
-      [multiple]="(controls$ | async)?.multiSelectableParam.value === 'gmm'"
-      [parameters]="parameters$ | async"
+  <!-- GMM menu -->
+  @if (parameters()) {
+    <nshmp-lib-no-ngrx-gmm-menu
+      [gmmControl]="controls.gmmSource"
+      [gmmGroupTypeControl]="controls.gmmGroupType"
+      [multiple]="controls.multiSelectableParam.value === 'gmm'"
+      [parameters]="parameters()"
+      [imtControl]="controls.imt"
+      [supportedImts]="supportedImts()"
     />
+  }
 
-    <!-- IMT select menu -->
-    <mat-form-field class="grid-col-12 imt-select">
-      <mat-label>Intensity Measure Type</mat-label>
-      <mat-select
-        #imtSelectEl
-        [ngrxFormControlState]="(controls$ | async)?.imt"
-      >
-        @if ((gmmSelected$ | async) === false) {
-          <mat-option selected="true" value="default">
-            --- Choose a GMM ---
-          </mat-option>
-        }
-        @for (imt of supportedImts$ | async; track imt) {
-          <mat-option [value]="imt.value">
-            {{ imt.display }}
-          </mat-option>
-        }
-      </mat-select>
-    </mat-form-field>
+  <!-- IMT select menu -->
+  <mat-form-field class="grid-col-12 imt-select">
+    <mat-label>Intensity Measure Type</mat-label>
+    <mat-select #imtSelectEl [formControl]="controls.imt">
+      @if (gmmSelected === false) {
+        <mat-option selected="true" value="default">
+          --- Choose a GMM ---
+        </mat-option>
+      }
+      @for (imt of supportedImts(); track imt) {
+        <mat-option [value]="imt.value">
+          {{ imt.display }}
+        </mat-option>
+      }
+    </mat-select>
+  </mat-form-field>
 
-    <app-event-parameters />
+  <app-event-parameters />
 
-    <app-source-parameters />
+  <app-source-parameters />
 
-    <app-path-parameters />
+  <app-path-parameters />
 
-    <app-site-parameters />
+  <app-site-parameters />
 
-    <nshmp-lib-ng-gmm-plot-options-control-panel
-      [showEpistemicFormControl]="(controls$ | async)?.showEpistemicUncertainty"
-    />
+  <nshmp-lib-no-ngrx-gmm-plot-options-control-panel
+    [showEpistemicFormControl]="controls?.showEpistemicUncertainty"
+  />
 
-    <div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-    <nshmp-lib-ng-control-panel-buttons
-      [plotDisabled]="(form$ | async)?.isInvalid"
-      [serviceCallInfo]="serviceCallInfo$ | async"
-      [resetDisabled]="(form$ | async)?.isPristine"
-      (resetClick)="facade.resetControlPanel()"
-    />
-  </form>
-}
+  <nshmp-lib-no-ngrx-control-panel-buttons
+    [plotDisabled]="form.invalid"
+    [serviceCallInfo]="serviceCallInfo()"
+    [resetDisabled]="form.pristine"
+    (resetClick)="facade.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
index 6f463e6b5..19933c757 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
@@ -1,19 +1,20 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatOption} from '@angular/material/core';
 import {MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatSelect} from '@angular/material/select';
 import {
+  GmmFormControlIds,
+  gmmUtils,
   NshmpLibNgGmmMenuComponent,
   NshmpLibNgGmmPlotOptionsControlPanelComponent,
-} from '@ghsc/nshmp-lib-ng/gmm';
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgControlPanelButtonsComponent,
-  NshmpNgrxFormsModule,
   NshmpService,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 import {EventParametersComponent} from '../event-parameters/event-parameters.component';
@@ -38,41 +39,67 @@ import {SourceParametersComponent} from '../source-parameters/source-parameters.
     PathParametersComponent,
     SiteParametersComponent,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-control-panel',
   standalone: true,
   styleUrl: './control-panel.component.scss',
   templateUrl: './control-panel.component.html',
 })
-export class ControlPanelComponent {
+export class ControlPanelComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Whether a GMM has been selected */
-  gmmSelected$ = this.controls$.pipe(
-    map(controls => unbox(controls.gmmSource.value).length > 0)
-  );
+  gmmSelected = this.controls.gmmSource.getRawValue()?.length > 0;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
 
   /** Service call info state */
-  serviceCallInfo$ = this.facade.serviceCallInfo$;
+  serviceCallInfo = this.facade.serviceCallInfo;
 
   /** Supported IMTs based on GMMs selected */
-  supportedImts$ = this.facade.supportedImts$;
+  supportedImts = this.facade.supportedImts;
+
+  private subs: Subscription[] = [];
 
   constructor(
     public facade: AppFacade,
     private nshmpService: NshmpService
   ) {}
 
+  ngOnInit(): void {
+    this.subs.push(
+      this.controls.gmmSource.valueChanges.subscribe(() => {
+        this.onGmmSource();
+      })
+    );
+
+    this.subs.push(
+      this.controls.multiSelectableParam.valueChanges.subscribe(() =>
+        this.onMultiSelectableParam()
+      )
+    );
+
+    this.subs.push(
+      this.controls.showEpistemicUncertainty.valueChanges.subscribe(() =>
+        this.onShowEpistemicUncertainty()
+      )
+    );
+
+    this.subs.push(
+      this.controls.imt.valueChanges.subscribe(() => this.facade.resetState())
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub?.unsubscribe());
+  }
+
   /**
    * On form submit.
    */
@@ -80,4 +107,62 @@ export class ControlPanelComponent {
     this.facade.callService();
     this.nshmpService.selectPlotControl();
   }
+
+  private onGmmSource() {
+    const formValues = this.form.getRawValue();
+
+    if (
+      !this.facade.state().usageResponse ||
+      formValues.gmmSource.length === 0
+    ) {
+      this.controls.imt.patchValue(gmmUtils.imtPlaceHolder().value);
+      this.facade.updateState({
+        supportedImts: [],
+      });
+      return;
+    }
+
+    const supportedImts = gmmUtils.getSupportedImts(
+      formValues.gmmSource,
+      this.facade.state().usageResponse
+    );
+
+    this.facade.updateState({
+      supportedImts,
+    });
+
+    const imt = [...supportedImts].shift();
+
+    this.controls.imt.patchValue(imt?.value);
+    this.facade.resetState();
+  }
+
+  private onMultiSelectableParam(): void {
+    if (!this.facade.state().usageResponse) {
+      return;
+    }
+
+    const {multiSelectableParam} = this.form.getRawValue();
+    const parameters = this.facade.state().usageResponse.response.parameters;
+
+    if (multiSelectableParam === GmmFormControlIds.MW) {
+      this.controls.gmmSource.setValue([]);
+      this.controls.Mw.setValue(parameters.Mw.value as number);
+      this.controls.MwMulti.setValue([]);
+    } else if (multiSelectableParam === GmmFormControlIds.VS30) {
+      this.controls.gmmSource.setValue([]);
+      this.controls.vs30.setValue(parameters.vs30.value as number);
+      this.controls.vs30Multi.setValue([]);
+    }
+
+    this.facade.resetState();
+  }
+
+  private onShowEpistemicUncertainty(): void {
+    this.facade.createPlots();
+  }
+
+  private updatePlots(): void {
+    this.facade.createPlots();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
index bab977bfd..f0a3b452d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
@@ -1,64 +1,62 @@
 <!-- Event parameters -->
-@if (form$ | async) {
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Event Parameters</mat-label>
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Event parameters: mMin input -->
-      <mat-form-field class="grid-col-4 mmin-input">
-        <mat-label>Minimum Mw</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.mMin?.max"
-          [min]="(parameters$ | async)?.mMin?.min"
-          [ngrxFormControlState]="(controls$ | async)?.mMin"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.mMin?.min }},
-          {{ (parameters$ | async)?.mMin?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Event Parameters</mat-label>
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Event parameters: mMin input -->
+    <mat-form-field class="grid-col-4 mmin-input">
+      <mat-label>Minimum Mw</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.mMin?.max"
+        [min]="parameters()?.mMin?.min"
+        [formControl]="controls.mMin"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.mMin?.min }},
+        {{ parameters()?.mMin?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Event parameters: mMax input -->
-      <mat-form-field class="grid-col-4 mmax-input">
-        <mat-label>Maximum Mw</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.mMax?.max"
-          [min]="(parameters$ | async)?.mMax?.min"
-          [ngrxFormControlState]="(controls$ | async)?.mMax"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.mMax?.min }},
-          {{ (parameters$ | async)?.mMax?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Event parameters: mMax input -->
+    <mat-form-field class="grid-col-4 mmax-input">
+      <mat-label>Maximum Mw</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.mMax?.max"
+        [min]="parameters()?.mMax?.min"
+        [formControl]="controls.mMax"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.mMax?.min }},
+        {{ parameters()?.mMax?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Event parameters: Step input -->
-      <mat-form-field class="grid-col-4 mstep-input">
-        <mat-label>Mw Step</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.step?.max"
-          [min]="(parameters$ | async)?.step?.min"
-          [ngrxFormControlState]="(controls$ | async)?.step"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.step?.min }},
-          {{ (parameters$ | async)?.step?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Event parameters: Step input -->
+    <mat-form-field class="grid-col-4 mstep-input">
+      <mat-label>Mw Step</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.step?.max"
+        [min]="parameters()?.step?.min"
+        [formControl]="controls.step"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.step?.min }},
+        {{ parameters()?.step?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
index b9494f380..ddff30560 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
@@ -1,9 +1,8 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {combineLatest, Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -11,30 +10,35 @@ import {AppFacade} from '../../state/app.facade';
  * Control panel form fields for GMM event parameters.
  */
 @Component({
-  imports: [
-    MatLabel,
-    MatFormField,
-    MatInput,
-    MatError,
-    AsyncPipe,
-    NshmpNgrxFormsModule,
-  ],
+  imports: [MatLabel, MatFormField, MatInput, MatError, ReactiveFormsModule],
   selector: 'app-event-parameters',
   standalone: true,
   styleUrl: './event-parameters.component.scss',
   templateUrl: './event-parameters.component.html',
 })
-export class EventParametersComponent {
+export class EventParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private valueSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.valueSubscription = combineLatest([
+      this.controls.mMax.valueChanges,
+      this.controls.mMin.valueChanges,
+      this.controls.step.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
index f0044425a..06e1589fb 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
@@ -1,97 +1,94 @@
-@if (form$ | async; as form) {
-  <div class="grid-row parameter-summary">
-    <div class="grid-col-12 tablet-lg:grid-col-6">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter">Ground Motion Models</span>:
-        </mat-list-item>
-
-        @for (gmmSource of unbox(form?.value?.gmmSource); track gmmSource) {
-          <mat-list-item class="indent-list font-small">
-            {{ gmmSource.gmm.display }}
-          </mat-list-item>
-        }
-      </mat-list>
-    </div>
+<div class="grid-row parameter-summary">
+  <div class="grid-col-12 tablet-lg:grid-col-6">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">Ground Motion Models</span>:
+      </mat-list-item>
 
-    <div class="grid-col-12 tablet-lg:grid-col-4">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter">IMT</span>:
-          {{ imtIdToDisplay(form?.value?.imt) }}
+      @for (gmmSource of form?.value?.gmmSource; track gmmSource) {
+        <mat-list-item class="indent-list font-small">
+          {{ gmmSource.gmm.display }}
         </mat-list-item>
+      }
+    </mat-list>
+  </div>
 
-        <mat-list-item>
-          <span class="parameter">Mw Range</span>: [{{ form?.value?.mMin }},
-          {{ form?.value?.mMax }}]
-        </mat-list-item>
+  <div class="grid-col-12 tablet-lg:grid-col-4">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">IMT</span>:
+        {{ imtIdToDisplay(form?.value?.imt) }}
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Mw Step</span>:
-          {{ form?.value?.step }}
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Mw Range</span>: [{{ form?.value?.mMin }},
+        {{ form?.value?.mMax }}]
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>TOR</sub> </span>:
-          {{ form?.value?.zTor }} km
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Mw Step</span>:
+        {{ form?.value?.step }}
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Dip</span>: {{ form?.value?.dip }}°
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>TOR</sub> </span>:
+        {{ form?.value?.zTor }} km
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Width</span>: {{ form?.value?.width }} km
-        </mat-list-item>
-      </mat-list>
-    </div>
+      <mat-list-item>
+        <span class="parameter">Dip</span>: {{ form?.value?.dip }}°
+      </mat-list-item>
 
-    <div class="grid-col-12 tablet-lg:grid-col-2">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter"> Distance</span>:
-          {{ form?.value?.distance }} km
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Width</span>: {{ form?.value?.width }} km
+      </mat-list-item>
+    </mat-list>
+  </div>
+
+  <div class="grid-col-12 tablet-lg:grid-col-2">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter"> Distance</span>: {{ form?.value?.distance }} km
+      </mat-list-item>
 
-        @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-          <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item>
-          @for (vs30 of unbox(form?.value?.vs30Multi); track vs30) {
-            <mat-list-item class="indent-list">
-              {{ vs30 }}
-              <sup>&nbsp;m</sup>/<sub>s</sub>
-            </mat-list-item>
-          }
-        } @else {
-          <mat-list-item>
-            <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }}
+      @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
+        <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item>
+        @for (vs30 of form?.value?.vs30Multi; track vs30) {
+          <mat-list-item class="indent-list">
+            {{ vs30 }}
             <sup>&nbsp;m</sup>/<sub>s</sub>
           </mat-list-item>
         }
-
+      } @else {
         <mat-list-item>
-          <span class="parameter"> Z<sub>1.0</sub> </span>:
-          {{ form?.value?.z1p0 }}
-          @if (form?.value?.z1p0) {
-            <span>&nbsp;km</span>
-          }
+          <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }}
+          <sup>&nbsp;m</sup>/<sub>s</sub>
         </mat-list-item>
+      }
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>2.5</sub> </span>:
-          {{ form?.value?.z2p5 }}
-          @if (form?.value?.z2p5) {
-            <span>&nbsp;km</span>
-          }
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>1.0</sub> </span>:
+        {{ form?.value?.z1p0 }}
+        @if (form?.value?.z1p0) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>SED</sub> </span>:
-          {{ form?.value?.zSed }}
-          @if (form?.value?.zSed) {
-            <span>&nbsp;km</span>
-          }
-        </mat-list-item>
-      </mat-list>
-    </div>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>2.5</sub> </span>:
+        {{ form?.value?.z2p5 }}
+        @if (form?.value?.z2p5) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> Z<sub>SED</sub> </span>:
+        {{ form?.value?.zSed }}
+        @if (form?.value?.zSed) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+    </mat-list>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
index 166e75cec..ea07924b4 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
@@ -1,9 +1,7 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatList, MatListItem} from '@angular/material/list';
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs';
+import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -23,16 +21,12 @@ export class ParameterSummaryComponent {
 
   /** Function to convert IMT id to display */
   imtIdToDisplay = gmmUtils.imtIdToDisplay;
-  /** ngrx-forms unbox function */
-  unbox = unbox;
 
   /** Control form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** List of GMMs */
-  gmms$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters?.gmm.values)
-  );
+  gmms = computed(() => this.facade.usage()?.response?.parameters.gmm.values);
 
   constructor(private facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
index ca22b23d1..b397645ef 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
@@ -1,26 +1,24 @@
-@if (form$ | async) {
-  <!-- Path Parameters -->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Path Parameters</mat-label>
-    <!-- Event parameters: Distance input -->
-    <div class="settings-subsection--section distance-input">
-      <mat-form-field class="grid-col-12">
-        <mat-label>Distance (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.distance?.max"
-          [min]="(parameters$ | async)?.distance?.min"
-          [ngrxFormControlState]="(controls$ | async)?.distance"
-          step="1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.distance?.min }},
-          {{ (parameters$ | async)?.distance?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+<!-- Path Parameters -->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Path Parameters</mat-label>
+  <!-- Event parameters: Distance input -->
+  <div class="settings-subsection--section distance-input">
+    <mat-form-field class="grid-col-12">
+      <mat-label>Distance (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.distance?.max"
+        [min]="parameters()?.distance?.min"
+        [formControl]="controls.distance"
+        step="1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.distance?.min }},
+        {{ parameters()?.distance?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
index 4608ac623..1ca9f5cfd 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
@@ -1,9 +1,8 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -11,30 +10,33 @@ import {AppFacade} from '../../state/app.facade';
  * Control panel form fields for GMM path parameters.
  */
 @Component({
-  imports: [
-    MatLabel,
-    MatFormField,
-    MatInput,
-    MatError,
-    AsyncPipe,
-    NshmpNgrxFormsModule,
-  ],
+  imports: [MatLabel, MatFormField, MatInput, MatError, ReactiveFormsModule],
   selector: 'app-path-parameters',
   standalone: true,
   styleUrl: './path-parameters.component.scss',
   templateUrl: './path-parameters.component.html',
 })
-export class PathParametersComponent {
+export class PathParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private distanceSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.distanceSubscription = this.controls.distance.valueChanges.subscribe(
+      () => this.facade.resetState()
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.distanceSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
index 82d44ff07..cb9c55121 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
@@ -1,27 +1,26 @@
 <div class="height-full overflow-auto">
-  @if (meanPlot$ | async) {
-    <nshmp-lib-ng-plot-settings
-      [plot]="meanPlot$ | async"
+  @if (meanPlot()) {
+    <nshmp-lib-no-ngrx-plot-settings
+      [plot]="meanPlot()"
+      (updatedPlot)="updatePlot(PlotIds.MEANS, $event)"
       class="mean-settings"
     />
   }
 
-  @if (sigmaPlot$ | async) {
+  @if (sigmaPlot()) {
     <nshmp-lib-ng-plot-settings
-      [plot]="sigmaPlot$ | async"
+      [plot]="sigmaPlot()"
+      (updatedPlot)="updatePlot(PlotIds.SIGMA, $event)"
       class="sigma-settings"
     />
   }
 
   <div class="padding-y-2"></div>
 
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetPlotSettings()"
     [resetDisabled]="
-      (meanPlotSettings$ | async)?.isPristine &&
-      (meanPlotSettings$ | async)?.isUntouched &&
-      (sigmaPlotSettings$ | async)?.isPristine &&
-      (sigmaPlotSettings$ | async)?.isPristine
+      meanPlotSettings().pristine && sigmaPlotSettings().pristine
     "
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
index 483f71ff8..3e6c0e88d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
@@ -1,10 +1,11 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgPlotResetPlotSettingsComponent,
   NshmpLibNgPlotSettingsComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+  NshmpPlot,
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -23,19 +24,27 @@ import {AppFacade} from '../../state/app.facade';
   templateUrl: './plots-settings.component.html',
 })
 export class PlotsSettingsComponent {
+  PlotIds = gmmUtils.PlotType;
+
   /** Mean plot state */
-  meanPlot$ = this.facade.meanPlotState$;
+  meanPlot = this.facade.meanPlotState;
+
   /** Mean plot settings */
-  meanPlotSettings$ = this.facade.meanPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  meanPlotSettings = computed(() => this.facade.meanPlotState().settingsForm);
 
   /** Sigma plot state */
-  sigmaPlot$ = this.facade.sigmaPlotState$;
+  sigmaPlot = this.facade.sigmaPlotState;
+
   /** Sigma plot settings */
-  sigmaPlotSettings$ = this.facade.sigmaPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  sigmaPlotSettings = computed(() => this.facade.sigmaPlotState().settingsForm);
 
   constructor(public facade: AppFacade) {}
+
+  updatePlot(id: string, plot: NshmpPlot): void {
+    const plots = new Map(this.facade.state().plots);
+    plots.set(id, plot);
+    this.facade.updateState({
+      plots,
+    });
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
index a18cbd37e..dac5eeac1 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
@@ -1,4 +1,4 @@
-<nshmp-lib-ng-plots-container>
+<nshmp-lib-no-ngrx-plots-container>
   <mat-accordion multi>
     <!-- GMM vs. magnitude plot -->
     <mat-expansion-panel expanded>
@@ -8,8 +8,8 @@
 
       <mat-divider />
 
-      @if (meanPlotData$ | async; as meanPlot) {
-        <nshmp-lib-ng-plot class="mean-plot" [plot]="meanPlot" />
+      @if (meanPlotData()) {
+        <nshmp-lib-no-ngrx-plot class="mean-plot" [plot]="meanPlotData()" />
       }
     </mat-expansion-panel>
 
@@ -21,8 +21,8 @@
 
       <mat-divider />
 
-      @if (sigmaPlotData$ | async; as sigmaPlot) {
-        <nshmp-lib-ng-plot class="sigma-plot" [plot]="sigmaPlot" />
+      @if (sigmaPlotData()) {
+        <nshmp-lib-no-ngrx-plot class="sigma-plot" [plot]="sigmaPlotData()" />
       }
     </mat-expansion-panel>
 
@@ -43,7 +43,7 @@
       </mat-expansion-panel-header>
 
       <mat-divider />
-      <nshmp-lib-ng-app-metadata [repositories]="repositories$ | async" />
+      <nshmp-lib-no-ngrx-app-metadata [repositories]="repositories()" />
     </mat-expansion-panel>
   </mat-accordion>
-</nshmp-lib-ng-plots-container>
+</nshmp-lib-no-ngrx-plots-container>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts
index 7800906fb..8088793cd 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.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,
@@ -7,12 +7,11 @@ import {
   MatExpansionPanelHeader,
   MatExpansionPanelTitle,
 } from '@angular/material/expansion';
-import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpLibNgPlotComponent,
   NshmpLibNgPlotsContainerComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {AppFacade} from '../../state/app.facade';
 import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.component';
@@ -40,15 +39,13 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
 })
 export class PlotsComponent {
   /** Mean plot data */
-  meanPlotData$ = this.facade.meanPlotState$.pipe(map(plot => plot.plotData));
+  meanPlotData = computed(() => this.facade.meanPlotState().plotData);
 
   /** Sigma plot data */
-  sigmaPlotData$ = this.facade.sigmaPlotState$.pipe(map(plot => plot.plotData));
+  sigmaPlotData = computed(() => this.facade.sigmaPlotState().plotData);
 
   /** Repo metadata */
-  repositories$ = this.facade.usage$.pipe(
-    map(usage => usage?.metadata.repositories)
-  );
+  repositories = computed(() => this.facade.usage()?.metadata.repositories);
 
   constructor(private facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
index 782f79c8b..6389406f3 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
@@ -1,77 +1,75 @@
-@if (form$ | async) {
-  <!-- Site & Basin-->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Site & Basin</mat-label>
+<!-- Site & Basin-->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Site & Basin</mat-label>
 
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Site & Basin: Vs30 input -->
-      <nshmp-lib-ng-gmm-multi-select
-        class="grid-col-12 vs30-input"
-        label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)"
-        [multiple]="vs30Multiple$ | async"
-        [numberControl]="(controls$ | async)?.vs30"
-        [parameter]="(parameters$ | async)?.vs30"
-        placeholder="Select values..."
-        [selectControl]="(controls$ | async)?.vs30Multi"
-        [selectOptions]="vs30CommonValues"
-      />
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Site & Basin: Vs30 input -->
+    <nshmp-lib-no-ngrx-gmm-multi-select
+      class="grid-col-12 vs30-input"
+      label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)"
+      [multiple]="vs30Multiple$ | async"
+      [numberControl]="controls?.vs30"
+      [parameter]="parameters()?.vs30"
+      placeholder="Select values..."
+      [selectControl]="controls.vs30Multi"
+      [selectOptions]="vs30CommonValues"
+    />
 
-      <!-- Site & Basin: Z 1.0 input -->
-      <mat-form-field class="grid-col-4 z1p0-input">
-        <mat-label>Z<sub>1.0</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.z1p0?.max"
-          [min]="(parameters$ | async)?.z1p0?.min"
-          [ngrxFormControlState]="(controls$ | async)?.z1p0"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z1p0?.min }},
-          {{ (parameters$ | async)?.z1p0?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 1.0 input -->
+    <mat-form-field class="grid-col-4 z1p0-input">
+      <mat-label>Z<sub>1.0</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z1p0?.max"
+        [min]="parameters()?.z1p0?.min"
+        [formControl]="controls.z1p0"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.z1p0?.min }},
+        {{ parameters()?.z1p0?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Site & Basin: Z 2.5 input -->
-      <mat-form-field class="grid-col-4 z2p5-input">
-        <mat-label>Z<sub>2.5</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.z2p5?.max"
-          [min]="(parameters$ | async)?.z2p5?.min"
-          [ngrxFormControlState]="(controls$ | async)?.z2p5"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z2p5?.min }},
-          {{ (parameters$ | async)?.z2p5?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 2.5 input -->
+    <mat-form-field class="grid-col-4 z2p5-input">
+      <mat-label>Z<sub>2.5</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z2p5?.max"
+        [min]="parameters()?.z2p5?.min"
+        [formControl]="controls?.z2p5"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.z2p5?.min }},
+        {{ parameters()?.z2p5?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Site & Basin: zSed input -->
-      <mat-form-field class="grid-col-4 zsed-input">
-        <mat-label>Z<sub>SED</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.zSed?.max"
-          [min]="(parameters$ | async)?.zSed?.min"
-          [ngrxFormControlState]="(controls$ | async)?.zSed"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.zSed?.min }},
-          {{ (parameters$ | async)?.zSed?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Site & Basin: zSed input -->
+    <mat-form-field class="grid-col-4 zsed-input">
+      <mat-label>Z<sub>SED</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.zSed?.max"
+        [min]="parameters()?.zSed?.min"
+        [formControl]="controls.zSed"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.zSed?.min }},
+        {{ parameters()?.zSed?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
index f71aeb052..bc789827a 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
@@ -1,13 +1,14 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
 import {
   GmmFormControlIds,
   NshmpLibNgGmmMultiSelectComponent,
   VS30_COMMON_VALUES,
-} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {combineLatest, Subscription} from 'rxjs';
 import {map} from 'rxjs/operators';
 
 import {AppFacade} from '../../state/app.facade';
@@ -23,36 +24,49 @@ import {AppFacade} from '../../state/app.facade';
     MatInput,
     MatError,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-site-parameters',
   standalone: true,
   styleUrl: './site-parameters.component.scss',
   templateUrl: './site-parameters.component.html',
 })
-export class SiteParametersComponent {
+export class SiteParametersComponent implements OnInit, OnDestroy {
   /** Common vs30 values */
   vs30CommonValues = VS30_COMMON_VALUES;
 
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
 
-  /** Whether vs30 is multi-selectable */
-  vs30Multiple$ = this.controls$.pipe(
+  /** Whether Vs30 is multi-selectable */
+  vs30Multiple$ = this.controls.multiSelectableParam.valueChanges.pipe(
     map(
-      controls =>
-        controls.multiSelectableParam.value ===
-        GmmFormControlIds.VS30.toString()
+      multiSelectableParam =>
+        multiSelectableParam.toString() === GmmFormControlIds.VS30.toString()
     )
   );
 
+  private valueSubscription = new Subscription();
+
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    combineLatest([
+      this.controls.vs30Multi.valueChanges,
+      this.controls.vs30.valueChanges,
+      this.controls.z1p0.valueChanges,
+      this.controls.z2p5.valueChanges,
+      this.controls.zSed.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
index d76769447..b391b897e 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
@@ -1,65 +1,63 @@
-@if (form$ | async) {
-  <!-- Source Parameters -->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Source Geometry</mat-label>
+<!-- Source Parameters -->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Source Geometry</mat-label>
 
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Source Parameters: zTor input -->
-      <mat-form-field class="grid-col-4 ztor-input">
-        <mat-label>Z<sub>TOR</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.zTor?.max"
-          [min]="(parameters$ | async)?.zTor?.min"
-          [ngrxFormControlState]="(controls$ | async)?.zTor"
-          step="0.5"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.zTor?.min }},
-          {{ (parameters$ | async)?.zTor?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Source Parameters: zTor input -->
+    <mat-form-field class="grid-col-4 ztor-input">
+      <mat-label>Z<sub>TOR</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.zTor?.max"
+        [min]="parameters()?.zTor?.min"
+        [formControl]="controls.zTor"
+        step="0.5"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.zTor?.min }},
+        {{ parameters()?.zTor?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Source Parameters: Dip input -->
-      <mat-form-field class="grid-col-4 dip-input">
-        <mat-label>Dip (°)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.dip?.max"
-          [min]="(parameters$ | async)?.dip?.min"
-          [ngrxFormControlState]="(controls$ | async)?.dip"
-          step="5"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.dip?.min }},
-          {{ (parameters$ | async)?.dip?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Source Parameters: Dip input -->
+    <mat-form-field class="grid-col-4 dip-input">
+      <mat-label>Dip (°)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.dip?.max"
+        [min]="parameters()?.dip?.min"
+        [formControl]="controls.dip"
+        step="5"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.dip?.min }},
+        {{ parameters()?.dip?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Source Parameters: Width input -->
-      <mat-form-field class="grid-col-4 width-input">
-        <mat-label>Width (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.width?.max"
-          [min]="(parameters$ | async)?.width?.min"
-          [ngrxFormControlState]="(controls$ | async)?.width"
-          step="1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.width?.min }},
-          {{ (parameters$ | async)?.width?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Source Parameters: Width input -->
+    <mat-form-field class="grid-col-4 width-input">
+      <mat-label>Width (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.width?.max"
+        [min]="parameters()?.width?.min"
+        [formControl]="controls.width"
+        step="1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.width?.min }},
+        {{ parameters()?.width?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
index 4a4c59310..8ce5cdead 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
@@ -1,9 +1,9 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {combineLatest, Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -17,24 +17,36 @@ import {AppFacade} from '../../state/app.facade';
     MatInput,
     MatError,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-source-parameters',
   standalone: true,
   styleUrl: './source-parameters.component.scss',
   templateUrl: './source-parameters.component.html',
 })
-export class SourceParametersComponent {
+export class SourceParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private valueSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    combineLatest([
+      this.controls.dip.valueChanges,
+      this.controls.width.valueChanges,
+      this.controls.zTor.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts b/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
index ea75d6a0f..2d1c266e7 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
@@ -1,4 +1,7 @@
-import {GmmFormControlIds, GmmImtFormControls} from '@ghsc/nshmp-lib-ng/gmm';
+import {
+  GmmFormControlIds,
+  GmmImtFormControls,
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 /**
  * Control panel form fields.
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
index 0e09fe9ab..28c3ff154 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
@@ -1,19 +1,33 @@
-import {Injectable} from '@angular/core';
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {computed, Injectable, Signal, signal} from '@angular/core';
+import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {
+  FormGroupControls,
+  NshmpService,
+  ServiceCallInfo,
+  SpinnerService,
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmMagnitudeResponse,
   GmmMagnitudeUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {select, Store} from '@ngrx/store';
-import {FormGroupState} from 'ngrx-forms';
 import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {Observable} from 'rxjs';
+import {
+  redrawPlots,
+  resetPlotSettings,
+} from 'projects/nshmp-apps/src/shared/utils/facade.utils';
+import {catchError} from 'rxjs';
 
 import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {actions} from './app.actions';
-import {magnitudeAppFeature} from './app.reducer';
+import {
+  DEFAULT_FORM_VALUES,
+  defaultPlots,
+  usageFormValues,
+} from '../utils/app.default-values';
+import {serviceResponseToPlotData} from '../utils/response.handler';
+import {AppState, INITIAL_STATE} from './app.state';
 
 /**
  * Entrypoint to NGRX store for GM vs magnitude application.
@@ -27,89 +41,242 @@ export class AppFacade {
   /** GMM service URL */
   serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmMagnitude}`;
 
-  constructor(private store: Store) {}
+  readonly formGroup = this.formBuilder.group({
+    ...DEFAULT_FORM_VALUES,
+    gmmSource: [],
+    MwMulti: [],
+    vs30Multi: [],
+  }) as FormGroupControls<GmmMagnitudeFormControls>;
 
-  /**
-   * Returns the control panel form.
-   */
-  get controlForm$(): Observable<FormGroupState<GmmMagnitudeFormControls>> {
-    return this.store.pipe(select(magnitudeAppFeature.selectControlForm));
+  readonly state = signal<AppState>(INITIAL_STATE);
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private nshmpService: NshmpService,
+    private spinnerService: SpinnerService
+  ) {
+    this.addValidators(this.formGroup);
+    this.formGroup.controls.gmmSource.setValue([]);
+    this.formGroup.controls.showEpistemicUncertainty.disable();
   }
 
   /**
    * Returns the mean plot state.
    */
-  get meanPlotState$(): Observable<NshmpPlot> {
-    return this.store.pipe(select(magnitudeAppFeature.selectMeanPlot));
+  get meanPlotState(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.MEANS));
   }
 
   /**
    * Returns service call info observable.
    */
-  get serviceCallInfo$(): Observable<ServiceCallInfo> {
-    return this.store.pipe(select(magnitudeAppFeature.selectServiceCallInfo));
+  get serviceCallInfo(): Signal<ServiceCallInfo> {
+    return computed(() => this.state().serviceCallInfo);
   }
 
   /**
    * Returns the Gmm distance service responses.
    */
-  get serviceResponse$(): Observable<GmmMagnitudeResponse[]> {
-    return this.store.pipe(select(magnitudeAppFeature.selectServiceResponses));
+  get serviceResponse(): Signal<GmmMagnitudeResponse[]> {
+    return computed(() => this.state().serviceResponses);
   }
 
   /**
    * Returns the mean plot state.
    */
-  get sigmaPlotState$(): Observable<NshmpPlot> {
-    return this.store.pipe(select(magnitudeAppFeature.selectSigmaPlot));
+  get sigmaPlotState(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.SIGMA));
   }
 
   /**
    * Returns supported IMTs observable.
    */
-  get supportedImts$(): Observable<EnumParameterValues[]> {
-    return this.store.pipe(select(magnitudeAppFeature.selectSupportedImts));
+  get supportedImts(): Signal<EnumParameterValues[]> {
+    return computed(() => this.state().supportedImts);
   }
 
   /**
    * Returns the Gmm distance usage response.
    */
-  get usage$(): Observable<GmmMagnitudeUsage> {
-    return this.store.pipe(select(magnitudeAppFeature.selectUsageResponses));
+  get usage(): Signal<GmmMagnitudeUsage> {
+    return computed(() => this.state().usageResponse);
   }
 
   /**
    * Calls the Gmm distance service.
    */
   callService(): void {
-    this.store.dispatch(actions.callServices());
+    if (this.formGroup.invalid) {
+      return;
+    }
+
+    this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
+
+    const urls = gmmUtils.serviceEndpoints(
+      this.serviceUrl,
+      this.formGroup.getRawValue(),
+      this.formGroup.getRawValue().multiSelectableParam
+    );
+
+    this.nshmpService
+      .callServices$<GmmMagnitudeResponse>(urls)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(serviceResponses =>
+        this.handleServiceResponses(serviceResponses)
+      );
+  }
+
+  /**
+   * Create plots based on current state and form group.
+   */
+  createPlots(): void {
+    this.updateState({
+      plots: serviceResponseToPlotData(this.state(), this.formGroup),
+    });
   }
 
   /**
    * Initialize the application.
    */
   init(): void {
-    this.store.dispatch(actions.init());
+    this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
+
+    this.nshmpService
+      .callService$<GmmMagnitudeUsage>(this.serviceUrl)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
   /**
    * Redraw the plot.
    */
   plotRedraw(): void {
-    this.store.dispatch(actions.plotRedraw());
+    this.updateState({
+      plots: redrawPlots(this.state().plots),
+    });
   }
 
   /**
    * Reset the control panel.
    */
   resetControlPanel(): void {
-    this.store.dispatch(actions.resetControlPanel());
+    this.formGroup.reset(
+      usageFormValues(this.state().usageResponse.response.parameters)
+    );
   }
 
   /**
    * Reset the plot settings.
    */
   resetPlotSettings(): void {
-    this.store.dispatch(actions.resetSettings());
+    resetPlotSettings({
+      currentPlots: this.state().plots,
+      defaultPlots: defaultPlots(),
+    });
+  }
+
+  /**
+   * Reset the state.
+   */
+  resetState(): void {
+    const serviceCallInfo = gmmUtils.serviceCallInfo({
+      multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+      serviceName: this.state().serviceCallInfo.serviceName,
+      serviceResponses: this.state().serviceResponses,
+      serviceUrl: this.serviceUrl,
+      values: this.formGroup.getRawValue(),
+    });
+
+    this.updateState({
+      serviceCallInfo,
+      serviceResponses: null,
+    });
+
+    this.createPlots();
+  }
+
+  /**
+   * Update state.
+   *
+   * @param state Partial new state to update
+   */
+  updateState(state: Partial<AppState>): void {
+    this.state.set({
+      ...this.state(),
+      ...state,
+    });
+  }
+
+  /**
+   * Add validators to form controls.
+   *
+   * @param form The form group
+   */
+  private addValidators(
+    form: FormGroupControls<GmmMagnitudeFormControls>
+  ): void {
+    const required = (control: AbstractControl) => Validators.required(control);
+
+    form.controls.Mw.addValidators(required);
+    form.controls.dip.addValidators(required);
+    form.controls.gmmSource.addValidators(required);
+    form.controls.imt.addValidators(required);
+    form.controls.multiSelectableParam.addValidators(required);
+    form.controls.vs30.addValidators(required);
+    form.controls.width.addValidators(required);
+    form.controls.zTor.addValidators(required);
+
+    form.updateValueAndValidity();
+  }
+
+  private handleServiceResponses(
+    serviceResponses: GmmMagnitudeResponse[]
+  ): void {
+    const means = serviceResponses.map(s => s.response.means);
+    const sigmas = serviceResponses.map(s => s.response.sigmas);
+    const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
+
+    if (hasLogicTree) {
+      this.formGroup.controls.showEpistemicUncertainty.enable();
+    } else {
+      this.formGroup.controls.showEpistemicUncertainty.disable();
+    }
+
+    this.updateState({
+      serviceCallInfo: gmmUtils.serviceCallInfo({
+        multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+        serviceName: this.state().serviceCallInfo.serviceName,
+        serviceResponses,
+        serviceUrl: this.serviceUrl,
+        values: this.formGroup.getRawValue(),
+      }),
+      serviceResponses,
+    });
+
+    this.createPlots();
+
+    this.spinnerService.remove();
+  }
+
+  private handleUsageResponse(usageResponse: GmmMagnitudeUsage): void {
+    this.spinnerService.remove();
+    const parameters = usageResponse.response.parameters;
+    const values = usageFormValues(parameters);
+
+    this.formGroup.patchValue({
+      ...values,
+    });
+
+    const serviceCallInfo: ServiceCallInfo = {
+      ...this.state().serviceCallInfo,
+      usage: [this.serviceUrl],
+    };
+
+    this.updateState({
+      serviceCallInfo,
+      usageResponse,
+    });
+
+    this.spinnerService.remove();
   }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
index d76a922d0..2bddf912d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
@@ -1,45 +1,18 @@
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmMagnitudeResponse,
   GmmMagnitudeUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {
-  createFormGroupState,
-  disable,
-  FormGroupState,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {DEFAULT_FORM_VALUES, defaultPlots} from '../utils/app.default-values';
-
-/** Control form id for ngrx-forms */
-export const CONTROL_FORM_ID = '[ngrx-forms] GM vs. Magnitude App Control Form';
 
-const formState = createFormGroupState<GmmMagnitudeFormControls>(
-  CONTROL_FORM_ID,
-  {
-    ...DEFAULT_FORM_VALUES,
-  }
-);
-
-/**
- * Initial state for the control panel
- */
-export const INITIAL_CONTROL_FORM_STATE = updateGroup<GmmMagnitudeFormControls>(
-  {
-    showEpistemicUncertainty: disable,
-  }
-)(formState);
+import {defaultPlots} from '../utils/app.default-values';
 
 /**
  * GMM magnitude app state.
  */
-export interface AppState extends SharedAppState {
-  /** Control panel form field state */
-  controlForm: FormGroupState<GmmMagnitudeFormControls>;
+export interface AppState {
+  plots: Map<string, NshmpPlot>;
   /** Service call info */
   serviceCallInfo: ServiceCallInfo;
   /** GMM service response */
@@ -47,21 +20,20 @@ export interface AppState extends SharedAppState {
   /** Supported IMTs */
   supportedImts: EnumParameterValues[];
   /** GMM usages */
-  usageResponses: GmmMagnitudeUsage;
+  usageResponse: GmmMagnitudeUsage;
 }
 
 /**
  * GMM magnitude app inital state.
  */
 export const INITIAL_STATE: AppState = {
-  controlForm: INITIAL_CONTROL_FORM_STATE,
   plots: defaultPlots(),
   serviceCallInfo: {
     serviceCalls: [],
     serviceName: 'Ground Motion vs. Magnitude',
-    usage: null,
+    usage: [],
   },
   serviceResponses: [],
   supportedImts: [],
-  usageResponses: null,
+  usageResponse: null,
 };
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts b/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
index 88b44121b..fa7be0c71 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
@@ -1,15 +1,14 @@
-import {GmmSource, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpPlot,
   NshmpPlotSettings,
   PlotOptions,
   plotUtils,
-} from '@ghsc/nshmp-lib-ng/plot';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmGroupType,
   GmmMagnitudeUsageParameters,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {box, createFormGroupState} from 'ngrx-forms';
 
 import {
   FormControlIds,
@@ -27,7 +26,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
   dip: null,
   distance: null,
   gmmGroupType: GmmGroupType.ACTIVE_CRUST,
-  gmmSource: box([] as GmmSource[]),
+  gmmSource: [],
   imt: 'default',
   mMax: null,
   mMin: null,
@@ -36,7 +35,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
   showEpistemicUncertainty: false,
   step: null,
   vs30: null,
-  vs30Multi: box([] as number[]),
+  vs30Multi: [],
   width: null,
   z1p0: null,
   z2p5: null,
@@ -49,7 +48,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
  *
  * @param parameters The service parameters
  */
-export function defaultFormValues(
+export function usageFormValues(
   parameters: GmmMagnitudeUsageParameters
 ): GmmMagnitudeFormControls {
   return {
@@ -76,22 +75,12 @@ export const defaultPlots = (): Map<string, NshmpPlot> => {
   plots.set(gmmUtils.PlotType.MEANS, {
     label: 'Response Spectra',
     plotData: meanPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      MEAN_PLOT_SETTING_ID,
-      {
-        ...meanSettingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(meanSettingsForm),
   });
   plots.set(gmmUtils.PlotType.SIGMA, {
     label: 'Standard Deviation',
     plotData: sigmaPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      SIGMA_PLOT_SETTING_ID,
-      {
-        ...sigmaSettingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(sigmaSettingsForm),
   });
   return new Map(plots);
 };
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts b/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
index 22baeb0c9..65712ca71 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
@@ -1,8 +1,11 @@
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {XySequence} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data';
 
+import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
 import {AppState} from '../state/app.state';
+import {defaultPlots} from './app.default-values';
 
 /**
  * Transform Gmm distance service responses to plots.
@@ -10,15 +13,17 @@ import {AppState} from '../state/app.state';
  * @param state The application state
  */
 export function serviceResponseToPlotData(
-  state: AppState
+  state: AppState,
+  form: FormGroupControls<GmmMagnitudeFormControls>
 ): Map<string, NshmpPlot> {
-  if (state.serviceResponses.length === 0) {
-    return state.plots;
+  if (state.serviceResponses === null || state.serviceResponses?.length === 0) {
+    return defaultPlots();
   }
+
   const plots = new Map<string, NshmpPlot>();
   const meanPlot = state.plots.get(gmmUtils.PlotType.MEANS);
   const sigmaPlot = state.plots.get(gmmUtils.PlotType.SIGMA);
-  const formValues = state.controlForm.value;
+  const formValues = form.getRawValue();
 
   const responses: gmmUtils.GmmResponse<XySequence, number[]>[] =
     state.serviceResponses.map(serviceResponse => ({
-- 
GitLab


From c4bce0b27c3100ec6ac1fed18c7edbb715dbdbe2 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Mon, 29 Jul 2024 12:51:24 -0600
Subject: [PATCH 03/10] add providers

---
 .../magnitude/components/content/content.component.spec.ts | 7 ++-----
 .../control-panel/control-panel.component.spec.ts          | 4 ++--
 .../event-parameters/event-parameters.component.spec.ts    | 4 +++-
 .../parameter-summary/parameter-summary.component.spec.ts  | 4 ++--
 .../path-parameters/path-parameters.component.spec.ts      | 5 +++--
 .../plots-settings/plots-settings.component.html           | 2 +-
 .../plots-settings/plots-settings.component.spec.ts        | 4 ++--
 .../gmm/magnitude/components/plots/plots.component.spec.ts | 7 +------
 .../site-parameters/site-parameters.component.spec.ts      | 5 +++--
 .../source-parameters/source-parameters.component.spec.ts  | 5 +++--
 10 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
index 4a22de1da..3b3a0a2f3 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
@@ -1,6 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ContentComponent} from './content.component';
 
@@ -11,10 +11,7 @@ describe('ContentComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ContentComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideNoopAnimations(),
-      ],
+      providers: [provideNoopAnimations(), provideHttpClient()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
index dd4d4d086..524a65d03 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {ControlPanelComponent} from './control-panel.component';
 
@@ -11,7 +11,7 @@ describe('ControlPanelComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ControlPanelComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
index c46328c70..1fbfb9d01 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
@@ -2,6 +2,8 @@ import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideMockStore} from '@ngrx/store/testing';
 
 import {EventParametersComponent} from './event-parameters.component';
+import {provideHttpClient} from '@angular/common/http';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 describe('EventParametersComponent', () => {
   let component: EventParametersComponent;
@@ -10,7 +12,7 @@ describe('EventParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [EventParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
index d8af63c07..b8d5719b6 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
@@ -1,5 +1,5 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ParameterSummaryComponent} from './parameter-summary.component';
 
@@ -10,7 +10,7 @@ describe('ParameterSummaryComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [ParameterSummaryComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient()],
     }).compileComponents();
   });
 
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
index 874eb64c8..7a9ef8898 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {PathParametersComponent} from './path-parameters.component';
 
@@ -10,7 +11,7 @@ describe('PathParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PathParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
index cb9c55121..a7d0427a7 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
@@ -8,7 +8,7 @@
   }
 
   @if (sigmaPlot()) {
-    <nshmp-lib-ng-plot-settings
+    <nshmp-lib-no-ngrx-plot-settings
       [plot]="sigmaPlot()"
       (updatedPlot)="updatePlot(PlotIds.SIGMA, $event)"
       class="sigma-settings"
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
index 2d559563d..f32cb4991 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {PlotsSettingsComponent} from './plots-settings.component';
 
@@ -11,7 +11,7 @@ describe('PlotsSettingsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PlotsSettingsComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
index 611faa48c..12310e664 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
@@ -1,7 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {PlotsComponent} from './plots.component';
 
@@ -12,11 +11,7 @@ describe('PlotsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PlotsComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideHttpClient(),
-        provideNoopAnimations(),
-      ],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
index b7857664b..e6ed2c402 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {SiteParametersComponent} from './site-parameters.component';
 
@@ -10,7 +11,7 @@ describe('SiteParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [SiteParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
index a3e89bc20..a2e4133e6 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {SourceParametersComponent} from './source-parameters.component';
 
@@ -10,7 +11,7 @@ describe('SourceParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [SourceParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
-- 
GitLab


From 9b36be79aca86fd01a241fc3bc1488a142089c19 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Mon, 29 Jul 2024 13:09:07 -0600
Subject: [PATCH 04/10] lint fix

---
 .../event-parameters/event-parameters.component.spec.ts      | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
index 1fbfb9d01..ea17eb0a6 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
@@ -1,9 +1,8 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {EventParametersComponent} from './event-parameters.component';
-import {provideHttpClient} from '@angular/common/http';
-import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 describe('EventParametersComponent', () => {
   let component: EventParametersComponent;
-- 
GitLab


From 3caf8b776686ed258d1f4b416aeb96a5136c1907 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 09:46:02 -0600
Subject: [PATCH 05/10] reset gmm on change

---
 .../components/control-panel/control-panel.component.ts     | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
index 19933c757..b83e99fee 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
@@ -17,6 +17,7 @@ import {
 import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
+import {DEFAULT_FORM_VALUES} from '../../utils/app.default-values';
 import {EventParametersComponent} from '../event-parameters/event-parameters.component';
 import {PathParametersComponent} from '../path-parameters/path-parameters.component';
 import {SiteParametersComponent} from '../site-parameters/site-parameters.component';
@@ -146,15 +147,16 @@ export class ControlPanelComponent implements OnInit, OnDestroy {
     const parameters = this.facade.state().usageResponse.response.parameters;
 
     if (multiSelectableParam === GmmFormControlIds.MW) {
-      this.controls.gmmSource.setValue([]);
       this.controls.Mw.setValue(parameters.Mw.value as number);
       this.controls.MwMulti.setValue([]);
     } else if (multiSelectableParam === GmmFormControlIds.VS30) {
-      this.controls.gmmSource.setValue([]);
       this.controls.vs30.setValue(parameters.vs30.value as number);
       this.controls.vs30Multi.setValue([]);
     }
 
+    this.controls.gmmSource.setValue([]);
+    this.controls.gmmSource.markAsPristine();
+    this.controls.imt.setValue(DEFAULT_FORM_VALUES.imt);
     this.facade.resetState();
   }
 
-- 
GitLab


From 82f16679009bb6da4f93b5f0655ed752d5d4c256 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 10:05:54 -0600
Subject: [PATCH 06/10] test

---
 .gitlab-ci.yml | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2b4c52c35..0fb2c716a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,7 @@ variables:
   BASE_HREF: nshmp
   GITLAB_TOKEN: '${CI_JOB_TOKEN}'
   IMAGE_NAME: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${ENVIRONMENT}-${CI_COMMIT_SHORT_SHA}
-  NODE_IMAGE: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG}--node
+  # NODE_IMAGE: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG}--node
   UPSTREAM_PATH: ghsc/nshmp/nshmp-apps
 
 image: ${CI_REGISTRY}/devops/images/usgs/node:20
@@ -150,25 +150,25 @@ Init:
     - npm ci
   stage: init
 
-Build Node Image:
-  image: ${CI_REGISTRY}/devops/images/docker:20
-  script:
-    - |
-      docker build \
-        --build-arg FROM_IMAGE=${CI_REGISTRY}/devops/images/usgs/node:20 \
-        --file ".gitlab/Dockerfile" \
-        --pull \
-        --tag ${NODE_IMAGE} \
-        .
-    - docker push ${NODE_IMAGE}
-  services:
-    - alias: docker
-      name: ${CI_REGISTRY}/devops/images/docker:20-dind
-  stage: init
-  tags:
-    - build
-  variables:
-    DOCKER_DRIVER: overlay2
+# Build Node Image:
+#   image: ${CI_REGISTRY}/devops/images/docker:20
+#   script:
+#     - |
+#       docker build \
+#         --build-arg FROM_IMAGE=${CI_REGISTRY}/devops/images/usgs/node:20 \
+#         --file ".gitlab/Dockerfile" \
+#         --pull \
+#         --tag ${NODE_IMAGE} \
+#         .
+#     - docker push ${NODE_IMAGE}
+#   services:
+#     - alias: docker
+#       name: ${CI_REGISTRY}/devops/images/docker:20-dind
+#   stage: init
+#   tags:
+#     - build
+#   variables:
+#     DOCKER_DRIVER: overlay2
 
 ####
 # Stage: Build
@@ -179,8 +179,8 @@ Build Project:
     paths:
       - dist
   before_script:
-    - git config --global --add safe.directory '*'
-  image: ${NODE_IMAGE}
+    # - git config --global --add safe.directory '*'
+  # image: ${NODE_IMAGE}
   needs:
     - Init
     - Build Node Image
-- 
GitLab


From d48be5da7e819c66eb14a9c35f9c23c91ce73687 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 10:06:10 -0600
Subject: [PATCH 07/10] test

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0fb2c716a..f4c8cc740 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -178,12 +178,12 @@ Build Project:
   artifacts:
     paths:
       - dist
-  before_script:
+  # before_script:
     # - git config --global --add safe.directory '*'
   # image: ${NODE_IMAGE}
   needs:
     - Init
-    - Build Node Image
+    # - Build Node Image
   script:
     - npm run build:prod
   stage: build
-- 
GitLab


From 72ea56e4a331b6f71a73c571f1d9d191f45c06e8 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 10:13:12 -0600
Subject: [PATCH 08/10] remove image

---
 .gitlab-ci.yml     | 25 -------------------------
 .gitlab/Dockerfile |  9 ---------
 2 files changed, 34 deletions(-)
 delete mode 100644 .gitlab/Dockerfile

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f4c8cc740..8672e48c8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,6 @@ variables:
   BASE_HREF: nshmp
   GITLAB_TOKEN: '${CI_JOB_TOKEN}'
   IMAGE_NAME: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${ENVIRONMENT}-${CI_COMMIT_SHORT_SHA}
-  # NODE_IMAGE: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG}--node
   UPSTREAM_PATH: ghsc/nshmp/nshmp-apps
 
 image: ${CI_REGISTRY}/devops/images/usgs/node:20
@@ -150,26 +149,6 @@ Init:
     - npm ci
   stage: init
 
-# Build Node Image:
-#   image: ${CI_REGISTRY}/devops/images/docker:20
-#   script:
-#     - |
-#       docker build \
-#         --build-arg FROM_IMAGE=${CI_REGISTRY}/devops/images/usgs/node:20 \
-#         --file ".gitlab/Dockerfile" \
-#         --pull \
-#         --tag ${NODE_IMAGE} \
-#         .
-#     - docker push ${NODE_IMAGE}
-#   services:
-#     - alias: docker
-#       name: ${CI_REGISTRY}/devops/images/docker:20-dind
-#   stage: init
-#   tags:
-#     - build
-#   variables:
-#     DOCKER_DRIVER: overlay2
-
 ####
 # Stage: Build
 ####
@@ -178,12 +157,8 @@ Build Project:
   artifacts:
     paths:
       - dist
-  # before_script:
-    # - git config --global --add safe.directory '*'
-  # image: ${NODE_IMAGE}
   needs:
     - Init
-    # - Build Node Image
   script:
     - npm run build:prod
   stage: build
diff --git a/.gitlab/Dockerfile b/.gitlab/Dockerfile
deleted file mode 100644
index c5a2d0ec8..000000000
--- a/.gitlab/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-ARG FROM_IMAGE=code.usgs.gov:5001/devops/images/usgs/node:20
-
-FROM ${FROM_IMAGE}
-
-USER root
-
-RUN apt-get install -y git
-
-USER usgs-user
-- 
GitLab


From a311941c865803f745c85cfa9aa9b2903dfc1fbb Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 11:30:19 -0600
Subject: [PATCH 09/10] switch image

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8672e48c8..ecc444dae 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -74,10 +74,10 @@ default:
 # Docker in Docker
 ##
 .dind:
-  image: ${CI_REGISTRY}/devops/images/docker:20
+  image: ${CI_REGISTRY}/devops/images/usgs/docker:20
   services:
     - alias: docker
-      name: ${CI_REGISTRY}/devops/images/docker:20-dind
+      name: ${CI_REGISTRY}/devops/images/usgs/docker:20-dind
   variables:
     DOCKER_DRIVER: overlay2
 
-- 
GitLab


From 87951f59042e319b26ac71b7cce5946740f33bfc Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Tue, 30 Jul 2024 11:36:34 -0600
Subject: [PATCH 10/10] install git

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ecc444dae..eec4be44e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -160,6 +160,7 @@ Build Project:
   needs:
     - Init
   script:
+    - apt-get install -y git
     - npm run build:prod
   stage: build
 
-- 
GitLab