diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html
index a9334d5c75e42833d34ae7eedd7c38f60972edb5..9d1df4a31e21a9131f2b75950a48a617fd0296bd 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/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="gmm-distance-means.csv"
         buttonText="Export Means 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/gmm/distance/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.spec.ts
index f6d805b1bb51d339db0fbae337aa303dc7dab589..87deffc6a1218bd2028c5594413e50fa9a4c58df 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.spec.ts
@@ -1,6 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ContentComponent} from './content.component';
 
@@ -11,10 +11,7 @@ describe('GmmDistanceContentComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ContentComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideNoopAnimations(),
-      ],
+      providers: [provideNoopAnimations(), provideHttpClient()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.ts
index 11efd9b218c231390590d4d48537a1ec9f01176f..03e93dac3c18df468cd0012704f979c179b133c6 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/content/content.component.ts
@@ -1,12 +1,10 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatTab, MatTabContent, MatTabGroup} from '@angular/material/tabs';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgDataTableComponent,
   NshmpLibNgExportDataTableComponent,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {GmmDistanceAppFacade} from '../../state/app.facade';
 import {PlotsComponent} from '../plots/plots.component';
@@ -22,7 +20,6 @@ import {PlotsComponent} from '../plots/plots.component';
     MatTab,
     MatTabContent,
     PlotsComponent,
-    AsyncPipe,
     NshmpLibNgExportDataTableComponent,
     NshmpLibNgDataTableComponent,
   ],
@@ -38,14 +35,14 @@ export class ContentComponent {
   exp = true;
 
   /** Whether service has been called and data exists */
-  hasData$ = this.facade.serviceResponse$.pipe(
-    map(responseSpectra => responseSpectra?.length > 0)
-  );
+  hasData = computed(() => this.facade.serviceResponse()?.length > 0);
 
   /** Table data for table */
-  table$ = this.facade.meanPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(plot.plotData, this.exp, this.commonXValues)
+  table = computed(() =>
+    gmmUtils.plotToTable(
+      this.facade.meanPlotState().plotData,
+      this.exp,
+      this.commonXValues
     )
   );
 
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
index 55bdb4404a6fd4debebb00e0016c4316d48e4169..55b262f71a1aa723f3417fbae0dc9063fad0792d 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.html
@@ -1,221 +1,216 @@
 <!-- GMM Distance Conrol Panel -->
-@if (form$ | async) {
-  <form
-    class="settings-section control-panel height-full overflow-auto"
-    [ngrxFormState]="form$ | async"
-    (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]="(controls$ | async)?.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"
+<form
+  class="settings-section control-panel height-full overflow-auto"
+  [formGroup]="form"
+  (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]="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 -->
+  @if (parameters()) {
+    <nshmp-lib-no-ngrx-gmm-menu
+      [gmmControl]="controls.gmmSource"
+      [gmmGroupTypeControl]="controls.gmmGroupType"
+      [multiple]="controls.multiSelectableParam.value === 'gmm'"
+      [parameters]="parameters()"
+      [imtControl]="controls.imt"
+      [supportedImts]="supportedImts()"
     />
-
-    <!-- IMT select menu -->
-    <mat-form-field class="grid-col-12 imt-select">
-      <mat-label>Intensity Measure Type</mat-label>
-      <mat-select
-        #imtSelectEl
-        [ngrxFormControlState]="(controls$ | async)?.imt"
-      >
-        @if ((gmmSelected$ | async) === false) {
-          <mat-option selected="true" value="default">
-            --- Choose a GMM ---
-          </mat-option>
-        }
-        @for (imt of supportedImts$ | async; track imt) {
-          <mat-option [value]="imt.value">
-            {{ imt.display }}
-          </mat-option>
-        }
-      </mat-select>
-    </mat-form-field>
-
-    <!-- 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]="(controls$ | async)?.Mw"
-          [parameter]="(parameters$ | async)?.Mw"
-          placeholder="Select Magnitudes"
-          [selectControl]="(controls$ | async)?.MwMulti"
-          [selectOptions]="MwCommonValues"
-        />
-      </div>
+  }
+
+  <!-- IMT select menu -->
+  <mat-form-field class="grid-col-12 imt-select">
+    <mat-label>Intensity Measure Type</mat-label>
+    <mat-select [formControl]="controls?.imt">
+      @if (gmmSelected === false) {
+        <mat-option selected="true" value="default">
+          --- Choose a GMM ---
+        </mat-option>
+      }
+      @for (imt of supportedImts(); track imt) {
+        <mat-option [value]="imt.value">
+          {{ imt.display }}
+        </mat-option>
+      }
+    </mat-select>
+  </mat-form-field>
+
+  <!-- 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]="controls.Mw"
+        [parameter]="parameters()?.Mw"
+        placeholder="Select Magnitudes"
+        [selectControl]="controls?.MwMulti"
+        [selectOptions]="MwCommonValues"
+      />
     </div>
-
-    <!-- Source geometry -->
-    <div class="settings-subsection geometry-parameters">
-      <mat-label class="settings-subsection--label">Source Geometry</mat-label>
-
-      <div class="settings-subsection--section grid-row grid-gap-sm">
-        <!-- Source geometry: zTor input -->
-        <mat-form-field class="grid-col-4 ztor-input">
-          <mat-label>Z<sub>TOR</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.zTor.max"
-            [min]="(parameters$ | async)?.zTor.min"
-            [ngrxFormControlState]="(controls$ | async)?.zTor"
-            step="0.5"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.zTor.min }},
-            {{ (parameters$ | async)?.zTor.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-
-        <!-- Source geometry: Dip input -->
-        <mat-form-field class="grid-col-4 dip-input">
-          <mat-label>Dip (°)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.dip.max"
-            [min]="(parameters$ | async)?.dip.min"
-            [ngrxFormControlState]="(controls$ | async)?.dip"
-            step="0.5"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.dip.min }},
-            {{ (parameters$ | async)?.dip.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-
-        <!-- Source geometry: Width input -->
-        <mat-form-field class="grid-col-4 width-input">
-          <mat-label>Width (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.width.max"
-            [min]="(parameters$ | async)?.width.min"
-            [ngrxFormControlState]="(controls$ | async)?.width"
-            step="0.5"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.width.min }},
-            {{ (parameters$ | async)?.width.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-      </div>
+  </div>
+
+  <!-- Source geometry -->
+  <div class="settings-subsection geometry-parameters">
+    <mat-label class="settings-subsection--label">Source Geometry</mat-label>
+
+    <div class="settings-subsection--section grid-row grid-gap-sm">
+      <!-- Source geometry: zTor input -->
+      <mat-form-field class="grid-col-4 ztor-input">
+        <mat-label>Z<sub>TOR</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.zTor.max"
+          [min]="parameters()?.zTor.min"
+          [formControl]="controls.zTor"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.zTor.min }},
+          {{ parameters()?.zTor.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
+
+      <!-- Source geometry: Dip input -->
+      <mat-form-field class="grid-col-4 dip-input">
+        <mat-label>Dip (°)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.dip.max"
+          [min]="parameters()?.dip.min"
+          [formControl]="controls?.dip"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.dip.min }},
+          {{ parameters()?.dip.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
+
+      <!-- Source geometry: Width input -->
+      <mat-form-field class="grid-col-4 width-input">
+        <mat-label>Width (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.width.max"
+          [min]="parameters()?.width.min"
+          [formControl]="controls.width"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.width.min }},
+          {{ parameters()?.width.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
     </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]="(controls$ | async)?.vs30"
-          [parameter]="(parameters$ | async)?.vs30"
-          placeholder="Select values..."
-          [selectControl]="(controls$ | async)?.vs30Multi"
-          [selectOptions]="vs30CommonValues"
+  </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-no-ngrx-gmm-multi-select
+        class="grid-col-12 vs30-input"
+        label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)"
+        [multiple]="vs30Multiple$ | async"
+        [numberControl]="controls.vs30"
+        [parameter]="parameters()?.vs30"
+        placeholder="Select values..."
+        [selectControl]="controls?.vs30Multi"
+        [selectOptions]="vs30CommonValues"
+      />
+
+      <!-- Site & Basin: Z 1.0 input -->
+      <mat-form-field class="grid-col-4 z1p0-input">
+        <mat-label>Z<sub>1.0</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.z1p0.max"
+          [min]="parameters()?.z1p0.min"
+          [formControl]="controls.z1p0"
+          step="1.0"
+          type="number"
         />
-
-        <!-- Site & Basin: Z 1.0 input -->
-        <mat-form-field class="grid-col-4 z1p0-input">
-          <mat-label>Z<sub>1.0</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.z1p0.max"
-            [min]="(parameters$ | async)?.z1p0.min"
-            [ngrxFormControlState]="(controls$ | async)?.z1p0"
-            step="1.0"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.z1p0.min }},
-            {{ (parameters$ | async)?.z1p0.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-
-        <!-- Site & Basin: Z 2.5 input -->
-        <mat-form-field class="grid-col-4 z2p5-input">
-          <mat-label>Z<sub>2.5</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.z2p5.max"
-            [min]="(parameters$ | async)?.z2p5.min"
-            [ngrxFormControlState]="(controls$ | async)?.z2p5"
-            step="1.0"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.z2p5.min }},
-            {{ (parameters$ | async)?.z2p5.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-
-        <!-- Site & Basin: zSed input -->
-        <mat-form-field class="grid-col-4 zsed-input">
-          <mat-label>Z<sub>SED</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.zSed?.max"
-            [min]="(parameters$ | async)?.zSed?.min"
-            [ngrxFormControlState]="(controls$ | async)?.zSed"
-            step="1.0"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.zSed?.min }},
-            {{ (parameters$ | async)?.zSed?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-      </div>
+        <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()?.z2p5.max"
+          [min]="parameters()?.z2p5.min"
+          [formControl]="controls.z2p5"
+          step="1.0"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.z2p5.min }},
+          {{ parameters()?.z2p5.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
+
+      <!-- Site & Basin: zSed input -->
+      <mat-form-field class="grid-col-4 zsed-input">
+        <mat-label>Z<sub>SED</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.zSed?.max"
+          [min]="parameters()?.zSed?.min"
+          [formControl]="controls.zSed"
+          step="1.0"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.zSed?.min }},
+          {{ parameters()?.zSed?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
     </div>
+  </div>
 
-    <nshmp-lib-ng-gmm-plot-options-control-panel
-      [showEpistemicFormControl]="(controls$ | async)?.showEpistemicUncertainty"
-    />
+  <nshmp-lib-no-ngrx-gmm-plot-options-control-panel
+    [showEpistemicFormControl]="controls?.showEpistemicUncertainty"
+  />
 
-    <div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-    <nshmp-lib-ng-control-panel-buttons
-      [plotDisabled]="(form$ | async)?.isInvalid"
-      [serviceCallInfo]="serviceCallInfo$ | async"
-      [resetDisabled]="(form$ | async)?.isPristine"
-      (resetClick)="facade.resetControlPanel()"
-    />
-  </form>
-}
+  <nshmp-lib-no-ngrx-control-panel-buttons
+    [plotDisabled]="form.invalid"
+    [serviceCallInfo]="serviceCallInfo()"
+    [resetDisabled]="form.pristine"
+    (resetClick)="facade.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.spec.ts
index 854ada74403fb4242658a11fda9d456706ec975b..627b07dcb160c9b3bd6b195f653882f31b233172 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {ControlPanelComponent} from './control-panel.component';
 
@@ -11,7 +11,7 @@ describe('GmmDistanceControlPanelComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ControlPanelComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
index c52863ee4e4395abb8148cb4874f1e8152191513..e4718bf16a53f4239006f169dc495e5e05818e97 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
@@ -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 {GmmDistanceAppFacade} from '../../state/app.facade';
@@ -38,69 +39,104 @@ import {GmmDistanceAppFacade} 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 {
   /** List of common magnitudes */
   MwCommonValues = MW_COMMON_VALUES;
   /** List of common vs30 values */
   vs30CommonValues = VS30_COMMON_VALUES;
 
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Whether a GMM has been selected */
-  gmmSelected$ = this.controls$.pipe(
-    map(controls => unbox(controls.gmmSource.value).length > 0)
-  );
-
-  /** List magnitudes */
-  MwList$ = this.controls$.pipe(map(controls => unbox(controls.Mw.value)));
+  gmmSelected = this.controls.gmmSource.getRawValue()?.length > 0;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
 
   /** Service call info state */
-  serviceCallInfo$ = this.facade.serviceCallInfo$;
+  serviceCallInfo = this.facade.serviceCallInfo;
 
   /** Supported IMTs based on GMMs selected */
-  supportedImts$ = this.facade.supportedImts$;
+  supportedImts = this.facade.supportedImts;
 
   /** 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()
     )
   );
 
-  /** List of Vs30s */
-  vs30List$ = this.controls$.pipe(map(controls => unbox(controls.vs30.value)));
-
   /** 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()
     )
   );
 
+  private subs: Subscription[] = [];
+
   constructor(
     public facade: GmmDistanceAppFacade,
     private nshmpService: NshmpService
   ) {}
 
+  ngOnInit(): void {
+    this.subs.push(
+      this.controls.gmmSource.valueChanges.subscribe(() => {
+        this.onGmmSource();
+      })
+    );
+
+    this.subs.push(
+      this.controls.multiSelectableParam.valueChanges.subscribe(() =>
+        this.onMultiSelectableParam()
+      )
+    );
+
+    this.subs.push(
+      this.controls.showEpistemicUncertainty.valueChanges.subscribe(() =>
+        this.onShowEpistemicUncertainty()
+      )
+    );
+
+    this.subs.push(
+      combineLatest([
+        this.controls.dip.valueChanges,
+        this.controls.width.valueChanges,
+      ]).subscribe(() => this.updatePlots())
+    );
+
+    this.subs.push(
+      combineLatest([
+        this.controls.imt.valueChanges,
+        this.controls.Mw.valueChanges,
+        this.controls.MwMulti.valueChanges,
+        this.controls.vs30Multi.valueChanges,
+        this.controls.vs30.valueChanges,
+        this.controls.z1p0.valueChanges,
+        this.controls.z2p5.valueChanges,
+        this.controls.zSed.valueChanges,
+      ]).subscribe(() => this.facade.resetState())
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub?.unsubscribe());
+  }
+
   /**
    * On form submit.
    */
@@ -108,4 +144,62 @@ export class ControlPanelComponent {
     this.facade.callService();
     this.nshmpService.selectPlotControl();
   }
+
+  private onGmmSource() {
+    const formValues = this.form.getRawValue();
+
+    if (
+      !this.facade.state().usageResponse ||
+      formValues.gmmSource.length === 0
+    ) {
+      this.controls.imt.patchValue(gmmUtils.imtPlaceHolder().value);
+      this.facade.updateState({
+        supportedImts: [],
+      });
+      return;
+    }
+
+    const supportedImts = gmmUtils.getSupportedImts(
+      formValues.gmmSource,
+      this.facade.state().usageResponse
+    );
+
+    this.facade.updateState({
+      supportedImts,
+    });
+
+    const imt = [...supportedImts].shift();
+
+    this.controls.imt.patchValue(imt?.value);
+    this.facade.resetState();
+  }
+
+  private onMultiSelectableParam(): void {
+    if (!this.facade.state().usageResponse) {
+      return;
+    }
+
+    const {multiSelectableParam} = this.form.getRawValue();
+    const parameters = this.facade.state().usageResponse.response.parameters;
+
+    if (multiSelectableParam === GmmFormControlIds.MW) {
+      this.controls.gmmSource.setValue([]);
+      this.controls.Mw.setValue(parameters.Mw.value as number);
+      this.controls.MwMulti.setValue([]);
+    } else if (multiSelectableParam === GmmFormControlIds.VS30) {
+      this.controls.gmmSource.setValue([]);
+      this.controls.vs30.setValue(parameters.vs30.value as number);
+      this.controls.vs30Multi.setValue([]);
+    }
+
+    this.facade.resetState();
+  }
+
+  private onShowEpistemicUncertainty(): void {
+    this.facade.createPlots();
+  }
+
+  private updatePlots(): void {
+    this.facade.createPlots();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.html
index 68392622ea15a2f41a8d630f6b8ce64723457aef..d61b7f90198b22b9741be2c60ecffdcf8deca35c 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.html
@@ -1,98 +1,96 @@
-@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>
+      }
+    </mat-list>
+  </div>
 
-        @for (gmmSource of unbox(form?.value?.gmmSource); track gmmSource) {
-          <mat-list-item class="indent-list font-small">
-            {{ gmmSource.gmm.display }}
-          </mat-list-item>
-        }
-      </mat-list>
-    </div>
+  <div class="grid-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>
 
-    <div class="grid-col-12 tablet-lg:grid-col-3">
-      <mat-list class="parameter-list">
+      @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>
-          @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 }}
+      <mat-list-item>
+        <span class="parameter">Width</span>: {{ form?.value?.width }} km
+      </mat-list-item>
+    </mat-list>
+  </div>
+
+  <div class="grid-col-12 tablet-lg:grid-col-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>
-    </div>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>2.5</sub> </span>:
+        {{ form?.value?.z2p5 }}
+        @if (form?.value?.z2p5) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> Z<sub>SED</sub> </span>:
+        {{ form?.value?.zSed }}
+        @if (form?.value?.zSed) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+    </mat-list>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.spec.ts
index d8af63c07b5ed14a5dbba0e247a2b3467c4cbddb..b8d5719b6ff3e200fa21333de58a54d2850d2f8d 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.spec.ts
@@ -1,5 +1,5 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ParameterSummaryComponent} from './parameter-summary.component';
 
@@ -10,7 +10,7 @@ describe('ParameterSummaryComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [ParameterSummaryComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient()],
     }).compileComponents();
   });
 
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.ts
index 2914b9a18617f91202c633cc67538ba7bdcadd3f..1881477ff79c46a17dd1b1a752b53d687f0e72c8 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/parameter-summary/parameter-summary.component.ts
@@ -1,9 +1,7 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatList, MatListItem} from '@angular/material/list';
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs';
+import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 import {GmmDistanceAppFacade} from '../../state/app.facade';
 
@@ -23,16 +21,12 @@ export class ParameterSummaryComponent {
 
   /** Function to convert IMT id to display */
   imtIdToDisplay = gmmUtils.imtIdToDisplay;
-  /** ngrx-forms unbox function */
-  unbox = unbox;
 
   /** Control form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** List of GMMs */
-  gmms$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters?.gmm.values)
-  );
+  gmms = computed(() => this.facade.usage()?.response.parameters.gmm.values);
 
   constructor(private facade: GmmDistanceAppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
index 0365bce5017f3245062a9e96f5a624336d30ffee..38224cae3b366306a52fcf6938e7aeee72a4f42f 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.html
@@ -1,5 +1,5 @@
 <!-- Means -->
-<nshmp-lib-ng-plots-container>
+<nshmp-lib-no-ngrx-plots-container>
   <mat-accordion multi>
     <!-- GMM vs. distance plot -->
     <mat-expansion-panel expanded>
@@ -9,8 +9,8 @@
 
       <mat-divider />
 
-      @if (meanPlotData$ | async; as meanPlot) {
-        <nshmp-lib-ng-plot class="distance-plot" [plot]="meanPlot" />
+      @if (meanPlotData()) {
+        <nshmp-lib-no-ngrx-plot class="distance-plot" [plot]="meanPlotData()" />
       }
     </mat-expansion-panel>
 
@@ -31,7 +31,7 @@
       </mat-expansion-panel-header>
 
       <mat-divider />
-      <nshmp-lib-ng-app-metadata [repositories]="repositories$ | async" />
+      <nshmp-lib-no-ngrx-app-metadata [repositories]="repositories()" />
     </mat-expansion-panel>
   </mat-accordion>
-</nshmp-lib-ng-plots-container>
+</nshmp-lib-no-ngrx-plots-container>
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.spec.ts
index feb6ffa8a09b6027ef4e7b70bd4eebaa1a241155..609ca30e2ca89b371714ca905ee20dee5d96fff1 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.spec.ts
@@ -1,7 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {PlotsComponent} from './plots.component';
 
@@ -12,11 +11,7 @@ describe('GmmDistancePlotsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PlotsComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideHttpClient(),
-        provideNoopAnimations(),
-      ],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.ts
index 9e7f3561a1041df4c57acd444ac0b0d8b3897142..fdbf522bfc0ed2953b5ec7f2dcceedaafc94fccc 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/plots/plots.component.ts
@@ -1,5 +1,5 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatDivider} from '@angular/material/divider';
 import {
   MatAccordion,
@@ -7,12 +7,11 @@ import {
   MatExpansionPanelHeader,
   MatExpansionPanelTitle,
 } from '@angular/material/expansion';
-import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpLibNgPlotComponent,
   NshmpLibNgPlotsContainerComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {GmmDistanceAppFacade} from '../../state/app.facade';
 import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.component';
@@ -40,12 +39,10 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
 })
 export class PlotsComponent {
   /** GM vs distance plot data */
-  meanPlotData$ = this.facade.meanPlotState$.pipe(map(plot => plot.plotData));
+  meanPlotData = computed(() => this.facade.meanPlotState()?.plotData);
 
   /** Repo metadata */
-  repositories$ = this.facade.usage$.pipe(
-    map(usage => usage?.metadata.repositories)
-  );
+  repositories = computed(() => this.facade.usage()?.metadata.repositories);
 
-  constructor(private facade: GmmDistanceAppFacade) {}
+  constructor(public facade: GmmDistanceAppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
index 78c062689e6e382b131611aaabded0a85a839d5c..ef2792bbd9f39dcb6538eb2ef260ae12eec184f0 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.html
@@ -1,18 +1,18 @@
 <div class="height-full overflow-auto">
-  @if (meanPlot$ | async) {
-    <nshmp-lib-ng-plot-settings
+  @if (meanPlot()) {
+    <nshmp-lib-no-ngrx-plot-settings
       class="distance-settings"
-      [plot]="meanPlot$ | async"
+      [plot]="meanPlot()"
+      (updatedPlot)="updatePlot($event)"
     />
   }
 
   <div class="padding-y-3"></div>
 
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetPlotSettings()"
     [resetDisabled]="
-      (meanPlotSettings$ | async)?.isPristine &&
-      (meanPlotSettings$ | async)?.isUntouched
+      meanPlotSettings()?.pristine && meanPlotSettings()?.untouched
     "
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.spec.ts b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.spec.ts
index 8f687875e3c8ea2e913532f78a12e1e4b04c06d7..babda5391913c6f2da26ab4a2b0765cdaea05ec0 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {SettingsComponent} from './settings.component';
 
@@ -11,7 +11,7 @@ describe('GmmDistancePlotSettingsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [SettingsComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.ts b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.ts
index 526f961e9daff47ec42bdb055dfc012c5e981064..0cc061dc6c31500c8cc4b6a5f4405ed58304e079 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/settings/settings.component.ts
@@ -1,10 +1,11 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgPlotResetPlotSettingsComponent,
   NshmpLibNgPlotSettingsComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+  NshmpPlot,
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {GmmDistanceAppFacade} from '../../state/app.facade';
 
@@ -24,12 +25,18 @@ import {GmmDistanceAppFacade} from '../../state/app.facade';
 })
 export class SettingsComponent {
   /** Plot data */
-  meanPlot$ = this.facade.meanPlotState$;
+  meanPlot = this.facade.meanPlotState;
 
   /** Plot settings */
-  meanPlotSettings$ = this.facade.meanPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  meanPlotSettings = computed(() => this.facade.meanPlotState()?.settingsForm);
 
   constructor(public facade: GmmDistanceAppFacade) {}
+
+  updatePlot(plot: NshmpPlot): void {
+    const plots = new Map(this.facade.state().plots);
+    plots.set(gmmUtils.PlotType.MEANS, plot);
+    this.facade.updateState({
+      plots,
+    });
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/distance/models/form-controls.model.ts b/projects/nshmp-apps/src/app/gmm/distance/models/form-controls.model.ts
index c6831aaebcd0e39a11261714552aa1d76b245501..ca7134ec9de45178aef2793ff6c2266a83474057 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/models/form-controls.model.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/models/form-controls.model.ts
@@ -1,4 +1,7 @@
-import {GmmFormControlIds, GmmImtFormControls} from '@ghsc/nshmp-lib-ng/gmm';
+import {
+  GmmFormControlIds,
+  GmmImtFormControls,
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 /**
  * Control panel form fields.
diff --git a/projects/nshmp-apps/src/app/gmm/distance/state/app.actions.ts b/projects/nshmp-apps/src/app/gmm/distance/state/app.actions.ts
deleted file mode 100644
index ec9233dceba4ecb3566776da1d9a0b1bc5b086dc..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/distance/state/app.actions.ts
+++ /dev/null
@@ -1,22 +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';
-
-/**
- * GMM distance NGRX actions.
- */
-export const actions = createActionGroup({
-  events: {
-    ...sharedActions,
-    /** Action to set the initial form values from query or default */
-    'Initial Form Set': emptyProps(),
-    /** Service response action */
-    'Service Response': props<{serviceResponses: GmmDistanceResponse[]}>(),
-    /** Usage response action */
-    'Usage Response': props<{usageResponses: GmmDistanceUsage}>(),
-  },
-  source: 'GMM vs Distance',
-});
diff --git a/projects/nshmp-apps/src/app/gmm/distance/state/app.effects.ts b/projects/nshmp-apps/src/app/gmm/distance/state/app.effects.ts
deleted file mode 100644
index 8a5dfef38f17ce4241a0d96c028e9d1a1e8a8233..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/distance/state/app.effects.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import {Injectable} from '@angular/core';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpService, SpinnerService} from '@ghsc/nshmp-lib-ng/nshmp';
-import {plotUtils} from '@ghsc/nshmp-lib-ng/plot';
-import {
-  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 {defaultFormValues} from '../utils/app.default-values';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {distanceAppFeature} from './app.reducer';
-
-/**
- * NGRX application effects.
- */
-@Injectable()
-export class GmmDistanceAppEffects {
-  /** nshmp-ws base URL */
-  baseUrl = environment.webServices.data.url;
-  /** GMM service URL */
-  serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmDistance}`;
-
-  /**
-   * Call the services.
-   */
-  callService$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.callServices),
-      concatLatestFrom(() =>
-        this.store.select(distanceAppFeature.selectGmmDistanceAppState)
-      ),
-      exhaustMap(([, state]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
-
-        const urls = gmmUtils.serviceEndpoints(
-          this.serviceUrl,
-          state.controlForm.value,
-          state.controlForm.value.multiSelectableParam
-        );
-
-        return this.nshmpService.callServices$(urls).pipe(
-          mergeMap((serviceResponses: GmmDistanceResponse[]) => {
-            this.spinnerService.remove();
-            return [actions.serviceResponse({serviceResponses})];
-          }),
-          catchError((error: Error) => this.nshmpService.throwError$(error))
-        );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Transform the service responses to plot data.
-   */
-  createData$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.serviceResponse),
-      concatLatestFrom(() =>
-        this.store.select(distanceAppFeature.selectGmmDistanceAppState)
-      ),
-      map(([, state]) => {
-        const plots = plotUtils.updateAppPlotSettings(
-          serviceResponseToPlotData(state)
-        );
-        return actions.plots({plots});
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Initialize the application.
-   */
-  init$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.init),
-      concatLatestFrom(() =>
-        this.store.select(distanceAppFeature.selectControlForm)
-      ),
-      exhaustMap(([, form]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
-
-        return this.nshmpService
-          .callService$<GmmDistanceUsage>(this.serviceUrl)
-          .pipe(
-            mergeMap(usage => {
-              this.spinnerService.remove();
-              const parameters = usage.response.parameters;
-              const values = defaultFormValues(parameters);
-              const controls = form.controls;
-
-              const typedActions = gmmUtils.initialFormSetActions(
-                form,
-                values,
-                parameters
-              );
-              typedActions.push(actions.usageResponse({usageResponses: usage}));
-              gmmUtils.addFormAction(
-                parameters.rMax,
-                controls.rMax.id,
-                values.rMax,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.rMin,
-                controls.rMin.id,
-                values.rMin,
-                typedActions
-              );
-
-              return 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/gmm/distance/state/app.facade.ts b/projects/nshmp-apps/src/app/gmm/distance/state/app.facade.ts
index 2ba9dcc302aea6ae2996c5d7b651f37fe0bf8292..f13fbfb267f615410cb8633cadba92bf29aa12b5 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/state/app.facade.ts
@@ -1,18 +1,33 @@
-import {Injectable} from '@angular/core';
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {computed, Injectable, Signal, signal} from '@angular/core';
+import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {
+  FormGroupControls,
+  NshmpService,
+  ServiceCallInfo,
+  SpinnerService,
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   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 {environment} from 'projects/nshmp-apps/src/environments/environment';
+import {
+  redrawPlots,
+  resetPlotSettings,
+} from 'projects/nshmp-apps/src/shared/utils/facade.utils';
+import {catchError} from 'rxjs';
 
 import {FormControls} from '../models/form-controls.model';
-import {actions} from './app.actions';
-import {distanceAppFeature} from './app.reducer';
+import {
+  DEFAULT_FORM_VALUES,
+  defaultPlots,
+  usageFormValues,
+} from '../utils/app.default-values';
+import {serviceResponseToPlotData} from '../utils/response.handler';
+import {AppState, INITIAL_STATE} from './app.state';
 
 /**
  * Entrypoint to NGRX store for GM vs distance application.
@@ -21,76 +36,231 @@ import {distanceAppFeature} from './app.reducer';
   providedIn: 'root',
 })
 export class GmmDistanceAppFacade {
-  constructor(private store: Store) {}
+  /** nshmp-ws base URL */
+  private baseUrl = environment.webServices.data.url;
+  /** GMM service URL */
+  private serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmDistance}`;
 
-  /**
-   * Returns the control panel form.
-   */
-  get controlForm$(): Observable<FormGroupState<FormControls>> {
-    return this.store.pipe(select(distanceAppFeature.selectControlForm));
+  readonly formGroup = this.formBuilder.group({
+    ...DEFAULT_FORM_VALUES,
+    gmmSource: [],
+    MwMulti: [],
+    vs30Multi: [],
+  }) as FormGroupControls<FormControls>;
+  readonly state = signal<AppState>(INITIAL_STATE);
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private nshmpService: NshmpService,
+    private spinnerService: SpinnerService
+  ) {
+    this.addValidators(this.formGroup);
+    this.formGroup.controls.gmmSource.setValue([]);
+    this.formGroup.controls.showEpistemicUncertainty.disable();
   }
 
   /**
    * Returns the mean plot state.
    */
-  get meanPlotState$(): Observable<NshmpPlot> {
-    return this.store.pipe(select(distanceAppFeature.selectMeanPlot));
+  get meanPlotState(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.MEANS));
   }
 
-  get serviceCallInfo$(): Observable<ServiceCallInfo> {
-    return this.store.pipe(select(distanceAppFeature.selectServiceCallInfo));
+  get serviceCallInfo(): Signal<ServiceCallInfo> {
+    return computed(() => this.state().serviceCallInfo);
   }
 
   /**
    * Returns the Gmm distance service responses.
    */
-  get serviceResponse$(): Observable<GmmDistanceResponse[]> {
-    return this.store.pipe(select(distanceAppFeature.selectServiceResponses));
+  get serviceResponse(): Signal<GmmDistanceResponse[]> {
+    return computed(() => this.state().serviceResponses);
   }
 
-  get supportedImts$(): Observable<EnumParameterValues[]> {
-    return this.store.pipe(select(distanceAppFeature.selectSupportedImts));
+  get supportedImts(): Signal<EnumParameterValues[]> {
+    return computed(() => this.state().supportedImts);
   }
 
   /**
    * Returns the Gmm distance usage response.
    */
-  get usage$(): Observable<GmmDistanceUsage> {
-    return this.store.pipe(select(distanceAppFeature.selectUsageResponses));
+  get usage(): Signal<GmmDistanceUsage> {
+    return computed(() => this.state().usageResponse);
   }
 
   /**
    * Calls the Gmm distance service.
    */
   callService(): void {
-    this.store.dispatch(actions.callServices());
+    if (this.formGroup.invalid) {
+      return;
+    }
+
+    this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
+
+    const urls = gmmUtils.serviceEndpoints(
+      this.serviceUrl,
+      this.formGroup.getRawValue(),
+      this.formGroup.getRawValue().multiSelectableParam
+    );
+
+    this.nshmpService
+      .callServices$<GmmDistanceResponse>(urls)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(serviceResponses =>
+        this.handleServiceResponses(serviceResponses)
+      );
+  }
+
+  /**
+   * Create plots based on current state and form group.
+   */
+  createPlots(): void {
+    this.updateState({
+      plots: serviceResponseToPlotData(this.state(), this.formGroup),
+    });
   }
 
   /**
    * Initialize the application.
    */
   init(): void {
-    this.store.dispatch(actions.init());
+    this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
+
+    this.nshmpService
+      .callService$<GmmDistanceUsage>(this.serviceUrl)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
   /**
    * Redraw the plot.
    */
   plotRedraw(): void {
-    this.store.dispatch(actions.plotRedraw());
+    this.updateState({
+      plots: redrawPlots(this.state().plots),
+    });
   }
 
   /**
    * Reset the control panel.
    */
   resetControlPanel(): void {
-    this.store.dispatch(actions.resetControlPanel());
+    this.formGroup.reset(
+      usageFormValues(this.state().usageResponse.response.parameters)
+    );
   }
 
   /**
    * Reset the plot settings.
    */
   resetPlotSettings(): void {
-    this.store.dispatch(actions.resetSettings());
+    resetPlotSettings({
+      currentPlots: this.state().plots,
+      defaultPlots: defaultPlots(),
+    });
+  }
+
+  /**
+   * Reset the state.
+   */
+  resetState(): void {
+    const serviceCallInfo = gmmUtils.serviceCallInfo({
+      multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+      serviceName: this.state().serviceCallInfo.serviceName,
+      serviceResponses: this.state().serviceResponses,
+      serviceUrl: this.serviceUrl,
+      values: this.formGroup.getRawValue(),
+    });
+
+    this.updateState({
+      serviceCallInfo,
+      serviceResponses: null,
+    });
+
+    this.createPlots();
+  }
+
+  /**
+   * Update state.
+   *
+   * @param state Partial new state to update
+   */
+  updateState(state: Partial<AppState>): void {
+    this.state.set({
+      ...this.state(),
+      ...state,
+    });
+  }
+
+  /**
+   * Add validators to form controls.
+   *
+   * @param form The form group
+   */
+  private addValidators(form: FormGroupControls<FormControls>): void {
+    const required = (control: AbstractControl) => Validators.required(control);
+
+    form.controls.Mw.addValidators(required);
+    form.controls.dip.addValidators(required);
+    form.controls.gmmSource.addValidators(required);
+    form.controls.imt.addValidators(required);
+    form.controls.multiSelectableParam.addValidators(required);
+    form.controls.vs30.addValidators(required);
+    form.controls.width.addValidators(required);
+    form.controls.zTor.addValidators(required);
+
+    form.updateValueAndValidity();
+  }
+
+  private handleServiceResponses(
+    serviceResponses: GmmDistanceResponse[]
+  ): void {
+    const means = serviceResponses.map(s => s.response.means);
+    const sigmas = serviceResponses.map(s => s.response.sigmas);
+    const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
+
+    if (hasLogicTree) {
+      this.formGroup.controls.showEpistemicUncertainty.enable();
+    } else {
+      this.formGroup.controls.showEpistemicUncertainty.disable();
+    }
+
+    this.updateState({
+      serviceCallInfo: gmmUtils.serviceCallInfo({
+        multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+        serviceName: this.state().serviceCallInfo.serviceName,
+        serviceResponses,
+        serviceUrl: this.serviceUrl,
+        values: this.formGroup.getRawValue(),
+      }),
+      serviceResponses,
+    });
+
+    this.createPlots();
+
+    this.spinnerService.remove();
+  }
+
+  private handleUsageResponse(usageResponse: 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();
   }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/distance/state/app.reducer.ts b/projects/nshmp-apps/src/app/gmm/distance/state/app.reducer.ts
deleted file mode 100644
index 78d59977aca060470f404557084f4024761c5154..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/distance/state/app.reducer.ts
+++ /dev/null
@@ -1,281 +0,0 @@
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
-import {createFeature, createReducer, createSelector, on} from '@ngrx/store';
-import {
-  box,
-  createFormGroupState,
-  disable,
-  enable,
-  onNgrxForms,
-  onNgrxFormsAction,
-  setValue,
-  SetValueAction,
-  unbox,
-  updateGroup,
-  wrapReducerWithFormStateUpdate,
-} from 'ngrx-forms';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {
-  onPlotRedraw,
-  onPlotSettingsForm,
-  onResetSetting,
-} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {FormControls} from '../models/form-controls.model';
-import {
-  defaultFormValues,
-  defaultPlots,
-  MEAN_PLOT_SETTING_ID,
-} from '../utils/app.default-values';
-import {validateControlPanelForm} from '../utils/control-form.validators';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {AppState, CONTROL_FORM_ID, INITIAL_STATE} from './app.state';
-
-/** Form state key ids */
-const formKeys = {
-  control: {
-    gmmSource: `${CONTROL_FORM_ID}.${GmmFormControlIds.GMM_SOURCE}`,
-    multiSelectable: `${CONTROL_FORM_ID}.${GmmFormControlIds.MULTI_SELECTABLE_PARAM}`,
-    Mw: `${CONTROL_FORM_ID}.${GmmFormControlIds.MW}`,
-    showEpistemicUncertainty: `${CONTROL_FORM_ID}.${GmmFormControlIds.SHOW_EPISTEMIC_UNCERTAINTY}`,
-    vs30: `${CONTROL_FORM_ID}.${GmmFormControlIds.VS30}`,
-  },
-  settings: {
-    mean: MEAN_PLOT_SETTING_ID,
-  },
-};
-
-const baseUrl = environment.webServices.data.url;
-const endpoint = environment.webServices.data.services.gmmDistance;
-const serviceUrl = `${baseUrl}${endpoint}`;
-
-/**
- * GMM distance NGRX application feature.
- */
-export const distanceAppFeature = createFeature({
-  extraSelectors: ({selectPlots}) => ({
-    selectMeanPlot: createSelector(selectPlots, plots =>
-      plots.get(gmmUtils.PlotType.MEANS)
-    ),
-  }),
-  name: 'gmmDistanceApp',
-  reducer: createReducer(
-    /** Application intitial sate */
-    INITIAL_STATE,
-    /* On NGRX forms */
-    onNgrxForms(),
-    /* On form change */
-    onNgrxFormsAction(SetValueAction, (state, action) => {
-      const clearPlots = new Map<string, NshmpPlot>();
-
-      state.plots.forEach((plot, key) =>
-        clearPlots.set(key, {
-          ...plot,
-          plotData: {
-            ...plot.plotData,
-            data: [
-              ...plot.plotData.data.map(data => {
-                data = {...data};
-                data.x = [];
-                data.y = [];
-                return data;
-              }),
-            ],
-          },
-        })
-      );
-
-      const clearState: AppState = {
-        ...state,
-        controlForm: updateGroup<FormControls>({
-          showEpistemicUncertainty: control =>
-            disable(setValue(control, false)),
-        })(state.controlForm),
-        plots: clearPlots,
-        serviceResponses: null,
-      };
-
-      const serviceCallInfo = gmmUtils.serviceCallInfo({
-        multiSelectableParam: clearState.controlForm.value.multiSelectableParam,
-        serviceName: clearState.serviceCallInfo.serviceName,
-        serviceResponses: clearState.serviceResponses,
-        serviceUrl,
-        values: clearState.controlForm.value,
-      });
-
-      switch (action.controlId) {
-        case formKeys.control.multiSelectable: {
-          let form = {...clearState.controlForm};
-          const parameters =
-            clearState.usageResponses?.response?.parameters || null;
-
-          if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
-            form = updateGroup<FormControls>({
-              gmmSource: gmm => setValue(gmm, box([])),
-              Mw: Mw => setValue(Mw, (parameters.Mw.value as number) || null),
-              MwMulti: Mw => setValue(Mw, box([])),
-            })(clearState.controlForm);
-          } else if (
-            form.value.multiSelectableParam === GmmFormControlIds.VS30
-          ) {
-            form = updateGroup<FormControls>({
-              gmmSource: gmm => setValue(gmm, box([])),
-              vs30: vs30 =>
-                setValue(vs30, (parameters.vs30.value as number) || null),
-              vs30Multi: vs30 => setValue(vs30, box([])),
-            })(clearState.controlForm);
-          }
-
-          return {
-            ...clearState,
-            controlForm: form,
-            serviceCallInfo,
-          };
-        }
-        case formKeys.control.gmmSource: {
-          const gmm = unbox(clearState.controlForm.value.gmmSource);
-
-          if (
-            clearState.usageResponses &&
-            clearState.controlForm.controls.gmmSource &&
-            gmm.length > 0
-          ) {
-            const supportedImts = gmmUtils.getSupportedImts(
-              gmm,
-              clearState.usageResponses
-            );
-            const controlForm = gmmUtils.onGmmChange(
-              clearState.controlForm,
-              supportedImts
-            );
-            return {
-              ...clearState,
-              controlForm,
-              serviceCallInfo,
-              supportedImts,
-            };
-          }
-          return {
-            ...clearState,
-            serviceCallInfo,
-          };
-        }
-        case formKeys.control.showEpistemicUncertainty: {
-          return {
-            ...state,
-            plots: serviceResponseToPlotData(state),
-          };
-        }
-        default: {
-          if (action.controlId.includes(formKeys.settings.mean)) {
-            return onPlotSettingsForm(state, action);
-          } else {
-            return {
-              ...clearState,
-              serviceCallInfo,
-            };
-          }
-        }
-      }
-    }),
-    /* On init action */
-    on(actions.init, () => {
-      return {
-        ...INITIAL_STATE,
-      };
-    }),
-    /* On reset control panel action */
-    on(actions.resetControlPanel, state => {
-      const controlForm = createFormGroupState<FormControls>(
-        CONTROL_FORM_ID,
-        defaultFormValues(state.usageResponses.response.parameters)
-      );
-
-      state = {
-        ...state,
-        controlForm,
-        plots: INITIAL_STATE.plots,
-        serviceResponses: INITIAL_STATE.serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On reset plot settings action */
-    on(actions.resetSettings, state => {
-      return onResetSetting(state, defaultPlots());
-    }),
-    /* On plots action */
-    on(actions.plots, (state, {plots}) => {
-      return {
-        ...state,
-        plots,
-      };
-    }),
-    /* On plot redraw action */
-    on(actions.plotRedraw, state => {
-      return onPlotRedraw(state);
-    }),
-    /* On service call info action */
-    on(actions.serviceCallInfo, (state, {serviceCallInfo}) => {
-      return {
-        ...state,
-        serviceCallInfo: {
-          ...state.serviceCallInfo,
-          ...serviceCallInfo,
-        },
-      };
-    }),
-    /* On service response action */
-    on(actions.serviceResponse, (state, {serviceResponses}) => {
-      const response = serviceResponses;
-      const means = response.map(s => s.response.means);
-      const sigmas = response.map(s => s.response.sigmas);
-      const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
-
-      state = {
-        ...state,
-        controlForm: updateGroup<FormControls>({
-          showEpistemicUncertainty: hasLogicTree ? enable : disable,
-        })(state.controlForm),
-        serviceResponses: response,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On usage response action */
-    on(actions.usageResponse, (state, {usageResponses}) => {
-      return {
-        ...state,
-        usageResponses: usageResponses,
-      };
-    })
-  ),
-});
-
-/**
- * Application NGRX reducer with validators.
- */
-distanceAppFeature.reducer = wrapReducerWithFormStateUpdate(
-  distanceAppFeature.reducer,
-  state => state.controlForm,
-  validateControlPanelForm
-);
diff --git a/projects/nshmp-apps/src/app/gmm/distance/state/app.state.ts b/projects/nshmp-apps/src/app/gmm/distance/state/app.state.ts
index 4a63b64a752077e352f02a115a36ea7a376be290..da369801dcd72e293d51a8391a73da8688488bf3 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/state/app.state.ts
@@ -1,40 +1,18 @@
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   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 {
-  createFormGroupState,
-  disable,
-  FormGroupState,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {FormControls} from '../models/form-controls.model';
-import {DEFAULT_FORM_VALUES, defaultPlots} from '../utils/app.default-values';
-
-/** Control form id for ngrx-forms */
-export const CONTROL_FORM_ID = '[ngrx-forms] GMM Distance App Control Form';
 
-const formState = createFormGroupState<FormControls>(CONTROL_FORM_ID, {
-  ...DEFAULT_FORM_VALUES,
-});
-
-/**
- * Initial state for the control panel
- */
-export const INITIAL_CONTROL_FORM_STATE = updateGroup<FormControls>({
-  showEpistemicUncertainty: disable,
-})(formState);
+import {defaultPlots} from '../utils/app.default-values';
 
 /**
  * GMM distance app state
  */
-export interface AppState extends SharedAppState {
-  /** Control panel form field state */
-  controlForm: FormGroupState<FormControls>;
+export interface AppState {
+  plots: Map<string, NshmpPlot>;
   /** Service call info */
   serviceCallInfo: ServiceCallInfo;
   /** GMM service response */
@@ -42,21 +20,20 @@ export interface AppState extends SharedAppState {
   /** Supported IMTs */
   supportedImts: EnumParameterValues[];
   /** GMM usages */
-  usageResponses: GmmDistanceUsage;
+  usageResponse: GmmDistanceUsage;
 }
 
 /**
  * GMM distance app inital state
  */
 export const INITIAL_STATE: AppState = {
-  controlForm: INITIAL_CONTROL_FORM_STATE,
   plots: defaultPlots(),
   serviceCallInfo: {
     serviceCalls: [],
     serviceName: 'Ground Motion vs. Distance',
-    usage: null,
+    usage: [],
   },
   serviceResponses: [],
   supportedImts: [],
-  usageResponses: null,
+  usageResponse: null,
 };
diff --git a/projects/nshmp-apps/src/app/gmm/distance/utils/app.default-values.ts b/projects/nshmp-apps/src/app/gmm/distance/utils/app.default-values.ts
index 7f7d9916517fae38dc2f2207111baae615ab2322..ec3b7358f73aa37a2302e75c83d0dc2eb7f227ff 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/utils/app.default-values.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/utils/app.default-values.ts
@@ -1,10 +1,13 @@
-import {GmmSource, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot, NshmpPlotSettings, plotUtils} from '@ghsc/nshmp-lib-ng/plot';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {
+  NshmpPlot,
+  NshmpPlotSettings,
+  plotUtils,
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmDistanceUsageParameters,
   GmmGroupType,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {box, createFormGroupState} from 'ngrx-forms';
 
 import {FormControlIds, FormControls} from '../models/form-controls.model';
 
@@ -16,16 +19,16 @@ export const MEAN_PLOT_SETTING_ID = '[ngrx-forms] Means Plot Settings Form';
 export const DEFAULT_FORM_VALUES: FormControls = {
   dip: null,
   gmmGroupType: GmmGroupType.ACTIVE_CRUST,
-  gmmSource: box([] as GmmSource[]),
+  gmmSource: [],
   imt: 'default',
   multiSelectableParam: FormControlIds.GMM,
   Mw: null,
-  MwMulti: box([] as number[]),
+  MwMulti: [],
   rMax: 300,
   rMin: 0.1,
   showEpistemicUncertainty: false,
   vs30: null,
-  vs30Multi: box([] as number[]),
+  vs30Multi: [],
   width: null,
   z1p0: null,
   z2p5: null,
@@ -38,7 +41,7 @@ export const DEFAULT_FORM_VALUES: FormControls = {
  *
  * @param parameters The service parameters
  */
-export function defaultFormValues(
+export function usageFormValues(
   parameters: GmmDistanceUsageParameters
 ): FormControls {
   return {
@@ -61,12 +64,7 @@ export function defaultPlots(): Map<string, NshmpPlot> {
   plots.set(gmmUtils.PlotType.MEANS, {
     label: 'Means',
     plotData: meansPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      MEAN_PLOT_SETTING_ID,
-      {
-        ...meansPlotSettingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(meansPlotSettingsForm),
   });
 
   return new Map(plots);
diff --git a/projects/nshmp-apps/src/app/gmm/distance/utils/response.handler.ts b/projects/nshmp-apps/src/app/gmm/distance/utils/response.handler.ts
index 39927a713d2fc39534606b4e935a3331dd90ef96..622dc0df4ad8829c817d929ae42228ab5a890483 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/utils/response.handler.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/utils/response.handler.ts
@@ -1,9 +1,12 @@
-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 {FormControls} from '../models/form-controls.model';
 import {AppState} from '../state/app.state';
+import {defaultPlots} from './app.default-values';
 
 /**
  * Transform Gmm distance service responses to plots.
@@ -11,19 +14,28 @@ import {AppState} from '../state/app.state';
  * @param state The application state
  */
 export function serviceResponseToPlotData(
-  state: AppState
+  state: AppState,
+  form: FormGroupControls<FormControls>
 ): Map<string, NshmpPlot> {
-  if (state.serviceResponses.length === 0) {
-    return state.plots;
+  if (state.serviceResponses === null || state.serviceResponses?.length === 0) {
+    return defaultPlots();
   }
 
   const plots = new Map<string, NshmpPlot>();
+  const formValues = form.getRawValue();
   const meanPlot = state.plots.get(gmmUtils.PlotType.MEANS);
   const hoverTemplate = '%{x} km, %{y} AFE';
   const title = `Ground Motion vs. Distance: ${imtToString(
-    state.controlForm.value.imt as Imt
+    formValues.imt as Imt
   )}`;
-  const formValues = state.controlForm.value;
+
+  meanPlot.settingsForm.patchValue({
+    layout: {
+      title: {
+        text: title,
+      },
+    },
+  });
 
   const responses: gmmUtils.GmmResponse<XySequence, number[]>[] =
     state.serviceResponses.map(serviceResponse => ({
diff --git a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
index 15ed496220e7a1f09be025666021de63974f2a66..52138a14c49bbf2b5051944fdb2864fdffa313ed 100644
--- a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
+++ b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
@@ -2,8 +2,6 @@ import {Routes} from '@angular/router';
 import {provideEffects} from '@ngrx/effects';
 import {provideState} from '@ngrx/store';
 
-import {GmmDistanceAppEffects} from './distance/state/app.effects';
-import {distanceAppFeature} from './distance/state/app.reducer';
 import {MagnitudeAppEffects} from './magnitude/state/app.effects';
 import {magnitudeAppFeature} from './magnitude/state/app.reducer';
 import {SpectraAppEffects} from './spectra/state/app.effects';
@@ -15,10 +13,6 @@ const routes: Routes = [
     loadComponent: () =>
       import('./distance/app.component').then(com => com.AppComponent),
     path: 'distance',
-    providers: [
-      provideState(distanceAppFeature),
-      provideEffects(GmmDistanceAppEffects),
-    ],
   },
   {
     loadComponent: () =>