diff --git a/package-lock.json b/package-lock.json
index 191d4b031da3959065982344f2f3a8fb139edd58..a0fff130c79012874b23e6edc912cdf52b49da48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
         "@compodoc/compodoc": "^1.1.25",
         "@ghsc/disagg-d3": "^0.9.0",
         "@ghsc/nshmp-lib-ng": "^18.1.3",
+        "@ghsc/nshmp-lib-no-ngrx": "^18.2.0",
         "@ghsc/nshmp-template": "^18.0.3",
         "@ghsc/nshmp-utils-ts": "^3.5.1",
         "@ngrx/effects": "^18.0.0",
@@ -3966,6 +3967,18 @@
         "@angular/core": "^18.0.2"
       }
     },
+    "node_modules/@ghsc/nshmp-lib-no-ngrx": {
+      "version": "18.2.0",
+      "resolved": "https://code.usgs.gov/api/v4/projects/12416/packages/npm/@ghsc/nshmp-lib-no-ngrx/-/@ghsc/nshmp-lib-no-ngrx-18.2.0.tgz",
+      "integrity": "sha1-aJb/VPsi2fU3fsJn0T1zicsyY1c=",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": "^18.0.2",
+        "@angular/core": "^18.0.2"
+      }
+    },
     "node_modules/@ghsc/nshmp-template": {
       "version": "18.0.3",
       "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-18.0.3.tgz",
diff --git a/package.json b/package.json
index 970fab9170f22170830e85958b2cbb4c6dcbcb7f..c5c80e4ad21adc1a599da3b16952fdfa6d20ec64 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
     "@compodoc/compodoc": "^1.1.25",
     "@ghsc/disagg-d3": "^0.9.0",
     "@ghsc/nshmp-lib-ng": "^18.1.3",
+    "@ghsc/nshmp-lib-no-ngrx": "^18.2.0",
     "@ghsc/nshmp-template": "^18.0.3",
     "@ghsc/nshmp-utils-ts": "^3.5.1",
     "@ngrx/effects": "^18.0.0",
diff --git a/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts b/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts
index 55592eb37e4b6a94923c322a4f7270eabc8b5d68..2535e4b485a8ab2fc24ade03ba70dde0efa02051 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts
@@ -1,9 +1,4 @@
 import {Routes} from '@angular/router';
-import {provideEffects} from '@ngrx/effects';
-import {provideState} from '@ngrx/store';
-
-import {HangingWallEffectsAppEffects} from './hanging-wall-effects/state/app.effects';
-import {hangingWallEffectsAppFeature} from './hanging-wall-effects/state/app.reducer';
 
 /** Routes for dev GMM applications */
 const routes: Routes = [
@@ -13,10 +8,6 @@ const routes: Routes = [
         com => com.AppComponent
       ),
     path: 'hanging-wall-effects',
-    providers: [
-      provideState(hangingWallEffectsAppFeature),
-      provideEffects(HangingWallEffectsAppEffects),
-    ],
   },
 ];
 
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html
index 5bde83f8e8c36af493e24c8044ed0abbb9fb99f5..f9bffe28365fcdd73308f52bbb7fab74421c48c9 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html
@@ -1,4 +1,4 @@
-<nshmp-lib-ng-template [navigationList]="navigationList" [title]="title">
+<nshmp-lib-no-ngrx-template [navigationList]="navigationList" [title]="title">
   <nshmp-template-content-container>
     <!-- Control panel -->
     <nshmp-template-control-panel>
@@ -17,7 +17,7 @@
   </nshmp-template-content-container>
 
   <!-- About page -->
-  <nshmp-lib-ng-about-page>
+  <nshmp-lib-no-ngrx-about-page>
     <app-about />
-  </nshmp-lib-ng-about-page>
-</nshmp-lib-ng-template>
+  </nshmp-lib-no-ngrx-about-page>
+</nshmp-lib-no-ngrx-template>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts
index 55df0c74739b789e76aa7313c3de3ec6a6fccf24..464dcbb4a76acd8047df2ebeb266fb6227e49255 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts
@@ -1,6 +1,6 @@
 import {Component, OnInit} from '@angular/core';
-import {NshmpLibNgAboutPageComponent} from '@ghsc/nshmp-lib-ng/about';
-import {NshmpLibNgTemplateComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {NshmpLibNgAboutPageComponent} from '@ghsc/nshmp-lib-no-ngrx/about';
+import {NshmpLibNgTemplateComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpTemplateContentContainerComponent,
   NshmpTemplateControlPanelComponent,
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html
index 24cd5fc763344f824880aa97d2a3c97872750478..3fad4144d7854c4df02e4c5fd74c9f03db3d4b79 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/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]="table$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="table()"
         filename="hanging-wall-effects.csv"
         buttonText="Export to CSV"
       />
-      <nshmp-lib-ng-data-table [table]="table$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="table()" />
     </ng-template>
   </mat-tab>
 </mat-tab-group>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts
index 3c7ea105abe149d229ee4159046bfe9eae280029..76112a27aad690bbd895c65199bdef3f8c94ae89 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts
@@ -1,6 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} 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(async () => {
     await TestBed.configureTestingModule({
       imports: [ContentComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideNoopAnimations(),
-      ],
+      providers: [provideNoopAnimations(), provideHttpClient()],
     }).compileComponents();
 
     fixture = TestBed.createComponent(ContentComponent);
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts
index 6178e6285ca3a0f12ebc78dd2d62d1df9aa54e94..8d7f1307434cdf356dff7a8ba441aedac4ab0aaf 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/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';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {AppFacade} from '../../state/app.facade';
 import {Plots} from '../../state/app.state';
@@ -34,18 +33,14 @@ export class ContentComponent {
   exp = true;
 
   /** Whether service has been called and data exists */
-  hasData$ = this.facade.serviceResponses$.pipe(
-    map(responses => responses?.length > 0)
-  );
+  hasData = computed(() => this.facade.state().serviceResponses?.length > 0);
 
   /** Table data for table */
-  table$ = this.facade.plots$.pipe(
-    map(plots =>
-      gmmUtils.plotToTable(
-        plots.get(Plots.GROUND_MOTION).plotData,
-        this.exp,
-        this.commonXValues
-      )
+  table = computed(() =>
+    gmmUtils.plotToTable(
+      this.facade.state().plots.get(Plots.GROUND_MOTION).plotData,
+      this.exp,
+      this.commonXValues
     )
   );
 
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html
index ec65e81599d89b08d483c2ced2af47c4e22e6712..692ceb089a66950fdee4d0475e591f041ce0500c 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html
@@ -1,155 +1,150 @@
 <!-- Hanging wall effects control panel -->
-@if (formState$ | async; as formState) {
-  <form
-    class="settings-section control-panel height-full overflow-auto"
-    [ngrxFormState]="formState"
-    (submit)="onSubmit()"
-  >
-    <mat-form-field
-      class="grid-col-12 multi-select-parameter-menu padding-top-1"
-    >
-      <mat-label>Multi-Selectable Parameter</mat-label>
-      <mat-select
-        [ngrxFormControlState]="formState?.controls?.multiSelectableParam"
-      >
-        <mat-option value="gmm">Ground Motion Models</mat-option>
-        <mat-option value="Mw">M<sub>W</sub></mat-option>
-        <mat-option value="vs30">V<sub>S30</sub></mat-option>
-      </mat-select>
-    </mat-form-field>
+<form
+  class="settings-section control-panel height-full overflow-auto"
+  [formGroup]="formState"
+  (submit)="onSubmit()"
+>
+  <mat-form-field class="grid-col-12 multi-select-parameter-menu padding-top-1">
+    <mat-label>Multi-Selectable Parameter</mat-label>
+    <mat-select [formControl]="formState.controls.multiSelectableParam">
+      <mat-option value="gmm">Ground Motion Models</mat-option>
+      <mat-option value="Mw">M<sub>W</sub></mat-option>
+      <mat-option value="vs30">V<sub>S30</sub></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]="formState?.controls?.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 [formControl]="formState.controls.imt">
+      @if ((gmmSelected$ | async) === false) {
+        <mat-option selected="true" [value]="imtPlaceHolder.value">
+          {{ imtPlaceHolder.display }}
+        </mat-option>
+      }
+      @for (imt of supportedImts(); track imt) {
+        <mat-option [value]="imt.value">
+          {{ imt.display }}
+        </mat-option>
+      }
+    </mat-select>
+  </mat-form-field>
 
-    <!-- Event parameters -->
-    <div class="settings-subsection event-parameters">
-      <mat-label class="settings-subsection--label">Event Parameters</mat-label>
-      <!-- Event parameters: Mw input -->
-      <div class="settings-subsection--section">
-        <nshmp-lib-ng-gmm-multi-select
-          class="grid-col-6 mw-input"
-          label="Mw"
-          [multiple]="MwMultiple$ | async"
-          [numberControl]="formState?.controls?.Mw"
-          [parameter]="(parameters$ | async)?.Mw"
-          placeholder="Select Magnitudes"
-          [selectControl]="formState?.controls?.MwMulti"
-          [selectOptions]="MwCommonValues"
-        />
-      </div>
+  <!-- Event parameters -->
+  <div class="settings-subsection event-parameters">
+    <mat-label class="settings-subsection--label">Event Parameters</mat-label>
+    <!-- Event parameters: Mw input -->
+    <div class="settings-subsection--section">
+      <nshmp-lib-no-ngrx-gmm-multi-select
+        class="grid-col-6 mw-input"
+        label="Mw"
+        [multiple]="MwMultiple$ | async"
+        [numberControl]="formState.controls.Mw"
+        [parameter]="parameters()?.Mw"
+        placeholder="Select Magnitudes"
+        [selectControl]="formState.controls.MwMulti"
+        [selectOptions]="MwCommonValues"
+      />
     </div>
+  </div>
 
-    <!-- Site & Basin-->
-    <div class="settings-subsection site-parameters">
-      <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]="formState?.controls?.vs30"
-          [parameter]="(parameters$ | async)?.vs30"
-          placeholder="Select values..."
-          [selectControl]="formState?.controls?.vs30Multi"
-          [selectOptions]="vs30CommonValues"
-        />
+  <!-- Site & Basin-->
+  <div class="settings-subsection site-parameters">
+    <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-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]="formState.controls.vs30"
+        [parameter]="parameters()?.vs30"
+        placeholder="Select values..."
+        [selectControl]="formState.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]="formState?.controls?.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]="formState.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]="formState?.controls?.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]="formState.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]="formState?.controls?.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]="formState.controls.zSed"
+          step="1.0"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.zSed?.min }},
+          {{ parameters()?.zSed?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
     </div>
+  </div>
 
-    <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]="formState?.isInvalid"
-      [serviceCallInfo]="serviceCallInfo$ | async"
-      [resetDisabled]="formState?.isPristine"
-      (resetClick)="facade.resetControlPanel()"
-    />
-  </form>
-}
+  <nshmp-lib-no-ngrx-control-panel-buttons
+    [plotDisabled]="formState.invalid"
+    [serviceCallInfo]="serviceCallInfo()"
+    [resetDisabled]="formState.pristine"
+    (resetClick)="facade.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts
index 17b38b87c795f14adb6a7164459f488b9a119b06..e5be2cede3fcac4c81c4fcb544275e7e13f1c974 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} 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(async () => {
     await TestBed.configureTestingModule({
       imports: [ControlPanelComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
     }).compileComponents();
 
     fixture = TestBed.createComponent(ControlPanelComponent);
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts
index c6ed1d5e3c47c9ba2101705cc9ebe88ee8b09fa0..5c814a7cbfdd14e42d580b8025710dd03f11ebc6 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts
@@ -1,23 +1,24 @@
 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 {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
 import {MatSelect} from '@angular/material/select';
 import {
   GmmFormControlIds,
+  gmmUtils,
   MW_COMMON_VALUES,
   NshmpLibNgGmmMenuComponent,
   NshmpLibNgGmmMultiSelectComponent,
   NshmpLibNgGmmPlotOptionsControlPanelComponent,
   VS30_COMMON_VALUES,
-} 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';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {combineLatest, Subscription} from 'rxjs';
 import {map} from 'rxjs/operators';
 
 import {AppFacade} from '../../state/app.facade';
@@ -38,63 +39,111 @@ import {AppFacade} from '../../state/app.facade';
     NshmpLibNgGmmMultiSelectComponent,
     NshmpLibNgGmmPlotOptionsControlPanelComponent,
     NshmpLibNgControlPanelButtonsComponent,
-    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 {
+  imtPlaceHolder = gmmUtils.imtPlaceHolder();
+
   /** List of common magnitudes */
   MwCommonValues = MW_COMMON_VALUES;
+
   /** List of common vs30 values */
   vs30CommonValues = VS30_COMMON_VALUES;
 
   /** Form controls state */
-  controls$ = this.facade.controlPanelForm$.pipe(map(form => form.controls));
+  controls = this.facade.controlPanelForm.controls;
 
   /** Control panel form field state */
-  formState$ = this.facade.controlPanelForm$;
+  formState = this.facade.controlPanelForm;
 
   /** Whether a GMM has been selected */
-  gmmSelected$ = this.controls$.pipe(
-    map(controls => unbox(controls.gmmSource.value).length > 0)
+  gmmSelected$ = this.controls.gmmSource.valueChanges.pipe(
+    map(gmmSource => gmmSource?.length > 0)
   );
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
+  parameters = computed(
+    () => this.facade.state().usageResponse?.response?.parameters
   );
 
   /** Service call info state */
-  serviceCallInfo$ = this.facade.serviceCallInfo$;
+  serviceCallInfo = computed(() => this.facade.state().serviceCallInfo);
 
   /** Supported IMTs based on GMMs selected */
-  supportedImts$ = this.facade.supportedImts$;
+  supportedImts = computed(() => this.facade.state().supportedImts);
 
   /** Whether magnitudes are multi-selectable */
-  MwMultiple$ = this.controls$.pipe(
+  MwMultiple$ = this.controls.multiSelectableParam.valueChanges.pipe(
     map(
-      controls =>
-        controls.multiSelectableParam.value === GmmFormControlIds.MW.toString()
+      multiSelectableParam =>
+        multiSelectableParam.toString() === GmmFormControlIds.MW.toString()
     )
   );
 
   /** Whether Vs30 is multi-selectable */
-  vs30Multiple$ = this.controls$.pipe(
+  vs30Multiple$ = this.controls.multiSelectableParam.valueChanges.pipe(
     map(
-      controls =>
-        controls.multiSelectableParam.value ===
-        GmmFormControlIds.VS30.toString()
+      multiSelectableParam =>
+        multiSelectableParam.toString() === GmmFormControlIds.VS30.toString()
     )
   );
 
+  subs: Subscription[] = [];
+
   constructor(
     public facade: AppFacade,
     private nshmpService: NshmpService
   ) {}
 
+  ngOnInit(): void {
+    this.subs.push(
+      this.formState.controls.gmmSource.valueChanges.subscribe(() => {
+        this.onGmmSource();
+      })
+    );
+
+    this.subs.push(
+      this.formState.controls.multiSelectableParam.valueChanges.subscribe(() =>
+        this.onMultiSelectableParam()
+      )
+    );
+
+    this.subs.push(
+      this.formState.controls.showEpistemicUncertainty.valueChanges.subscribe(
+        () => this.onShowEpistemicUncertainty()
+      )
+    );
+
+    this.subs.push(
+      combineLatest([
+        this.formState.controls.dip.valueChanges,
+        this.formState.controls.width.valueChanges,
+      ]).subscribe(() => this.updatePlots())
+    );
+
+    this.subs.push(
+      combineLatest([
+        this.formState.controls.imt.valueChanges,
+        this.formState.controls.Mw.valueChanges,
+        this.formState.controls.MwMulti.valueChanges,
+        this.formState.controls.vs30Multi.valueChanges,
+        this.formState.controls.vs30.valueChanges,
+        this.formState.controls.z1p0.valueChanges,
+        this.formState.controls.z2p5.valueChanges,
+        this.formState.controls.zSed.valueChanges,
+      ]).subscribe(() => this.facade.resetState())
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub?.unsubscribe());
+  }
+
   /**
    * On form submit.
    */
@@ -102,4 +151,62 @@ export class ControlPanelComponent {
     this.facade.callServices();
     this.nshmpService.selectPlotControl();
   }
+
+  private onGmmSource() {
+    const formValues = this.formState.getRawValue();
+
+    if (
+      !this.facade.state().usageResponse ||
+      formValues.gmmSource.length === 0
+    ) {
+      this.formState.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.formState.controls.imt.patchValue(imt?.value);
+    this.facade.resetState();
+  }
+
+  private onMultiSelectableParam(): void {
+    if (!this.facade.state().usageResponse) {
+      return;
+    }
+
+    const {multiSelectableParam} = this.formState.getRawValue();
+    const parameters = this.facade.state().usageResponse.response.parameters;
+
+    if (multiSelectableParam === GmmFormControlIds.MW) {
+      this.formState.controls.gmmSource.setValue([]);
+      this.formState.controls.Mw.setValue(parameters.Mw.value as number);
+      this.formState.controls.MwMulti.setValue([]);
+    } else if (multiSelectableParam === GmmFormControlIds.VS30) {
+      this.formState.controls.gmmSource.setValue([]);
+      this.formState.controls.vs30.setValue(parameters.vs30.value as number);
+      this.formState.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/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html
index 57fcd8745ebc9a8e0118f02fbe06467553747c8c..2bad50664e43ca51084bd8612cb467f9ea6ca073 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html
@@ -1,40 +1,38 @@
 <mat-label>{{ label }}</mat-label>
 
-@if (formControlState) {
-  <div class="grid-row grid-gap-lg">
-    <div class="grid-col-7">
-      <mat-slider
-        discrete
-        [min]="parameter?.min"
-        [max]="parameter?.max"
-        matTooltip="Slide to change fault"
-        (change)="faultControlChange.emit()"
-      >
-        <input matSliderThumb [ngrxFormControlState]="formControlState" />
-      </mat-slider>
-    </div>
+<div class="grid-row grid-gap-lg">
+  <div class="grid-col-7">
+    <mat-slider
+      discrete
+      [min]="parameter?.min"
+      [max]="parameter?.max"
+      matTooltip="Slide to change fault"
+      (change)="facade.callServices()"
+    >
+      <input matSliderThumb [formControl]="control" />
+    </mat-slider>
+  </div>
 
-    <mat-form-field class="grid-col-5">
-      <mat-label>{{ label }}</mat-label>
-      <input
-        matInput
-        [ngrxFormControlState]="formControlState"
-        type="number"
-        [min]="parameter?.min"
-        [max]="parameter?.max"
-        (input)="faultControlChange.emit()"
-      />
+  <mat-form-field class="grid-col-5">
+    <mat-label>{{ label }}</mat-label>
+    <input
+      matInput
+      [formControl]="control"
+      type="number"
+      [min]="parameter?.min"
+      [max]="parameter?.max"
+      (input)="facade.callServices()"
+    />
 
-      @if (textSuffix) {
-        <span matTextSuffix>{{ textSuffix }}</span>
-      }
+    @if (textSuffix) {
+      <span matTextSuffix>{{ textSuffix }}</span>
+    }
 
-      <mat-error>
-        [
-        {{ parameter?.min }},
-        {{ parameter?.max }}
-        ]
-      </mat-error>
-    </mat-form-field>
-  </div>
-}
+    <mat-error>
+      [
+      {{ parameter?.min }},
+      {{ parameter?.max }}
+      ]
+    </mat-error>
+  </mat-form-field>
+</div>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts
index ddb7e896113b6f064aebf10aeac4c57adced8b6d..1446776dc2db11d770a0637e94641b3aca5ac2d4 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts
@@ -1,5 +1,7 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {FormControl} from '@angular/forms';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {FaultControlComponent} from './fault-control.component';
 
@@ -10,11 +12,14 @@ describe('FaultControlComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [FaultControlComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
     }).compileComponents();
 
     fixture = TestBed.createComponent(FaultControlComponent);
     component = fixture.componentInstance;
+    component.control = new FormControl(0, {
+      nonNullable: true,
+    });
     fixture.detectChanges();
   });
 
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts
index 442fa61f9ba8cf4a3ac32b4f0cceae5f32d2d524..c89c55fa076804a810168656256d4a5b65842f6a 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts
@@ -1,4 +1,5 @@
-import {Component, EventEmitter, Input, Output} from '@angular/core';
+import {Component, Input} from '@angular/core';
+import {FormControl, ReactiveFormsModule} from '@angular/forms';
 import {
   MatError,
   MatFormField,
@@ -8,9 +9,9 @@ import {
 import {MatInput} from '@angular/material/input';
 import {MatSlider, MatSliderThumb} from '@angular/material/slider';
 import {MatTooltip} from '@angular/material/tooltip';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
 import {GmmUsageParameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {FormControlState} from 'ngrx-forms';
+
+import {AppFacade} from '../../state/app.facade';
 
 /**
  * Fault control with slider and input box.
@@ -25,7 +26,7 @@ import {FormControlState} from 'ngrx-forms';
     MatInput,
     MatSuffix,
     MatError,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-fault-control',
   standalone: true,
@@ -33,23 +34,21 @@ import {FormControlState} from 'ngrx-forms';
   templateUrl: './fault-control.component.html',
 })
 export class FaultControlComponent {
-  /** Event emiiter when the slider or input value changes */
-  @Output()
-  faultControlChange = new EventEmitter();
-
-  /** NGRX form control state */
-  @Input()
-  formControlState: FormControlState<number>;
+  /** Form control state */
+  @Input({required: true})
+  control: FormControl<number>;
 
   /** Label */
   @Input()
   label: string;
 
   /** GMM usage parameter with min and max values */
-  @Input()
+  @Input({required: true})
   parameter: GmmUsageParameter;
 
   /** Input text suffix */
   @Input()
   textSuffix: string;
+
+  constructor(public facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html
index 721416a257b65769b7b0bf16d694b7a5b9ec4242..7478650f282308cce72a66a5436821122a657d90 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html
@@ -1,104 +1,102 @@
-@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>:
+<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 form?.value?.gmmSource; track gmmSource) {
+        <mat-list-item class="indent-list font-small">
+          {{ gmmSource.gmm.display }}
         </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>
+      }
+    </mat-list>
+  </div>
 
-    <div class="grid-col-12 tablet-lg:grid-col-3">
-      <mat-list class="parameter-list">
+  <div class="grid-col-12 tablet-lg:grid-col-3">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">IMT</span>:
+        {{ imtIdToDisplay(form?.value?.imt) }}
+      </mat-list-item>
+
+      @if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
         <mat-list-item>
-          <span class="parameter">IMT</span>:
-          {{ imtIdToDisplay(form?.value?.imt) }}
+          <span class="parameter">Magnitude</span>:
         </mat-list-item>
 
-        @if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
-          <mat-list-item>
-            <span class="parameter">Magnitude</span>:
-          </mat-list-item>
-
-          @for (Mw of unbox(form?.value?.MwMulti); track Mw) {
-            <mat-list-item class="indent-list">
-              {{ Mw }}
-            </mat-list-item>
-          }
-        } @else {
-          <mat-list-item>
-            <span class="parameter">Magnitude</span>:
-            {{ form?.value?.Mw }}
+        @for (Mw of form?.value?.MwMulti; track Mw) {
+          <mat-list-item class="indent-list">
+            {{ Mw }}
           </mat-list-item>
         }
-
+      } @else {
         <mat-list-item>
-          <span class="parameter"> Z<sub>TOR</sub> </span>:
-          {{ form?.value?.zTor }} km
+          <span class="parameter">Magnitude</span>:
+          {{ form?.value?.Mw }}
         </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-3">
-      <mat-list class="parameter-list">
-        @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-          <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Width</span>: {{ form?.value?.width }} km
+      </mat-list-item>
+    </mat-list>
+  </div>
 
-          @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 }}
+  <div class="grid-col-12 tablet-lg:grid-col-3">
+    <mat-list class="parameter-list">
+      @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-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="parameters">Epistemic Uncertainty</span>:
-          {{ form?.value?.showEpistemicUncertainty ? 'On' : 'Off' }}
-        </mat-list-item>
-      </mat-list>
-    </div>
+      <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-item>
+        <span class="parameters">Epistemic Uncertainty</span>:
+        {{ form?.value?.showEpistemicUncertainty ? 'On' : 'Off' }}
+      </mat-list-item>
+    </mat-list>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts
index 10bfea4db08555fa96249b81d15dd82cba66a7b4..32fac014cc17324c50894465d7c80a661cc18395 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/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();
 
     fixture = TestBed.createComponent(ParameterSummaryComponent);
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts
index 218ec9395eacf70e30a456f05ca8e5e706153feb..1621d574cd49d42c091bf8dcb1d883e93b947cfd 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts
@@ -1,9 +1,7 @@
 import {AsyncPipe} from '@angular/common';
 import {Component} 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,9 @@ 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.controlPanelForm$;
-
-  /** List of GMMs */
-  gmms$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters?.gmm.values)
-  );
+  form = this.facade.controlPanelForm;
 
   constructor(private facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html
index 38395ac8064f04fadef85f6d11ba7fcedd9b7524..a59c17095885b8f2ae5911def6788decdcd3eff1 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html
@@ -5,16 +5,16 @@
         <mat-expansion-panel-header>
           <mat-panel-title>{{ panel.title }}</mat-panel-title>
         </mat-expansion-panel-header>
-        <nshmp-lib-ng-plot-settings [plot]="panel.plot$ | async" />
+        <nshmp-lib-no-ngrx-plot-settings
+          [plot]="panel.plot()"
+          (updatedPlot)="updatePlot($event)"
+        />
       </mat-expansion-panel>
     }
   </mat-accordion>
 
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetPlotSettings()"
-    [resetDisabled]="
-      (groundMotionPlotData$ | async)?.settingsForm?.isPristine &&
-      (groundMotionPlotData$ | async)?.settingsForm?.isUntouched
-    "
+    [resetDisabled]="resetDisabled"
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts
index dc4ce1a2437efbfb89663a635f73e4402fa0b5f7..7e8e9ecfcb3122bb29ad6c109ae5e92d3b8c72c0 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts
@@ -1,7 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {PlotSettingsComponent} from './plot-settings.component';
 
@@ -12,11 +11,7 @@ describe('PlotSettingsComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [PlotSettingsComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideHttpClient(),
-        provideNoopAnimations(),
-      ],
+      providers: [provideHttpClient(), provideNoopAnimations()],
     }).compileComponents();
 
     fixture = TestBed.createComponent(PlotSettingsComponent);
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts
index 261f8b8ce8f8a72ca04d0a7490bd4dd1e7fd6969..8eccf40700b5fce6285dd6acce7f86d5e1c87412 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts
@@ -1,5 +1,4 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy} from '@angular/core';
 import {
   MatAccordion,
   MatExpansionPanel,
@@ -9,9 +8,10 @@ import {
 import {
   NshmpLibNgPlotResetPlotSettingsComponent,
   NshmpLibNgPlotSettingsComponent,
+  NshmpPlot,
   PlotSettingsPanel,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 import {Plots} from '../../state/app.state';
@@ -24,25 +24,45 @@ import {Plots} from '../../state/app.state';
     MatExpansionPanel,
     MatExpansionPanelHeader,
     MatExpansionPanelTitle,
-    AsyncPipe,
   ],
   selector: 'app-plot-settings',
   standalone: true,
   styleUrl: './plot-settings.component.scss',
   templateUrl: './plot-settings.component.html',
 })
-export class PlotSettingsComponent {
+export class PlotSettingsComponent implements OnDestroy {
   /** Ground motion plot */
-  groundMotionPlotData$ = this.facade.plots$.pipe(
-    map(plots => plots?.get(Plots.GROUND_MOTION))
-  );
+  groundMotionPlot = computed(() => {
+    this.settingsFormSubscription?.unsubscribe();
+    const plot = this.facade.state().plots.get(Plots.GROUND_MOTION);
+
+    this.settingsFormSubscription = plot.settingsForm.valueChanges.subscribe(
+      () => {
+        this.resetDisabled =
+          plot.settingsForm.pristine && plot.settingsForm.untouched;
+      }
+    );
+
+    return plot;
+  });
 
   panels: PlotSettingsPanel[] = [
     {
-      plot$: this.groundMotionPlotData$,
+      plot: this.groundMotionPlot,
       title: 'Ground Motion Plot Settings',
     },
   ];
 
+  resetDisabled = true;
+  private settingsFormSubscription = new Subscription();
+
   constructor(public facade: AppFacade) {}
+
+  ngOnDestroy(): void {
+    this.settingsFormSubscription.unsubscribe();
+  }
+
+  updatePlot(plot: NshmpPlot): void {
+    this.facade.updatePlot(plot, Plots.GROUND_MOTION);
+  }
 }
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html
index 230e07f5420450bf0ecf87f48bb70573969132ba..6beaf05147eb73f519f3380c9651fe347c3143b8 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/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. distance plot -->
     <mat-expansion-panel expanded>
@@ -8,8 +8,8 @@
 
       <mat-divider />
 
-      @if (groundMotionPlotData$ | async; as groundMotionPlot) {
-        <nshmp-lib-ng-plot [plot]="groundMotionPlot" />
+      @if (groundMotionPlotData()) {
+        <nshmp-lib-no-ngrx-plot [plot]="groundMotionPlotData()" />
       }
 
       <mat-divider />
@@ -18,8 +18,8 @@
       <div class="fault grid-row">
         <!-- Fault plot -->
         <div class="fault-plot grid-col-12 tablet-lg:grid-col-7">
-          @if (faultPlotData$ | async; as faultPlot) {
-            <nshmp-lib-ng-plot [plot]="faultPlot" />
+          @if (faultPlotData()) {
+            <nshmp-lib-no-ngrx-plot [plot]="faultPlotData()" />
           }
         </div>
 
@@ -29,36 +29,31 @@
         <div class="fault-controls grid-col-12 tablet-lg:grid-col">
           <mat-label class="section-label">Source Geometry</mat-label>
 
-          @if (formState$ | async; as formState) {
-            <form [ngrxFormState]="formState">
-              <!-- Dip -->
-              <app-fault-control
-                [formControlState]="formState?.controls?.dip"
-                label="Dip"
-                [parameter]="dipParameter$ | async"
-                textSuffix="°"
-                (faultControlChange)="onFaultControlChange()"
-              />
+          <form [formGroup]="formState">
+            <!-- Dip -->
+            <app-fault-control
+              [control]="formState.controls.dip"
+              label="Dip"
+              [parameter]="dipParameter()"
+              textSuffix="°"
+            />
 
-              <!-- Width -->
-              <app-fault-control
-                [formControlState]="formState?.controls?.width"
-                label="Width"
-                [parameter]="widthParameter$ | async"
-                textSuffix="km"
-                (faultControlChange)="onFaultControlChange()"
-              />
+            <!-- Width -->
+            <app-fault-control
+              [control]="formState.controls.width"
+              label="Width"
+              [parameter]="widthParameter()"
+              textSuffix="km"
+            />
 
-              <!-- zTop -->
-              <app-fault-control
-                [formControlState]="formState?.controls?.zTor"
-                label="zTor"
-                [parameter]="zTorParameter$ | async"
-                textSuffix="km"
-                (faultControlChange)="onFaultControlChange()"
-              />
-            </form>
-          }
+            <!-- zTop -->
+            <app-fault-control
+              [control]="formState.controls.zTor"
+              label="zTor"
+              [parameter]="zTorParameter()"
+              textSuffix="km"
+            />
+          </form>
         </div>
       </div>
     </mat-expansion-panel>
@@ -80,7 +75,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/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts
index e562e2d316dbc6e5c6968468fe2ecbe0b704f44f..dca4b3cde8d936b2ebf9434b673b175b51a007a9 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts
@@ -1,7 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} 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(async () => {
     await TestBed.configureTestingModule({
       imports: [PlotsComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideHttpClient(),
-        provideNoopAnimations(),
-      ],
+      providers: [provideHttpClient(), provideNoopAnimations()],
     }).compileComponents();
 
     fixture = TestBed.createComponent(PlotsComponent);
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts
index 2ce03e07c256605c121e7df133d65e31e44802de..154d539383dc568b9685413c4bd572e7aeeaffec 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts
@@ -1,5 +1,6 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, Signal} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatDivider} from '@angular/material/divider';
 import {
   MatAccordion,
@@ -8,12 +9,12 @@ import {
   MatExpansionPanelTitle,
 } from '@angular/material/expansion';
 import {MatLabel} from '@angular/material/form-field';
-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';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
+import {GmmUsageParameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 
 import {AppFacade} from '../../state/app.facade';
 import {Plots} from '../../state/app.state';
@@ -41,6 +42,7 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
     FaultControlComponent,
     ParameterSummaryComponent,
     AsyncPipe,
+    ReactiveFormsModule,
   ],
   selector: 'app-plots',
   standalone: true,
@@ -49,45 +51,41 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
 })
 export class PlotsComponent {
   /** Usage parameters */
-  private parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
+  private parameters = computed(
+    () => this.facade.state().usageResponse?.response?.parameters
   );
 
   /** Ground motion plot */
-  groundMotionPlotData$ = this.facade.plots$.pipe(
-    map(plots => plots?.get(Plots.GROUND_MOTION)?.plotData)
+  groundMotionPlotData = computed(
+    () => this.facade.state().plots?.get(Plots.GROUND_MOTION)?.plotData
   );
 
   /** Fault plot */
-  faultPlotData$ = this.facade.plots$.pipe(
-    map(plots => plots?.get(Plots.FAULT)?.plotData)
+  faultPlotData = computed(
+    () => this.facade.state().plots?.get(Plots.FAULT)?.plotData
   );
 
   /** Control panel form field state */
-  formState$ = this.facade.controlPanelForm$;
+  formState = this.facade.controlPanelForm;
 
   /** Dip gmm parameter */
-  dipParameter$ = this.parameters$.pipe(map(paramerters => paramerters?.dip));
+  dipParameter = computed(() => this.parameters()?.dip);
 
   /** Width gmm parameter with more restrictive max */
-  widthParameter$ = this.parameters$.pipe(
-    map(paramerters => ({
-      ...paramerters?.width,
-      max: 30,
-    }))
-  );
+  widthParameter: Signal<GmmUsageParameter> = computed(() => ({
+    ...this.parameters()?.width,
+    max: 30,
+  }));
 
   /** zTor gmm parameter with more restrictive max */
-  zTorParameter$ = this.parameters$.pipe(
-    map(paramerters => ({
-      ...paramerters?.zTor,
-      max: 10,
-    }))
-  );
+  zTorParameter: Signal<GmmUsageParameter> = computed(() => ({
+    ...this.parameters()?.zTor,
+    max: 10,
+  }));
 
   /** Repo metadata */
-  repositories$ = this.facade.usage$.pipe(
-    map(usage => usage?.metadata.repositories)
+  repositories = computed(
+    () => this.facade.state().usageResponse?.metadata.repositories
   );
 
   constructor(private facade: AppFacade) {}
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts
deleted file mode 100644
index 38c38fde5fb301c62eaafbf8b4031ab47441f4b8..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import {
-  GmmDistanceResponse,
-  GmmDistanceUsage,
-} 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';
-
-/**
- * Application NGRX actions
- */
-export const appActions = createActionGroup({
-  events: {
-    ...sharedActions,
-    /** Action to call services from query is valid */
-    'Initial Call From Query': emptyProps(),
-    /** Action to set the initial form values from query or default */
-    'Initial Form Set': emptyProps(),
-    /** Set the service response action */
-    'Service Responses': props<{serviceResponses: GmmDistanceResponse[]}>(),
-    /** Set the usage response action */
-    'Usage Response': props<{
-      usageResponse: GmmDistanceUsage;
-    }>(),
-  },
-  source: 'Development Hanging Wall Effects',
-});
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts
deleted file mode 100644
index 9b5aaca77b9290b4ad5591c4a0d55a1dd8aa2d6c..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-import {Injectable} from '@angular/core';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {
-  NshmpService,
-  ServiceCallInfo,
-  SpinnerService,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {
-  GmmDistanceResponse,
-  GmmDistanceUsage,
-} 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 {createPlots} from '../utils/response-handler.utils';
-import {appActions} from './app.actions';
-import {hangingWallEffectsAppFeature} from './app.reducer';
-import {defaultFormValues} from './app.state';
-
-/**
- * NGRX application effects.
- */
-@Injectable()
-export class HangingWallEffectsAppEffects {
-  /** nshmp-ws base URL */
-  baseUrl = environment.webServices.data.url;
-  /** GMM service URL */
-  serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmHwFw}`;
-
-  /**
-   * Call the services.
-   */
-  callService$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(appActions.callServices),
-      concatLatestFrom(() =>
-        this.store.select(
-          hangingWallEffectsAppFeature.selectHanginWallEffectsAppState
-        )
-      ),
-      exhaustMap(([, state]) => {
-        if (state.controlPanelForm.isInvalid) {
-          return [];
-        }
-
-        this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
-
-        const urls = gmmUtils.serviceEndpoints(
-          this.serviceUrl,
-          state.controlPanelForm.value,
-          state.controlPanelForm.value.multiSelectableParam
-        );
-
-        return this.nshmpService.callServices$(urls).pipe(
-          mergeMap((serviceResponses: GmmDistanceResponse[]) => {
-            return [appActions.serviceResponses({serviceResponses})];
-          }),
-          catchError((error: Error) => this.nshmpService.throwError$(error))
-        );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Create the plot data from the service responses.
-   */
-  createPlots$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(appActions.serviceResponses),
-      concatLatestFrom(() =>
-        this.store.select(
-          hangingWallEffectsAppFeature.selectHanginWallEffectsAppState
-        )
-      ),
-      map(([, state]) => {
-        const plots = createPlots(state);
-        this.spinnerService.remove();
-        return appActions.plots({plots});
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Initialize the application.
-   */
-  init$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(appActions.init),
-      concatLatestFrom(() =>
-        this.store.select(
-          hangingWallEffectsAppFeature.selectHanginWallEffectsAppState
-        )
-      ),
-      exhaustMap(([, state]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
-
-        return this.nshmpService
-          .callService$<GmmDistanceUsage>(this.serviceUrl)
-          .pipe(
-            mergeMap(usageResponse => {
-              this.spinnerService.remove();
-              const parameters = usageResponse.response.parameters;
-              const values = defaultFormValues(parameters);
-              const controls = state.controlPanelForm.controls;
-
-              const typedActions = gmmUtils.initialFormSetActions(
-                state.controlPanelForm,
-                values,
-                parameters
-              );
-              typedActions.push(appActions.usageResponse({usageResponse}));
-              gmmUtils.addFormAction(
-                parameters.rMax,
-                controls.rMax.id,
-                values.rMax,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.rMin,
-                controls.rMin.id,
-                values.rMin,
-                typedActions
-              );
-
-              const serviceCallInfo: ServiceCallInfo = {
-                ...state.serviceCallInfo,
-                usage: [this.serviceUrl],
-              };
-
-              return [
-                appActions.serviceCallInfo({serviceCallInfo}),
-                ...typedActions,
-              ];
-            }),
-            catchError((error: Error) => this.nshmpService.throwError$(error))
-          );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  constructor(
-    private actions$: Actions,
-    private nshmpService: NshmpService,
-    private spinnerService: SpinnerService,
-    private store: Store
-  ) {}
-}
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
index 34a40e6e0fceb67ea7c10614e2bb82f18c2c5666..e1788aeb6a5c9909c2c232a33b9659204ca4be67 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts
@@ -1,105 +1,252 @@
-import {Injectable} from '@angular/core';
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {Injectable, 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 {
   GmmDistanceResponse,
   GmmDistanceUsage,
 } 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 {Observable} from 'rxjs';
-
-import {appActions} from './app.actions';
-import {hangingWallEffectsAppFeature} from './app.reducer';
-import {ControlPanelForm} from './app.state';
-
-/**
- * Entrypoint to NGRX store for hanging wall effects application.
- */
-@Injectable({
-  providedIn: 'root',
-})
+import {environment} from 'projects/nshmp-apps/src/environments/environment';
+import {catchError} from 'rxjs';
+
+import {createPlots} from '../utils/response-handler.utils';
+import {
+  AppState,
+  ControlPanelForm,
+  defaultFormValues,
+  defaultPlots,
+  initialState,
+  usageFormValues,
+} from './app.state';
+
+@Injectable({providedIn: 'root'})
 export class AppFacade {
-  constructor(private store: Store) {}
+  /** Application state */
+  readonly state = signal<AppState>({
+    ...initialState(),
+  });
+
+  /** Control panel form group */
+  private formGroup = this.formBuilder.group({
+    ...defaultFormValues(),
+    gmmSource: [],
+    MwMulti: [],
+    vs30Multi: [],
+  }) as FormGroupControls<ControlPanelForm>;
+
+  /** nshmp-ws base URL */
+  private baseUrl = environment.webServices.data.url;
+  /** GMM service URL */
+  private serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmHwFw}`;
+
+  constructor(
+    private spinnerService: SpinnerService,
+    private nshmpService: NshmpService,
+    private formBuilder: FormBuilder
+  ) {
+    this.validators(this.formGroup);
+    this.formGroup.controls.gmmSource.setValue([]);
+    this.formGroup.controls.showEpistemicUncertainty.disable();
+  }
 
   /**
-   * Returns the control panel form state.
+   * Call the GMM distance service.
    */
-  get controlPanelForm$(): Observable<FormGroupState<ControlPanelForm>> {
-    return this.store.pipe(
-      select(hangingWallEffectsAppFeature.selectControlPanelForm)
+  callServices(): void {
+    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$<GmmDistanceResponse>(urls)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(serviceResponses =>
+        this.handleServiceResponses(serviceResponses)
+      );
   }
 
   /**
-   * Returns the plots.
+   * Returns the control panel form state.
    */
-  get plots$(): Observable<Map<string, NshmpPlot>> {
-    return this.store.pipe(select(hangingWallEffectsAppFeature.selectPlots));
+  get controlPanelForm(): FormGroupControls<ControlPanelForm> {
+    return this.formGroup;
   }
 
   /**
-   * Returns the service call info.
+   * Create plots based on current state and form group.
    */
-  get serviceCallInfo$(): Observable<ServiceCallInfo> {
-    return this.store.pipe(
-      select(hangingWallEffectsAppFeature.selectServiceCallInfo)
-    );
+  createPlots(): void {
+    this.updateState({
+      plots: createPlots(this.state(), this.formGroup),
+    });
   }
 
   /**
-   * Returns the service responses.
+   * Initialize application.
    */
-  get serviceResponses$(): Observable<GmmDistanceResponse[]> {
-    return this.store.pipe(
-      select(hangingWallEffectsAppFeature.selectServiceResponses)
-    );
+  init(): void {
+    this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
+
+    this.nshmpService
+      .callService$<GmmDistanceUsage>(this.serviceUrl)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
   /**
-   * Returns the supported IMTs.
+   * Reset the control panel.
    */
-  get supportedImts$(): Observable<EnumParameterValues[]> {
-    return this.store.pipe(
-      select(hangingWallEffectsAppFeature.selectSupportedImts)
+  resetControlPanel(): void {
+    this.formGroup.reset(
+      usageFormValues(this.state().usageResponse.response.parameters)
     );
   }
 
   /**
-   * Return the usage for the selected model.
+   * Reset the plot settings.
    */
-  get usage$(): Observable<GmmDistanceUsage> {
-    return this.store.pipe(
-      select(hangingWallEffectsAppFeature.selectUsageResponse)
-    );
+  resetPlotSettings(): void {
+    this.state().plots.forEach((plot, id) => {
+      const defaultSettings = defaultPlots().get(id).settingsForm;
+      plot.settingsForm.patchValue(defaultSettings.getRawValue());
+    });
   }
 
   /**
-   * Call the GMM distance service.
+   * Reset the state.
    */
-  callServices(): void {
-    this.store.dispatch(appActions.callServices());
+  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();
   }
 
   /**
-   * Initialize the application.
+   * Update a single plot in the state.
+   *
+   * @param plot Updated plot
+   * @param id Id of plot to update
    */
-  init(): void {
-    this.store.dispatch(appActions.init());
+  updatePlot(plot: NshmpPlot, id: string): void {
+    const plots = this.state().plots;
+    plots.set(id, plot);
+    this.updateState({
+      plots,
+    });
   }
 
   /**
-   * Reset the control panel.
+   * Update state.
+   *
+   * @param state Partial new state to update
    */
-  resetControlPanel(): void {
-    this.store.dispatch(appActions.resetControlPanel());
+  updateState(state: Partial<AppState>): void {
+    this.state.set({
+      ...this.state(),
+      ...state,
+    });
   }
 
   /**
-   * Reset the control panel.
+   * Handle service response result.
+   *
+   * @param serviceResponses The service responses
    */
-  resetPlotSettings(): void {
-    this.store.dispatch(appActions.resetSettings());
+  private handleServiceResponses(
+    serviceResponses: GmmDistanceResponse[]
+  ): void {
+    const means = serviceResponses.map(s => s.response.means);
+    const hasLogicTree = gmmUtils.hasTree(means);
+
+    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();
+  }
+
+  /**
+   * Handle usage response.
+   *
+   * @param usageResponse Usage response
+   */
+  private handleUsageResponse(usageResponse: GmmDistanceUsage): 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();
+  }
+
+  /**
+   * Add validators to form controls.
+   *
+   * @param form The form group
+   */
+  private validators(form: FormGroupControls<ControlPanelForm>): 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);
   }
 }
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts
deleted file mode 100644
index 386e34c782dddfc91e902d02ed7d44c29e45b1d8..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {nshmpUtils} from '@ghsc/nshmp-lib-ng/nshmp';
-import {createFeature, createReducer, on} from '@ngrx/store';
-import {
-  box,
-  createFormGroupState,
-  disable,
-  enable,
-  onNgrxForms,
-  onNgrxFormsAction,
-  setValue,
-  SetValueAction,
-  unbox,
-  updateGroup,
-  validate,
-  wrapReducerWithFormStateUpdate,
-} from 'ngrx-forms';
-import {required} from 'ngrx-forms/validation';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {
-  onPlotRedraw,
-  onPlots,
-  onPlotSettingsForm,
-  onResetSetting,
-} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {createPlots} from '../utils/response-handler.utils';
-import {appActions} from './app.actions';
-import {
-  AppState,
-  CONTROL_PANEL_FORM_ID,
-  ControlPanelForm,
-  defaultFormValues,
-  defaultPlots,
-  initialState,
-  PlotSettingsId,
-} from './app.state';
-
-/** Form state key ids */
-const formKeys = {
-  controlPanel: {
-    dip: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.DIP}`,
-    gmmSource: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.GMM_SOURCE}`,
-    imt: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.IMT}`,
-    multiSelectable: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.MULTI_SELECTABLE_PARAM}`,
-    showEpistemicUncertainty: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.SHOW_EPISTEMIC_UNCERTAINTY}`,
-    width: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.WIDTH}`,
-    zTop: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.ZTOR}`,
-  },
-};
-
-const baseUrl = environment.webServices.data.url;
-const endpoint = environment.webServices.data.services.gmmHwFw;
-const serviceUrl = `${baseUrl}${endpoint}`;
-
-export const hangingWallEffectsAppFeature = createFeature({
-  name: 'hanginWallEffectsApp',
-  reducer: createReducer(
-    // Initial state
-    initialState(),
-    // On NGRX forms
-    onNgrxForms(),
-    // Handle NGRX form changes
-    onNgrxFormsAction(SetValueAction, (state, action) =>
-      onFormAction(state, action)
-    ),
-    // Handle initialization
-    on(appActions.init, state => ({
-      ...state,
-      plots: createPlots(state),
-    })),
-    // On plots action
-    on(appActions.plots, (state, {plots}) => onPlots(state, plots)),
-    // On plot redraw action
-    on(appActions.plotRedraw, state => onPlotRedraw(state)),
-    // Reset the control panel
-    on(appActions.resetControlPanel, state => {
-      state = {
-        ...state,
-        controlPanelForm: createFormGroupState<ControlPanelForm>(
-          CONTROL_PANEL_FORM_ID,
-          defaultFormValues(state.usageResponse.response.parameters)
-        ),
-        plots: initialState().plots,
-        serviceCallInfo: initialState().serviceCallInfo,
-        serviceResponses: initialState().serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam:
-            state.controlPanelForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlPanelForm.value,
-        }),
-      };
-    }),
-    /* On reset plot settings action */
-    on(appActions.resetSettings, state => {
-      return onResetSetting(state, defaultPlots());
-    }),
-    // On service call info action
-    on(appActions.serviceCallInfo, (state, {serviceCallInfo}) => ({
-      ...state,
-      serviceCallInfo: {
-        ...state.serviceCallInfo,
-        ...serviceCallInfo,
-      },
-    })),
-    // Set the service responses
-    on(appActions.serviceResponses, (state, {serviceResponses}) => {
-      const means = serviceResponses.map(s => s.response.means);
-      const hasLogicTree = gmmUtils.hasTree(means);
-
-      state = {
-        ...state,
-        controlPanelForm: updateGroup<ControlPanelForm>({
-          showEpistemicUncertainty: hasLogicTree ? enable : disable,
-        })(state.controlPanelForm),
-        serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam:
-            state.controlPanelForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlPanelForm.value,
-        }),
-      };
-    }),
-    // On usage response action
-    on(appActions.usageResponse, (state, {usageResponse}) => ({
-      ...state,
-      usageResponse,
-    }))
-  ),
-});
-
-// Add validators
-hangingWallEffectsAppFeature.reducer = wrapReducerWithFormStateUpdate(
-  hangingWallEffectsAppFeature.reducer,
-  state => state.controlPanelForm,
-  updateGroup<ControlPanelForm>({
-    dip: nshmpUtils.validateBounds,
-    gmmSource: validate([required]),
-    imt: validate([required]),
-    multiSelectableParam: validate([required]),
-    Mw: nshmpUtils.validateBounds,
-    vs30: nshmpUtils.validateBounds,
-    width: nshmpUtils.validateBounds,
-    z1p0: nshmpUtils.validateBoundsOnly,
-    z2p5: nshmpUtils.validateBoundsOnly,
-    zSed: nshmpUtils.validateBoundsOnly,
-    zTor: nshmpUtils.validateBounds,
-  })
-);
-
-/**
- * Handle form actions.
- *
- * @param state The current state
- * @param action The action
- */
-function onFormAction(
-  state: AppState,
-  action: SetValueAction<unknown>
-): AppState {
-  const clearState: AppState = {
-    ...state,
-    controlPanelForm: updateGroup<ControlPanelForm>({
-      showEpistemicUncertainty: control => disable(setValue(control, false)),
-    })(state.controlPanelForm),
-    serviceResponses: null,
-  };
-  clearState.plots = createPlots(clearState);
-
-  const serviceCallInfo = gmmUtils.serviceCallInfo({
-    multiSelectableParam:
-      clearState.controlPanelForm.value.multiSelectableParam,
-    serviceName: clearState.serviceCallInfo.serviceName,
-    serviceResponses: clearState.serviceResponses,
-    serviceUrl,
-    values: clearState.controlPanelForm.value,
-  });
-
-  switch (action.controlId) {
-    case formKeys.controlPanel.gmmSource: {
-      const gmm = unbox(state.controlPanelForm.value.gmmSource);
-      if (
-        state.usageResponse &&
-        state.controlPanelForm.controls.gmmSource &&
-        gmm.length > 0
-      ) {
-        const supportedImts = gmmUtils.getSupportedImts(
-          gmm,
-          state.usageResponse
-        );
-
-        const controlPanelForm = gmmUtils.onGmmChange(
-          clearState.controlPanelForm,
-          supportedImts
-        );
-
-        return {
-          ...clearState,
-          controlPanelForm,
-          serviceCallInfo,
-          supportedImts,
-        };
-      }
-      return {
-        ...clearState,
-        serviceCallInfo,
-      };
-    }
-    case formKeys.controlPanel.multiSelectable: {
-      let form = {...clearState.controlPanelForm};
-      const parameters = clearState.usageResponse?.response?.parameters || null;
-
-      if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
-        form = updateGroup<ControlPanelForm>({
-          gmmSource: gmm => setValue(gmm, box([])),
-          Mw: Mw => setValue(Mw, (parameters.Mw.value as number) || null),
-          MwMulti: Mw => setValue(Mw, box([])),
-        })(state.controlPanelForm);
-      } else if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-        form = updateGroup<ControlPanelForm>({
-          gmmSource: gmm => setValue(gmm, box([])),
-          vs30: vs30 =>
-            setValue(vs30, (parameters.vs30.value as number) || null),
-          vs30Multi: vs30 => setValue(vs30, box([])),
-        })(state.controlPanelForm);
-      }
-
-      return {
-        ...clearState,
-        controlPanelForm: form,
-        serviceCallInfo,
-      };
-    }
-    case formKeys.controlPanel.showEpistemicUncertainty: {
-      return {
-        ...state,
-        plots: createPlots(state),
-      };
-    }
-    case formKeys.controlPanel.dip:
-    case formKeys.controlPanel.width:
-    case formKeys.controlPanel.zTop: {
-      return {
-        ...state,
-        plots: createPlots(state),
-        serviceCallInfo,
-      };
-    }
-    case formKeys.controlPanel.imt: {
-      return {
-        ...clearState,
-        serviceCallInfo,
-      };
-    }
-    default: {
-      if (
-        Object.values(PlotSettingsId).some(id => action.controlId.includes(id))
-      ) {
-        return onPlotSettingsForm(state, action);
-      } else {
-        return {
-          ...clearState,
-          serviceCallInfo,
-        };
-      }
-    }
-  }
-}
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts
index bad7120487b405ebff6cdd1b0f741dfc1e638362..b34d5acb272ee646ce4601ab29d98d28a8507ae1 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts
@@ -1,15 +1,15 @@
 import {
   GmmFormControlIds,
   GmmImtFormControls,
-  GmmSource,
-} from '@ghsc/nshmp-lib-ng/gmm';
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+  gmmUtils,
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpPlot,
   NshmpPlotSettings,
   PlotOptions,
   plotUtils,
-} from '@ghsc/nshmp-lib-ng/plot';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmDistanceResponse,
   GmmDistanceUsage,
@@ -17,18 +17,6 @@ import {
   GmmGroupType,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {
-  box,
-  createFormGroupState,
-  disable,
-  FormGroupState,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
-
-/** Control panel form id */
-export const CONTROL_PANEL_FORM_ID =
-  'ngrx-forms [Hanging Wall Effects Control Form]';
 
 /**
  * Plot ids.
@@ -38,20 +26,11 @@ export enum Plots {
   GROUND_MOTION = 'GROUND_MOTIONS',
 }
 
-/**
- * Plot settings ids.
- */
-export enum PlotSettingsId {
-  FAULT = 'ngrx-forms [Fault Settings]',
-  GROUND_MOTION = 'ngrx-forms [Ground Motion Settings]',
-}
-
 /**
  * GMM Hanging Wall Effects NGRX state.
  */
-export interface AppState extends SharedAppState {
-  /** Control panel form field state */
-  controlPanelForm: FormGroupState<ControlPanelForm>;
+export interface AppState {
+  plots: Map<string, NshmpPlot>;
   /** Service call info */
   serviceCallInfo: ServiceCallInfo;
   /** GMM service response */
@@ -72,45 +51,16 @@ export interface ControlPanelForm extends GmmImtFormControls {
   rMin: number;
 }
 
-/**
- * Initial state for the control panel,
- */
-export function controlPanelFormInitialState(): FormGroupState<ControlPanelForm> {
-  const state = createFormGroupState<ControlPanelForm>(CONTROL_PANEL_FORM_ID, {
-    dip: null,
-    gmmGroupType: GmmGroupType.ACTIVE_CRUST,
-    gmmSource: box([] as GmmSource[]),
-    imt: 'default',
-    multiSelectableParam: GmmFormControlIds.GMM,
-    Mw: null,
-    MwMulti: box([] as number[]),
-    rMax: 100,
-    rMin: -20,
-    showEpistemicUncertainty: false,
-    vs30: null,
-    vs30Multi: box([] as number[]),
-    width: null,
-    z1p0: null,
-    z2p5: null,
-    zSed: null,
-    zTor: null,
-  });
-
-  return updateGroup<ControlPanelForm>({
-    showEpistemicUncertainty: disable,
-  })(state);
-}
-
 /**
  * Returns the default form values based on the usage.
  *
  * @param parameters The response parameters
  */
-export function defaultFormValues(
+export function usageFormValues(
   parameters: GmmDistanceUsageParameters
 ): ControlPanelForm {
   return {
-    ...controlPanelFormInitialState().value,
+    ...defaultFormValues(),
     dip: parameters.dip.value as number,
     Mw: parameters.Mw.value as number,
     vs30: parameters.vs30.value as number,
@@ -121,65 +71,47 @@ export function defaultFormValues(
   };
 }
 
+/**
+ * Returns the default form values.
+ */
+export function defaultFormValues(): ControlPanelForm {
+  return {
+    dip: null,
+    gmmGroupType: GmmGroupType.ACTIVE_CRUST,
+    gmmSource: [],
+    imt: gmmUtils.imtPlaceHolder().value,
+    multiSelectableParam: GmmFormControlIds.GMM,
+    Mw: null,
+    MwMulti: [],
+    rMax: 100,
+    rMin: -20,
+    showEpistemicUncertainty: false,
+    vs30: null,
+    vs30Multi: [],
+    width: null,
+    z1p0: null,
+    z2p5: null,
+    zSed: null,
+    zTor: null,
+  };
+}
+
 /**
  * Returns the default plots.
  */
 export function defaultPlots(): Map<string, NshmpPlot> {
   const plots = new Map<string, NshmpPlot>();
 
-  const groundMotionPlotData = plotUtils.defaultPlot({
-    id: 'ground-motion-curves',
-    mobileOptions: {
-      ...groundMotionPlotOptions,
-      layout: {
-        ...groundMotionPlotOptions.layout,
-        aspectRatio: '4:3',
-      },
-    },
-    options: groundMotionPlotOptions,
-    title: 'Hanging Wall Effects',
-    xLabel: 'Distance (km)',
-    yLabel: 'Median ground motion (g)',
-  });
-
   plots.set(Plots.GROUND_MOTION, {
     label: 'Hanging Wall Effects',
     plotData: groundMotionPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      PlotSettingsId.GROUND_MOTION,
-      {
-        config: groundMotionPlotData.config,
-        layout: plotUtils.plotlyLayoutToSettings(groundMotionPlotData.layout),
-      }
-    ),
-  });
-
-  const faultPlotData = plotUtils.defaultPlot({
-    id: 'fault',
-    mobileOptions: {
-      ...faultPlotOptions,
-      layout: {
-        ...faultPlotOptions.layout,
-        aspectRatio: '4:3',
-      },
-    },
-    options: faultPlotOptions,
-    panelBreakpoint: 660 * 0.6,
-    title: '',
-    xLabel: 'Distance (km)',
-    yLabel: 'Depth (km)',
+    settingsForm: plotUtils.plotSettingsToFormGroup(groundMotionSettingsForm),
   });
 
   plots.set(Plots.FAULT, {
-    label: 'Fault',
+    label: 'Faults',
     plotData: faultPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      PlotSettingsId.FAULT,
-      {
-        config: faultPlotData.config,
-        layout: plotUtils.plotlyLayoutToSettings(faultPlotData.layout),
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(faultSettingsForm),
   });
 
   return plots;
@@ -190,14 +122,13 @@ export function defaultPlots(): Map<string, NshmpPlot> {
  */
 export function initialState(): AppState {
   return {
-    controlPanelForm: controlPanelFormInitialState(),
     plots: defaultPlots(),
     serviceCallInfo: {
       serviceCalls: [],
       serviceName: 'Ground Motion vs. Distance',
-      usage: null,
+      usage: [],
     },
-    serviceResponses: null,
+    serviceResponses: [],
     supportedImts: [],
     usageResponse: null,
   };
@@ -245,3 +176,44 @@ const faultPlotOptions: PlotOptions = {
     },
   },
 };
+
+const groundMotionPlotData = plotUtils.defaultPlot({
+  id: 'ground-motions',
+  mobileOptions: {
+    ...groundMotionPlotOptions,
+    layout: {
+      ...groundMotionPlotOptions.layout,
+      aspectRatio: '4:3',
+    },
+  },
+  options: groundMotionPlotOptions,
+  title: 'Hanging Wall Effects',
+  xLabel: 'Distance (km)',
+  yLabel: 'Median ground motion (g)',
+});
+
+const groundMotionSettingsForm: NshmpPlotSettings = {
+  config: groundMotionPlotData.config,
+  layout: plotUtils.plotlyLayoutToSettings(groundMotionPlotData.layout),
+};
+
+const faultPlotData = plotUtils.defaultPlot({
+  id: 'fault-id',
+  mobileOptions: {
+    ...faultPlotOptions,
+    layout: {
+      ...faultPlotOptions.layout,
+      aspectRatio: '4:3',
+    },
+  },
+  options: faultPlotOptions,
+  panelBreakpoint: 660 * 0.6,
+  title: '',
+  xLabel: 'Distance (km)',
+  yLabel: 'Depth (km)',
+});
+
+const faultSettingsForm: NshmpPlotSettings = {
+  config: faultPlotData.config,
+  layout: plotUtils.plotlyLayoutToSettings(faultPlotData.layout),
+};
diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts
index 3ec535ff06c73218a39b9f50579ebd8b597498d3..03912b07fd7ba8249f146eef144db570ef004d52 100644
--- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts
+++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts
@@ -1,5 +1,6 @@
-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 {Imt, imtToString} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm';
 import {PlotlyPlot} from '@ghsc/nshmp-utils-ts/libs/plotly';
@@ -17,16 +18,27 @@ import {
  *
  * @param state The application state
  */
-export function createPlots(state: AppState): Map<string, NshmpPlot> {
+export function createPlots(
+  state: AppState,
+  form: FormGroupControls<ControlPanelForm>
+): Map<string, NshmpPlot> {
   const plots = new Map<string, NshmpPlot>();
   const groundMotionPlot = state.plots.get(Plots.GROUND_MOTION);
   const faultPlot = state.plots.get(Plots.FAULT);
-  const formValues = state.controlPanelForm.value;
+  const formValues = form.getRawValue();
 
   if (state.serviceResponses !== null && state.serviceResponses?.length !== 0) {
     const hoverTemplate = '%{x} km, %{y} AFE';
     const title = `Hanging Wall Effects: ${imtToString(formValues.imt as Imt)}`;
 
+    groundMotionPlot.settingsForm.patchValue({
+      layout: {
+        title: {
+          text: title,
+        },
+      },
+    });
+
     const responses: gmmUtils.GmmResponse<XySequence, number[]>[] =
       state.serviceResponses.map(serviceResponse => ({
         input: serviceResponse.request.input,