diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2b4c52c3561d8459c70b9b0400360eb77d58709b..eec4be44e1c1f6d0b17b069bfadd0da927d64adf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,6 @@ variables:
   BASE_HREF: nshmp
   GITLAB_TOKEN: '${CI_JOB_TOKEN}'
   IMAGE_NAME: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${ENVIRONMENT}-${CI_COMMIT_SHORT_SHA}
-  NODE_IMAGE: ${CODE_REGISTRY_IMAGE}/${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG}--node
   UPSTREAM_PATH: ghsc/nshmp/nshmp-apps
 
 image: ${CI_REGISTRY}/devops/images/usgs/node:20
@@ -75,10 +74,10 @@ default:
 # Docker in Docker
 ##
 .dind:
-  image: ${CI_REGISTRY}/devops/images/docker:20
+  image: ${CI_REGISTRY}/devops/images/usgs/docker:20
   services:
     - alias: docker
-      name: ${CI_REGISTRY}/devops/images/docker:20-dind
+      name: ${CI_REGISTRY}/devops/images/usgs/docker:20-dind
   variables:
     DOCKER_DRIVER: overlay2
 
@@ -150,26 +149,6 @@ Init:
     - npm ci
   stage: init
 
-Build Node Image:
-  image: ${CI_REGISTRY}/devops/images/docker:20
-  script:
-    - |
-      docker build \
-        --build-arg FROM_IMAGE=${CI_REGISTRY}/devops/images/usgs/node:20 \
-        --file ".gitlab/Dockerfile" \
-        --pull \
-        --tag ${NODE_IMAGE} \
-        .
-    - docker push ${NODE_IMAGE}
-  services:
-    - alias: docker
-      name: ${CI_REGISTRY}/devops/images/docker:20-dind
-  stage: init
-  tags:
-    - build
-  variables:
-    DOCKER_DRIVER: overlay2
-
 ####
 # Stage: Build
 ####
@@ -178,13 +157,10 @@ Build Project:
   artifacts:
     paths:
       - dist
-  before_script:
-    - git config --global --add safe.directory '*'
-  image: ${NODE_IMAGE}
   needs:
     - Init
-    - Build Node Image
   script:
+    - apt-get install -y git
     - npm run build:prod
   stage: build
 
diff --git a/.gitlab/Dockerfile b/.gitlab/Dockerfile
deleted file mode 100644
index c5a2d0ec80bd0eb443ebc53f3aa4a589b0e4f30c..0000000000000000000000000000000000000000
--- a/.gitlab/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-ARG FROM_IMAGE=code.usgs.gov:5001/devops/images/usgs/node:20
-
-FROM ${FROM_IMAGE}
-
-USER root
-
-RUN apt-get install -y git
-
-USER usgs-user
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 e4718bf16a53f4239006f169dc495e5e05818e97..de3142140756ae35c94db34eb28f1243eaf7a97d 100644
--- a/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/distance/components/control-panel/control-panel.component.ts
@@ -115,12 +115,6 @@ export class ControlPanelComponent implements OnInit, OnDestroy {
     this.subs.push(
       combineLatest([
         this.controls.dip.valueChanges,
-        this.controls.width.valueChanges,
-      ]).subscribe(() => this.updatePlots())
-    );
-
-    this.subs.push(
-      combineLatest([
         this.controls.imt.valueChanges,
         this.controls.Mw.valueChanges,
         this.controls.MwMulti.valueChanges,
@@ -129,6 +123,7 @@ export class ControlPanelComponent implements OnInit, OnDestroy {
         this.controls.z1p0.valueChanges,
         this.controls.z2p5.valueChanges,
         this.controls.zSed.valueChanges,
+        this.controls.width.valueChanges,
       ]).subscribe(() => this.facade.resetState())
     );
   }
diff --git a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
index 52138a14c49bbf2b5051944fdb2864fdffa313ed..b20f1a8722b2dd854e9bb902f0d708824a12ee25 100644
--- a/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
+++ b/projects/nshmp-apps/src/app/gmm/gmm.routes.ts
@@ -2,8 +2,6 @@ import {Routes} from '@angular/router';
 import {provideEffects} from '@ngrx/effects';
 import {provideState} from '@ngrx/store';
 
-import {MagnitudeAppEffects} from './magnitude/state/app.effects';
-import {magnitudeAppFeature} from './magnitude/state/app.reducer';
 import {SpectraAppEffects} from './spectra/state/app.effects';
 import {spectraAppFeature} from './spectra/state/app.reducer';
 
@@ -18,10 +16,6 @@ const routes: Routes = [
     loadComponent: () =>
       import('./magnitude/app.component').then(com => com.AppComponent),
     path: 'magnitude',
-    providers: [
-      provideState(magnitudeAppFeature),
-      provideEffects(MagnitudeAppEffects),
-    ],
   },
   {
     loadComponent: () =>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
index 3ce3d99ca6c33d655dadba43bc0af83b84cc962f..045a0871571f3b574e62239774a18219d187d0e7 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.html
@@ -10,15 +10,15 @@
   <mat-tab
     labelClass="medians-tab"
     label="Medians"
-    [disabled]="(hasData$ | async) === false"
+    [disabled]="hasData() === false"
   >
     <ng-template matTabContent>
-      <nshmp-lib-ng-export-data-table
-        [table]="meanTable$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="meanTable()"
         filename="gmm-magnitude-means.csv"
         buttonText="Export Means as CSV"
       />
-      <nshmp-lib-ng-data-table [table]="meanTable$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="meanTable()" />
     </ng-template>
   </mat-tab>
 
@@ -26,15 +26,15 @@
   <mat-tab
     labelClass="sigmas-tab"
     label="Sigmas"
-    [disabled]="(hasData$ | async) === false"
+    [disabled]="hasData() === false"
   >
     <ng-template matTabContent>
-      <nshmp-lib-ng-export-data-table
-        [table]="sigmaTable$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="sigmaTable()"
         filename="gmm-magnitude-sigmas.csv"
         buttonText="Export Sigmas as CSV"
       />
-      <nshmp-lib-ng-data-table [table]="sigmaTable$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="sigmaTable()" />
     </ng-template>
   </mat-tab>
 </mat-tab-group>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
index 4a22de1da0a6214e9c09b4c04a17d50bc64f8a15..3b3a0a2f34ce2579c60042f19a6cf790b5a04199 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.spec.ts
@@ -1,6 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ContentComponent} from './content.component';
 
@@ -11,10 +11,7 @@ describe('ContentComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ContentComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideNoopAnimations(),
-      ],
+      providers: [provideNoopAnimations(), provideHttpClient()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
index b67a804fd62fdb6b5a8d71f04bff36d50d3c73af..f8561f3c85b6be4dda5cdf774d90552e03a82889 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/content/content.component.ts
@@ -1,12 +1,11 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatTab, MatTabContent, MatTabGroup} from '@angular/material/tabs';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgDataTableComponent,
   NshmpLibNgExportDataTableComponent,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 
 import {AppFacade} from '../../state/app.facade';
 import {PlotsComponent} from '../plots/plots.component';
@@ -41,23 +40,29 @@ export class ContentComponent {
   sigmaExp = false;
 
   /** Whether service has been called and data exists */
-  hasData$ = this.facade.serviceResponse$.pipe(
-    map(responseSpectra => responseSpectra?.length > 0)
-  );
+  hasData = computed(() => this.facade.serviceResponse()?.length > 0);
 
   /** Table data for mean data */
-  meanTable$ = this.facade.meanPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(plot.plotData, this.medianExp, this.commonXValues)
-    )
-  );
+  meanTable = computed(() => {
+    const plot = this.facade.meanPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.medianExp,
+      this.commonXValues
+    );
+  });
 
   /** Table data for sigma table */
-  sigmaTable$ = this.facade.sigmaPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(plot.plotData, this.sigmaExp, this.commonXValues)
-    )
-  );
+  sigmaTable = computed(() => {
+    const plot = this.facade.sigmaPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.medianExp,
+      this.commonXValues
+    );
+  });
 
   constructor(public facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
index f9fb679e70e2a9740bc7d4b489983a159b5ef424..4fa3ff227cd27be54c0befba86f5b96f6e3349b3 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.html
@@ -1,70 +1,65 @@
 <!-- Ground Motion vs. Magnitude Conrol Panel -->
-@if (form$ | async) {
-  <form
-    class="settings-section control-panel height-full overflow-auto"
-    [ngrxFormState]="form$ | async"
-    (submit)="onSubmit()"
-    novalidate
-  >
-    <mat-form-field
-      class="grid-col-12 multi-select-parameter-menu padding-top-1"
-    >
-      <mat-label>Multi-Selectable Parameter</mat-label>
-      <mat-select
-        [ngrxFormControlState]="(controls$ | async)?.multiSelectableParam"
-      >
-        <mat-option value="gmm">Ground Motion Models</mat-option>
-        <mat-option value="vs30">V30</mat-option>
-      </mat-select>
-    </mat-form-field>
+<form
+  class="settings-section control-panel height-full overflow-auto"
+  [formGroup]="form"
+  (submit)="onSubmit()"
+  novalidate
+>
+  <mat-form-field class="grid-col-12 multi-select-parameter-menu padding-top-1">
+    <mat-label>Multi-Selectable Parameter</mat-label>
+    <mat-select [formControl]="controls.multiSelectableParam">
+      <mat-option value="gmm">Ground Motion Models</mat-option>
+      <mat-option value="vs30">V30</mat-option>
+    </mat-select>
+  </mat-form-field>
 
-    <!-- GMM menu -->
-    <nshmp-lib-ng-gmm-menu
-      [gmmControlState]="(controls$ | async)?.gmmSource"
-      [gmmGroupTypeState]="(controls$ | async)?.gmmGroupType"
-      [multiple]="(controls$ | async)?.multiSelectableParam.value === 'gmm'"
-      [parameters]="parameters$ | async"
+  <!-- GMM menu -->
+  @if (parameters()) {
+    <nshmp-lib-no-ngrx-gmm-menu
+      [gmmControl]="controls.gmmSource"
+      [gmmGroupTypeControl]="controls.gmmGroupType"
+      [multiple]="controls.multiSelectableParam.value === 'gmm'"
+      [parameters]="parameters()"
+      [imtControl]="controls.imt"
+      [supportedImts]="supportedImts()"
     />
+  }
 
-    <!-- IMT select menu -->
-    <mat-form-field class="grid-col-12 imt-select">
-      <mat-label>Intensity Measure Type</mat-label>
-      <mat-select
-        #imtSelectEl
-        [ngrxFormControlState]="(controls$ | async)?.imt"
-      >
-        @if ((gmmSelected$ | async) === false) {
-          <mat-option selected="true" value="default">
-            --- Choose a GMM ---
-          </mat-option>
-        }
-        @for (imt of supportedImts$ | async; track imt) {
-          <mat-option [value]="imt.value">
-            {{ imt.display }}
-          </mat-option>
-        }
-      </mat-select>
-    </mat-form-field>
+  <!-- IMT select menu -->
+  <mat-form-field class="grid-col-12 imt-select">
+    <mat-label>Intensity Measure Type</mat-label>
+    <mat-select #imtSelectEl [formControl]="controls.imt">
+      @if (gmmSelected === false) {
+        <mat-option selected="true" value="default">
+          --- Choose a GMM ---
+        </mat-option>
+      }
+      @for (imt of supportedImts(); track imt) {
+        <mat-option [value]="imt.value">
+          {{ imt.display }}
+        </mat-option>
+      }
+    </mat-select>
+  </mat-form-field>
 
-    <app-event-parameters />
+  <app-event-parameters />
 
-    <app-source-parameters />
+  <app-source-parameters />
 
-    <app-path-parameters />
+  <app-path-parameters />
 
-    <app-site-parameters />
+  <app-site-parameters />
 
-    <nshmp-lib-ng-gmm-plot-options-control-panel
-      [showEpistemicFormControl]="(controls$ | async)?.showEpistemicUncertainty"
-    />
+  <nshmp-lib-no-ngrx-gmm-plot-options-control-panel
+    [showEpistemicFormControl]="controls?.showEpistemicUncertainty"
+  />
 
-    <div class="padding-y-3"></div>
+  <div class="padding-y-3"></div>
 
-    <nshmp-lib-ng-control-panel-buttons
-      [plotDisabled]="(form$ | async)?.isInvalid"
-      [serviceCallInfo]="serviceCallInfo$ | async"
-      [resetDisabled]="(form$ | async)?.isPristine"
-      (resetClick)="facade.resetControlPanel()"
-    />
-  </form>
-}
+  <nshmp-lib-no-ngrx-control-panel-buttons
+    [plotDisabled]="form.invalid"
+    [serviceCallInfo]="serviceCallInfo()"
+    [resetDisabled]="form.pristine"
+    (resetClick)="facade.resetControlPanel()"
+  />
+</form>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
index dd4d4d08659740fce235ba99d0bc1cc299c66fad..524a65d032356a098ecd8f442e66aa0be076a395 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {ControlPanelComponent} from './control-panel.component';
 
@@ -11,7 +11,7 @@ describe('ControlPanelComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [ControlPanelComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
index 6f463e6b51bc4bd291caa49bd18cb7285aadead8..b83e99feeccf022a82f0c7570685cb0454ac4201 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/control-panel/control-panel.component.ts
@@ -1,21 +1,23 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatOption} from '@angular/material/core';
 import {MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatSelect} from '@angular/material/select';
 import {
+  GmmFormControlIds,
+  gmmUtils,
   NshmpLibNgGmmMenuComponent,
   NshmpLibNgGmmPlotOptionsControlPanelComponent,
-} from '@ghsc/nshmp-lib-ng/gmm';
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgControlPanelButtonsComponent,
-  NshmpNgrxFormsModule,
   NshmpService,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
+import {DEFAULT_FORM_VALUES} from '../../utils/app.default-values';
 import {EventParametersComponent} from '../event-parameters/event-parameters.component';
 import {PathParametersComponent} from '../path-parameters/path-parameters.component';
 import {SiteParametersComponent} from '../site-parameters/site-parameters.component';
@@ -38,41 +40,67 @@ import {SourceParametersComponent} from '../source-parameters/source-parameters.
     PathParametersComponent,
     SiteParametersComponent,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-control-panel',
   standalone: true,
   styleUrl: './control-panel.component.scss',
   templateUrl: './control-panel.component.html',
 })
-export class ControlPanelComponent {
+export class ControlPanelComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Whether a GMM has been selected */
-  gmmSelected$ = this.controls$.pipe(
-    map(controls => unbox(controls.gmmSource.value).length > 0)
-  );
+  gmmSelected = this.controls.gmmSource.getRawValue()?.length > 0;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
 
   /** Service call info state */
-  serviceCallInfo$ = this.facade.serviceCallInfo$;
+  serviceCallInfo = this.facade.serviceCallInfo;
 
   /** Supported IMTs based on GMMs selected */
-  supportedImts$ = this.facade.supportedImts$;
+  supportedImts = this.facade.supportedImts;
+
+  private subs: Subscription[] = [];
 
   constructor(
     public facade: AppFacade,
     private nshmpService: NshmpService
   ) {}
 
+  ngOnInit(): void {
+    this.subs.push(
+      this.controls.gmmSource.valueChanges.subscribe(() => {
+        this.onGmmSource();
+      })
+    );
+
+    this.subs.push(
+      this.controls.multiSelectableParam.valueChanges.subscribe(() =>
+        this.onMultiSelectableParam()
+      )
+    );
+
+    this.subs.push(
+      this.controls.showEpistemicUncertainty.valueChanges.subscribe(() =>
+        this.onShowEpistemicUncertainty()
+      )
+    );
+
+    this.subs.push(
+      this.controls.imt.valueChanges.subscribe(() => this.facade.resetState())
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub?.unsubscribe());
+  }
+
   /**
    * On form submit.
    */
@@ -80,4 +108,63 @@ 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.Mw.setValue(parameters.Mw.value as number);
+      this.controls.MwMulti.setValue([]);
+    } else if (multiSelectableParam === GmmFormControlIds.VS30) {
+      this.controls.vs30.setValue(parameters.vs30.value as number);
+      this.controls.vs30Multi.setValue([]);
+    }
+
+    this.controls.gmmSource.setValue([]);
+    this.controls.gmmSource.markAsPristine();
+    this.controls.imt.setValue(DEFAULT_FORM_VALUES.imt);
+    this.facade.resetState();
+  }
+
+  private onShowEpistemicUncertainty(): void {
+    this.facade.createPlots();
+  }
+
+  private updatePlots(): void {
+    this.facade.createPlots();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
index bab977bfdbfec61be3892c9b57fa8943fab23a2e..f0a3b452d1ec8ee71b36922526f704fded66357d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.html
@@ -1,64 +1,62 @@
 <!-- Event parameters -->
-@if (form$ | async) {
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Event Parameters</mat-label>
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Event parameters: mMin input -->
-      <mat-form-field class="grid-col-4 mmin-input">
-        <mat-label>Minimum Mw</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.mMin?.max"
-          [min]="(parameters$ | async)?.mMin?.min"
-          [ngrxFormControlState]="(controls$ | async)?.mMin"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.mMin?.min }},
-          {{ (parameters$ | async)?.mMin?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Event Parameters</mat-label>
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Event parameters: mMin input -->
+    <mat-form-field class="grid-col-4 mmin-input">
+      <mat-label>Minimum Mw</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.mMin?.max"
+        [min]="parameters()?.mMin?.min"
+        [formControl]="controls.mMin"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.mMin?.min }},
+        {{ parameters()?.mMin?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Event parameters: mMax input -->
-      <mat-form-field class="grid-col-4 mmax-input">
-        <mat-label>Maximum Mw</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.mMax?.max"
-          [min]="(parameters$ | async)?.mMax?.min"
-          [ngrxFormControlState]="(controls$ | async)?.mMax"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.mMax?.min }},
-          {{ (parameters$ | async)?.mMax?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Event parameters: mMax input -->
+    <mat-form-field class="grid-col-4 mmax-input">
+      <mat-label>Maximum Mw</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.mMax?.max"
+        [min]="parameters()?.mMax?.min"
+        [formControl]="controls.mMax"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.mMax?.min }},
+        {{ parameters()?.mMax?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Event parameters: Step input -->
-      <mat-form-field class="grid-col-4 mstep-input">
-        <mat-label>Mw Step</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.step?.max"
-          [min]="(parameters$ | async)?.step?.min"
-          [ngrxFormControlState]="(controls$ | async)?.step"
-          step="0.1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.step?.min }},
-          {{ (parameters$ | async)?.step?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Event parameters: Step input -->
+    <mat-form-field class="grid-col-4 mstep-input">
+      <mat-label>Mw Step</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.step?.max"
+        [min]="parameters()?.step?.min"
+        [formControl]="controls.step"
+        step="0.1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.step?.min }},
+        {{ parameters()?.step?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
index c46328c70608250311f475dd748fd7ecc2811806..ea17eb0a689678f814d7d89a0f36c0a858dc503e 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {EventParametersComponent} from './event-parameters.component';
 
@@ -10,7 +11,7 @@ describe('EventParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [EventParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
index b9494f380b2491a2df639ac023fb27b2718a3f30..ddff305604521d9796a5f477c7376296214595e9 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/event-parameters/event-parameters.component.ts
@@ -1,9 +1,8 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {combineLatest, Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -11,30 +10,35 @@ import {AppFacade} from '../../state/app.facade';
  * Control panel form fields for GMM event parameters.
  */
 @Component({
-  imports: [
-    MatLabel,
-    MatFormField,
-    MatInput,
-    MatError,
-    AsyncPipe,
-    NshmpNgrxFormsModule,
-  ],
+  imports: [MatLabel, MatFormField, MatInput, MatError, ReactiveFormsModule],
   selector: 'app-event-parameters',
   standalone: true,
   styleUrl: './event-parameters.component.scss',
   templateUrl: './event-parameters.component.html',
 })
-export class EventParametersComponent {
+export class EventParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private valueSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.valueSubscription = combineLatest([
+      this.controls.mMax.valueChanges,
+      this.controls.mMin.valueChanges,
+      this.controls.step.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
index f0044425a8853c03dd8b56e38ccfb9780f527e2d..06e1589fbac83e95c2d39ec59c7075d4e20550ed 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.html
@@ -1,97 +1,94 @@
-@if (form$ | async; as form) {
-  <div class="grid-row parameter-summary">
-    <div class="grid-col-12 tablet-lg:grid-col-6">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter">Ground Motion Models</span>:
-        </mat-list-item>
-
-        @for (gmmSource of unbox(form?.value?.gmmSource); track gmmSource) {
-          <mat-list-item class="indent-list font-small">
-            {{ gmmSource.gmm.display }}
-          </mat-list-item>
-        }
-      </mat-list>
-    </div>
+<div class="grid-row parameter-summary">
+  <div class="grid-col-12 tablet-lg:grid-col-6">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">Ground Motion Models</span>:
+      </mat-list-item>
 
-    <div class="grid-col-12 tablet-lg:grid-col-4">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter">IMT</span>:
-          {{ imtIdToDisplay(form?.value?.imt) }}
+      @for (gmmSource of form?.value?.gmmSource; track gmmSource) {
+        <mat-list-item class="indent-list font-small">
+          {{ gmmSource.gmm.display }}
         </mat-list-item>
+      }
+    </mat-list>
+  </div>
 
-        <mat-list-item>
-          <span class="parameter">Mw Range</span>: [{{ form?.value?.mMin }},
-          {{ form?.value?.mMax }}]
-        </mat-list-item>
+  <div class="grid-col-12 tablet-lg:grid-col-4">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">IMT</span>:
+        {{ imtIdToDisplay(form?.value?.imt) }}
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Mw Step</span>:
-          {{ form?.value?.step }}
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Mw Range</span>: [{{ form?.value?.mMin }},
+        {{ form?.value?.mMax }}]
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>TOR</sub> </span>:
-          {{ form?.value?.zTor }} km
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Mw Step</span>:
+        {{ form?.value?.step }}
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Dip</span>: {{ form?.value?.dip }}°
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>TOR</sub> </span>:
+        {{ form?.value?.zTor }} km
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter">Width</span>: {{ form?.value?.width }} km
-        </mat-list-item>
-      </mat-list>
-    </div>
+      <mat-list-item>
+        <span class="parameter">Dip</span>: {{ form?.value?.dip }}°
+      </mat-list-item>
 
-    <div class="grid-col-12 tablet-lg:grid-col-2">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter"> Distance</span>:
-          {{ form?.value?.distance }} km
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter">Width</span>: {{ form?.value?.width }} km
+      </mat-list-item>
+    </mat-list>
+  </div>
+
+  <div class="grid-col-12 tablet-lg:grid-col-2">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter"> Distance</span>: {{ form?.value?.distance }} km
+      </mat-list-item>
 
-        @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-          <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item>
-          @for (vs30 of unbox(form?.value?.vs30Multi); track vs30) {
-            <mat-list-item class="indent-list">
-              {{ vs30 }}
-              <sup>&nbsp;m</sup>/<sub>s</sub>
-            </mat-list-item>
-          }
-        } @else {
-          <mat-list-item>
-            <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }}
+      @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
+        <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item>
+        @for (vs30 of form?.value?.vs30Multi; track vs30) {
+          <mat-list-item class="indent-list">
+            {{ vs30 }}
             <sup>&nbsp;m</sup>/<sub>s</sub>
           </mat-list-item>
         }
-
+      } @else {
         <mat-list-item>
-          <span class="parameter"> Z<sub>1.0</sub> </span>:
-          {{ form?.value?.z1p0 }}
-          @if (form?.value?.z1p0) {
-            <span>&nbsp;km</span>
-          }
+          <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }}
+          <sup>&nbsp;m</sup>/<sub>s</sub>
         </mat-list-item>
+      }
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>2.5</sub> </span>:
-          {{ form?.value?.z2p5 }}
-          @if (form?.value?.z2p5) {
-            <span>&nbsp;km</span>
-          }
-        </mat-list-item>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>1.0</sub> </span>:
+        {{ form?.value?.z1p0 }}
+        @if (form?.value?.z1p0) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
 
-        <mat-list-item>
-          <span class="parameter"> Z<sub>SED</sub> </span>:
-          {{ form?.value?.zSed }}
-          @if (form?.value?.zSed) {
-            <span>&nbsp;km</span>
-          }
-        </mat-list-item>
-      </mat-list>
-    </div>
+      <mat-list-item>
+        <span class="parameter"> Z<sub>2.5</sub> </span>:
+        {{ form?.value?.z2p5 }}
+        @if (form?.value?.z2p5) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> Z<sub>SED</sub> </span>:
+        {{ form?.value?.zSed }}
+        @if (form?.value?.zSed) {
+          <span>&nbsp;km</span>
+        }
+      </mat-list-item>
+    </mat-list>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
index d8af63c07b5ed14a5dbba0e247a2b3467c4cbddb..b8d5719b6ff3e200fa21333de58a54d2850d2f8d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.spec.ts
@@ -1,5 +1,5 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {ParameterSummaryComponent} from './parameter-summary.component';
 
@@ -10,7 +10,7 @@ describe('ParameterSummaryComponent', () => {
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [ParameterSummaryComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient()],
     }).compileComponents();
   });
 
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
index 166e75ceceae0c498c77a8ad634b2e22b42325ea..ea07924b428e1863c7b6cdd4cf9d5a05fae85831 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/parameter-summary/parameter-summary.component.ts
@@ -1,9 +1,7 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatList, MatListItem} from '@angular/material/list';
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs';
+import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -23,16 +21,12 @@ export class ParameterSummaryComponent {
 
   /** Function to convert IMT id to display */
   imtIdToDisplay = gmmUtils.imtIdToDisplay;
-  /** ngrx-forms unbox function */
-  unbox = unbox;
 
   /** Control form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** List of GMMs */
-  gmms$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters?.gmm.values)
-  );
+  gmms = computed(() => this.facade.usage()?.response?.parameters.gmm.values);
 
   constructor(private facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
index ca22b23d105a152befdc099d6e9597d043ee2f5a..b397645ef70dc9314de785f5ba03610739904e63 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.html
@@ -1,26 +1,24 @@
-@if (form$ | async) {
-  <!-- Path Parameters -->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Path Parameters</mat-label>
-    <!-- Event parameters: Distance input -->
-    <div class="settings-subsection--section distance-input">
-      <mat-form-field class="grid-col-12">
-        <mat-label>Distance (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.distance?.max"
-          [min]="(parameters$ | async)?.distance?.min"
-          [ngrxFormControlState]="(controls$ | async)?.distance"
-          step="1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.distance?.min }},
-          {{ (parameters$ | async)?.distance?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+<!-- Path Parameters -->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Path Parameters</mat-label>
+  <!-- Event parameters: Distance input -->
+  <div class="settings-subsection--section distance-input">
+    <mat-form-field class="grid-col-12">
+      <mat-label>Distance (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.distance?.max"
+        [min]="parameters()?.distance?.min"
+        [formControl]="controls.distance"
+        step="1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.distance?.min }},
+        {{ parameters()?.distance?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
index 874eb64c82124cde0e87d45faeceb06c756c4d58..7a9ef8898e4616a208025c0a7b78750fdbdd1e7f 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {PathParametersComponent} from './path-parameters.component';
 
@@ -10,7 +11,7 @@ describe('PathParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PathParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
index 4608ac6235ac66079b0d68c2d3fb5a7f4188362c..1ca9f5cfd27f93367d1324bb5b8b8ecf4030745d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/path-parameters/path-parameters.component.ts
@@ -1,9 +1,8 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -11,30 +10,33 @@ import {AppFacade} from '../../state/app.facade';
  * Control panel form fields for GMM path parameters.
  */
 @Component({
-  imports: [
-    MatLabel,
-    MatFormField,
-    MatInput,
-    MatError,
-    AsyncPipe,
-    NshmpNgrxFormsModule,
-  ],
+  imports: [MatLabel, MatFormField, MatInput, MatError, ReactiveFormsModule],
   selector: 'app-path-parameters',
   standalone: true,
   styleUrl: './path-parameters.component.scss',
   templateUrl: './path-parameters.component.html',
 })
-export class PathParametersComponent {
+export class PathParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private distanceSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.distanceSubscription = this.controls.distance.valueChanges.subscribe(
+      () => this.facade.resetState()
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.distanceSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
index 82d44ff0709bf39249fc1ac1be72792be4b05911..a7d0427a776548881a2f0df780f2f0be1c27b75f 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.html
@@ -1,27 +1,26 @@
 <div class="height-full overflow-auto">
-  @if (meanPlot$ | async) {
-    <nshmp-lib-ng-plot-settings
-      [plot]="meanPlot$ | async"
+  @if (meanPlot()) {
+    <nshmp-lib-no-ngrx-plot-settings
+      [plot]="meanPlot()"
+      (updatedPlot)="updatePlot(PlotIds.MEANS, $event)"
       class="mean-settings"
     />
   }
 
-  @if (sigmaPlot$ | async) {
-    <nshmp-lib-ng-plot-settings
-      [plot]="sigmaPlot$ | async"
+  @if (sigmaPlot()) {
+    <nshmp-lib-no-ngrx-plot-settings
+      [plot]="sigmaPlot()"
+      (updatedPlot)="updatePlot(PlotIds.SIGMA, $event)"
       class="sigma-settings"
     />
   }
 
   <div class="padding-y-2"></div>
 
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetPlotSettings()"
     [resetDisabled]="
-      (meanPlotSettings$ | async)?.isPristine &&
-      (meanPlotSettings$ | async)?.isUntouched &&
-      (sigmaPlotSettings$ | async)?.isPristine &&
-      (sigmaPlotSettings$ | async)?.isPristine
+      meanPlotSettings().pristine && sigmaPlotSettings().pristine
     "
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
index 2d559563d711b2f8b34ab3202efbb2e9cc544a14..f32cb499109bcd69ddd134227f47a21b493974ff 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.spec.ts
@@ -1,6 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {PlotsSettingsComponent} from './plots-settings.component';
 
@@ -11,7 +11,7 @@ describe('PlotsSettingsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PlotsSettingsComponent],
-      providers: [provideMockStore({initialState: {}}), provideHttpClient()],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
index 483f71ff8d498a43f6cc19719beac385400ba3df..3e6c0e88dab3cec56d839e6b05f176df8ae579a3 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots-settings/plots-settings.component.ts
@@ -1,10 +1,11 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgPlotResetPlotSettingsComponent,
   NshmpLibNgPlotSettingsComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+  NshmpPlot,
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -23,19 +24,27 @@ import {AppFacade} from '../../state/app.facade';
   templateUrl: './plots-settings.component.html',
 })
 export class PlotsSettingsComponent {
+  PlotIds = gmmUtils.PlotType;
+
   /** Mean plot state */
-  meanPlot$ = this.facade.meanPlotState$;
+  meanPlot = this.facade.meanPlotState;
+
   /** Mean plot settings */
-  meanPlotSettings$ = this.facade.meanPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  meanPlotSettings = computed(() => this.facade.meanPlotState().settingsForm);
 
   /** Sigma plot state */
-  sigmaPlot$ = this.facade.sigmaPlotState$;
+  sigmaPlot = this.facade.sigmaPlotState;
+
   /** Sigma plot settings */
-  sigmaPlotSettings$ = this.facade.sigmaPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  sigmaPlotSettings = computed(() => this.facade.sigmaPlotState().settingsForm);
 
   constructor(public facade: AppFacade) {}
+
+  updatePlot(id: string, plot: NshmpPlot): void {
+    const plots = new Map(this.facade.state().plots);
+    plots.set(id, plot);
+    this.facade.updateState({
+      plots,
+    });
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
index a18cbd37e9a366a325a41d0d9464c4f1e6e9d0cd..dac5eeac17ff78a30a11412a67f7b5235cc163f5 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.html
@@ -1,4 +1,4 @@
-<nshmp-lib-ng-plots-container>
+<nshmp-lib-no-ngrx-plots-container>
   <mat-accordion multi>
     <!-- GMM vs. magnitude plot -->
     <mat-expansion-panel expanded>
@@ -8,8 +8,8 @@
 
       <mat-divider />
 
-      @if (meanPlotData$ | async; as meanPlot) {
-        <nshmp-lib-ng-plot class="mean-plot" [plot]="meanPlot" />
+      @if (meanPlotData()) {
+        <nshmp-lib-no-ngrx-plot class="mean-plot" [plot]="meanPlotData()" />
       }
     </mat-expansion-panel>
 
@@ -21,8 +21,8 @@
 
       <mat-divider />
 
-      @if (sigmaPlotData$ | async; as sigmaPlot) {
-        <nshmp-lib-ng-plot class="sigma-plot" [plot]="sigmaPlot" />
+      @if (sigmaPlotData()) {
+        <nshmp-lib-no-ngrx-plot class="sigma-plot" [plot]="sigmaPlotData()" />
       }
     </mat-expansion-panel>
 
@@ -43,7 +43,7 @@
       </mat-expansion-panel-header>
 
       <mat-divider />
-      <nshmp-lib-ng-app-metadata [repositories]="repositories$ | async" />
+      <nshmp-lib-no-ngrx-app-metadata [repositories]="repositories()" />
     </mat-expansion-panel>
   </mat-accordion>
-</nshmp-lib-ng-plots-container>
+</nshmp-lib-no-ngrx-plots-container>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
index 611faa48c7030c6b191cef494fb6f9d4cf68a2be..12310e664cae4389f25ed6e690712a017a78e588 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.spec.ts
@@ -1,7 +1,6 @@
 import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
 import {provideNoopAnimations} from '@angular/platform-browser/animations';
-import {provideMockStore} from '@ngrx/store/testing';
 
 import {PlotsComponent} from './plots.component';
 
@@ -12,11 +11,7 @@ describe('PlotsComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [PlotsComponent],
-      providers: [
-        provideMockStore({initialState: {}}),
-        provideHttpClient(),
-        provideNoopAnimations(),
-      ],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts
index 7800906fbbd0c73035cb8254f874d92377559c42..8088793cdbb287844f31bf1e7c411aadc354e93d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/plots/plots.component.ts
@@ -1,5 +1,5 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed} from '@angular/core';
 import {MatDivider} from '@angular/material/divider';
 import {
   MatAccordion,
@@ -7,12 +7,11 @@ import {
   MatExpansionPanelHeader,
   MatExpansionPanelTitle,
 } from '@angular/material/expansion';
-import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpLibNgPlotComponent,
   NshmpLibNgPlotsContainerComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 import {AppFacade} from '../../state/app.facade';
 import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.component';
@@ -40,15 +39,13 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
 })
 export class PlotsComponent {
   /** Mean plot data */
-  meanPlotData$ = this.facade.meanPlotState$.pipe(map(plot => plot.plotData));
+  meanPlotData = computed(() => this.facade.meanPlotState().plotData);
 
   /** Sigma plot data */
-  sigmaPlotData$ = this.facade.sigmaPlotState$.pipe(map(plot => plot.plotData));
+  sigmaPlotData = computed(() => this.facade.sigmaPlotState().plotData);
 
   /** Repo metadata */
-  repositories$ = this.facade.usage$.pipe(
-    map(usage => usage?.metadata.repositories)
-  );
+  repositories = computed(() => this.facade.usage()?.metadata.repositories);
 
   constructor(private facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
index 782f79c8b99c07894cfa9596fee933828e0140be..6389406f39813c3b775970ac2b124970011e72d4 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.html
@@ -1,77 +1,75 @@
-@if (form$ | async) {
-  <!-- Site & Basin-->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Site & Basin</mat-label>
+<!-- Site & Basin-->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Site & Basin</mat-label>
 
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Site & Basin: Vs30 input -->
-      <nshmp-lib-ng-gmm-multi-select
-        class="grid-col-12 vs30-input"
-        label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)"
-        [multiple]="vs30Multiple$ | async"
-        [numberControl]="(controls$ | async)?.vs30"
-        [parameter]="(parameters$ | async)?.vs30"
-        placeholder="Select values..."
-        [selectControl]="(controls$ | async)?.vs30Multi"
-        [selectOptions]="vs30CommonValues"
-      />
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Site & Basin: Vs30 input -->
+    <nshmp-lib-no-ngrx-gmm-multi-select
+      class="grid-col-12 vs30-input"
+      label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)"
+      [multiple]="vs30Multiple$ | async"
+      [numberControl]="controls?.vs30"
+      [parameter]="parameters()?.vs30"
+      placeholder="Select values..."
+      [selectControl]="controls.vs30Multi"
+      [selectOptions]="vs30CommonValues"
+    />
 
-      <!-- Site & Basin: Z 1.0 input -->
-      <mat-form-field class="grid-col-4 z1p0-input">
-        <mat-label>Z<sub>1.0</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.z1p0?.max"
-          [min]="(parameters$ | async)?.z1p0?.min"
-          [ngrxFormControlState]="(controls$ | async)?.z1p0"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z1p0?.min }},
-          {{ (parameters$ | async)?.z1p0?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 1.0 input -->
+    <mat-form-field class="grid-col-4 z1p0-input">
+      <mat-label>Z<sub>1.0</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z1p0?.max"
+        [min]="parameters()?.z1p0?.min"
+        [formControl]="controls.z1p0"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.z1p0?.min }},
+        {{ parameters()?.z1p0?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Site & Basin: Z 2.5 input -->
-      <mat-form-field class="grid-col-4 z2p5-input">
-        <mat-label>Z<sub>2.5</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.z2p5?.max"
-          [min]="(parameters$ | async)?.z2p5?.min"
-          [ngrxFormControlState]="(controls$ | async)?.z2p5"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z2p5?.min }},
-          {{ (parameters$ | async)?.z2p5?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 2.5 input -->
+    <mat-form-field class="grid-col-4 z2p5-input">
+      <mat-label>Z<sub>2.5</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z2p5?.max"
+        [min]="parameters()?.z2p5?.min"
+        [formControl]="controls?.z2p5"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.z2p5?.min }},
+        {{ parameters()?.z2p5?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Site & Basin: zSed input -->
-      <mat-form-field class="grid-col-4 zsed-input">
-        <mat-label>Z<sub>SED</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.zSed?.max"
-          [min]="(parameters$ | async)?.zSed?.min"
-          [ngrxFormControlState]="(controls$ | async)?.zSed"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.zSed?.min }},
-          {{ (parameters$ | async)?.zSed?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Site & Basin: zSed input -->
+    <mat-form-field class="grid-col-4 zsed-input">
+      <mat-label>Z<sub>SED</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.zSed?.max"
+        [min]="parameters()?.zSed?.min"
+        [formControl]="controls.zSed"
+        step="1.0"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.zSed?.min }},
+        {{ parameters()?.zSed?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
index b7857664be688118f1bb5b350b8121ab47340f6c..e6ed2c402ac46bc7ed17e1650a6ffa423c4b635d 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {SiteParametersComponent} from './site-parameters.component';
 
@@ -10,7 +11,7 @@ describe('SiteParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [SiteParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
index f71aeb052dd64d16982788beecf487002a972ada..bc789827acc0462dba39e2f6451d85cbc873fcbd 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/site-parameters/site-parameters.component.ts
@@ -1,13 +1,14 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
 import {
   GmmFormControlIds,
   NshmpLibNgGmmMultiSelectComponent,
   VS30_COMMON_VALUES,
-} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {combineLatest, Subscription} from 'rxjs';
 import {map} from 'rxjs/operators';
 
 import {AppFacade} from '../../state/app.facade';
@@ -23,36 +24,49 @@ import {AppFacade} from '../../state/app.facade';
     MatInput,
     MatError,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-site-parameters',
   standalone: true,
   styleUrl: './site-parameters.component.scss',
   templateUrl: './site-parameters.component.html',
 })
-export class SiteParametersComponent {
+export class SiteParametersComponent implements OnInit, OnDestroy {
   /** Common vs30 values */
   vs30CommonValues = VS30_COMMON_VALUES;
 
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
 
-  /** Whether vs30 is multi-selectable */
-  vs30Multiple$ = this.controls$.pipe(
+  /** Whether Vs30 is multi-selectable */
+  vs30Multiple$ = this.controls.multiSelectableParam.valueChanges.pipe(
     map(
-      controls =>
-        controls.multiSelectableParam.value ===
-        GmmFormControlIds.VS30.toString()
+      multiSelectableParam =>
+        multiSelectableParam.toString() === GmmFormControlIds.VS30.toString()
     )
   );
 
+  private valueSubscription = new Subscription();
+
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    combineLatest([
+      this.controls.vs30Multi.valueChanges,
+      this.controls.vs30.valueChanges,
+      this.controls.z1p0.valueChanges,
+      this.controls.z2p5.valueChanges,
+      this.controls.zSed.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
index d767694472687cb9f8d06c650f6c964714ed1ec6..b391b897e4ce79eac16cedb33bfa2bf4b7cec663 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.html
@@ -1,65 +1,63 @@
-@if (form$ | async) {
-  <!-- Source Parameters -->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Source Geometry</mat-label>
+<!-- Source Parameters -->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Source Geometry</mat-label>
 
-    <div class="settings-subsection--section grid-row grid-gap-sm">
-      <!-- Source Parameters: zTor input -->
-      <mat-form-field class="grid-col-4 ztor-input">
-        <mat-label>Z<sub>TOR</sub> (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.zTor?.max"
-          [min]="(parameters$ | async)?.zTor?.min"
-          [ngrxFormControlState]="(controls$ | async)?.zTor"
-          step="0.5"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.zTor?.min }},
-          {{ (parameters$ | async)?.zTor?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+  <div class="settings-subsection--section grid-row grid-gap-sm">
+    <!-- Source Parameters: zTor input -->
+    <mat-form-field class="grid-col-4 ztor-input">
+      <mat-label>Z<sub>TOR</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.zTor?.max"
+        [min]="parameters()?.zTor?.min"
+        [formControl]="controls.zTor"
+        step="0.5"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.zTor?.min }},
+        {{ parameters()?.zTor?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Source Parameters: Dip input -->
-      <mat-form-field class="grid-col-4 dip-input">
-        <mat-label>Dip (°)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.dip?.max"
-          [min]="(parameters$ | async)?.dip?.min"
-          [ngrxFormControlState]="(controls$ | async)?.dip"
-          step="5"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.dip?.min }},
-          {{ (parameters$ | async)?.dip?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Source Parameters: Dip input -->
+    <mat-form-field class="grid-col-4 dip-input">
+      <mat-label>Dip (°)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.dip?.max"
+        [min]="parameters()?.dip?.min"
+        [formControl]="controls.dip"
+        step="5"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.dip?.min }},
+        {{ parameters()?.dip?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
 
-      <!-- Source Parameters: Width input -->
-      <mat-form-field class="grid-col-4 width-input">
-        <mat-label>Width (km)</mat-label>
-        <input
-          matInput
-          [max]="(parameters$ | async)?.width?.max"
-          [min]="(parameters$ | async)?.width?.min"
-          [ngrxFormControlState]="(controls$ | async)?.width"
-          step="1"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.width?.min }},
-          {{ (parameters$ | async)?.width?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Source Parameters: Width input -->
+    <mat-form-field class="grid-col-4 width-input">
+      <mat-label>Width (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.width?.max"
+        [min]="parameters()?.width?.min"
+        [formControl]="controls.width"
+        step="1"
+        type="number"
+      />
+      <mat-error>
+        [
+        {{ parameters()?.width?.min }},
+        {{ parameters()?.width?.max }}
+        ]
+      </mat-error>
+    </mat-form-field>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
index a3e89bc207355e1bd993b467882611c74970cacd..a2e4133e63cdba16d3520d9dd1cbfff868290f23 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.spec.ts
@@ -1,5 +1,6 @@
+import {provideHttpClient} from '@angular/common/http';
 import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
-import {provideMockStore} from '@ngrx/store/testing';
+import {provideNoopAnimations} from '@angular/platform-browser/animations';
 
 import {SourceParametersComponent} from './source-parameters.component';
 
@@ -10,7 +11,7 @@ describe('SourceParametersComponent', () => {
   beforeEach(waitForAsync(() => {
     TestBed.configureTestingModule({
       imports: [SourceParametersComponent],
-      providers: [provideMockStore({initialState: {}})],
+      providers: [provideHttpClient(), provideNoopAnimations()],
       teardown: {destroyAfterEach: false},
     }).compileComponents();
   }));
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
index 4a4c59310d766e4d773efd8a389085b842d7e310..8ce5cdeadaecc5702046f6e4c87cf416946558f5 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/components/source-parameters/source-parameters.component.ts
@@ -1,9 +1,9 @@
 import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {MatError, MatFormField, MatLabel} from '@angular/material/form-field';
 import {MatInput} from '@angular/material/input';
-import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp';
-import {map} from 'rxjs/operators';
+import {combineLatest, Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 
@@ -17,24 +17,36 @@ import {AppFacade} from '../../state/app.facade';
     MatInput,
     MatError,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-source-parameters',
   standalone: true,
   styleUrl: './source-parameters.component.scss',
   templateUrl: './source-parameters.component.html',
 })
-export class SourceParametersComponent {
+export class SourceParametersComponent implements OnInit, OnDestroy {
   /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
+  controls = this.facade.formGroup.controls;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private valueSubscription = new Subscription();
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    combineLatest([
+      this.controls.dip.valueChanges,
+      this.controls.width.valueChanges,
+      this.controls.zTor.valueChanges,
+    ]).subscribe(() => this.facade.resetState());
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts b/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
index ea75d6a0f7c73c73ba92a7c4147cb65d4da1e77b..2d1c266e740de116767d97028e18b93b38d75666 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/models/gmm-magnitude-form-controls.model.ts
@@ -1,4 +1,7 @@
-import {GmmFormControlIds, GmmImtFormControls} from '@ghsc/nshmp-lib-ng/gmm';
+import {
+  GmmFormControlIds,
+  GmmImtFormControls,
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 /**
  * Control panel form fields.
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts
deleted file mode 100644
index 26382a0d252a3e5f5ba35c4996c5580c998929bd..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.actions.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import {
-  GmmMagnitudeResponse,
-  GmmMagnitudeUsage,
-} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {createActionGroup, emptyProps, props} from '@ngrx/store';
-import {sharedActions} from 'projects/nshmp-apps/src/shared/state/shared';
-
-/**
- * GMM vs magnitude NGRX actions.
- */
-export const actions = createActionGroup({
-  events: {
-    ...sharedActions,
-    /** Action to set the initial form values from query or default */
-    'Initial Form Set': emptyProps(),
-    /** Service response action */
-    'Service Response': props<{serviceResponses: GmmMagnitudeResponse[]}>(),
-    /** Usage response action */
-    'Usage Response': props<{usageResponses: GmmMagnitudeUsage}>(),
-  },
-  source: 'GMM vs Magnitude',
-});
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts
deleted file mode 100644
index a45bc5824bb9371129e31ebe3fac7e6ba1ae5f13..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.effects.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import {Injectable} from '@angular/core';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpService, SpinnerService} from '@ghsc/nshmp-lib-ng/nshmp';
-import {plotUtils} from '@ghsc/nshmp-lib-ng/plot';
-import {
-  GmmMagnitudeResponse,
-  GmmMagnitudeUsage,
-} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {Actions, createEffect, ofType} from '@ngrx/effects';
-import {concatLatestFrom} from '@ngrx/operators';
-import {Store} from '@ngrx/store';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {catchError, exhaustMap, map, mergeMap} from 'rxjs/operators';
-
-import {defaultFormValues} from '../utils/app.default-values';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {AppFacade} from './app.facade';
-import {magnitudeAppFeature} from './app.reducer';
-
-/**
- * NGRX application effects.
- */
-@Injectable()
-export class MagnitudeAppEffects {
-  /** nshmp-ws base URL */
-  baseUrl = environment.webServices.data.url;
-  /** GMM service URL */
-  serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmMagnitude}`;
-
-  /**
-   * Call the services.
-   */
-  callService$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.callServices),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectGmmMagnitudeAppState)
-      ),
-      exhaustMap(([, state]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
-
-        const urls = gmmUtils.serviceEndpoints(
-          this.serviceUrl,
-          state.controlForm.value,
-          state.controlForm.value.multiSelectableParam
-        );
-
-        return this.nshmpService.callServices$(urls).pipe(
-          mergeMap((serviceResponses: GmmMagnitudeResponse[]) => {
-            return [actions.serviceResponse({serviceResponses})];
-          }),
-          catchError((error: Error) => this.nshmpService.throwError$(error))
-        );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Transform the service responses to plot data.
-   */
-  createData$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.serviceResponse),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectGmmMagnitudeAppState)
-      ),
-      map(([, state]) => {
-        const plots = plotUtils.updateAppPlotSettings(
-          serviceResponseToPlotData(state)
-        );
-        this.spinnerService.remove();
-        return actions.plots({plots});
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  /**
-   * Initialize the application.
-   */
-  init$ = createEffect(() =>
-    this.actions$.pipe(
-      ofType(actions.init),
-      concatLatestFrom(() =>
-        this.store.select(magnitudeAppFeature.selectControlForm)
-      ),
-      exhaustMap(([, form]) => {
-        this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
-        return this.nshmpService
-          .callService$<GmmMagnitudeUsage>(this.serviceUrl)
-          .pipe(
-            mergeMap(usage => {
-              const parameters = usage.response.parameters;
-              const controls = form.controls;
-              const values = defaultFormValues(parameters);
-
-              const typedActions = gmmUtils.initialFormSetActions(
-                form,
-                values,
-                parameters
-              );
-              typedActions.push(actions.usageResponse({usageResponses: usage}));
-              gmmUtils.addFormAction(
-                parameters.distance,
-                controls.distance.id,
-                values.distance,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.mMax,
-                controls.mMax.id,
-                values.mMax,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.mMin,
-                controls.mMin.id,
-                values.mMin,
-                typedActions
-              );
-              gmmUtils.addFormAction(
-                parameters.step,
-                controls.step.id,
-                values.step,
-                typedActions
-              );
-
-              this.spinnerService.remove();
-              return typedActions;
-            }),
-            catchError((error: Error) => this.nshmpService.throwError$(error))
-          );
-      }),
-      catchError((error: Error) => this.nshmpService.throwError$(error))
-    )
-  );
-
-  constructor(
-    private actions$: Actions,
-    private facade: AppFacade,
-    private nshmpService: NshmpService,
-    private spinnerService: SpinnerService,
-    private store: Store
-  ) {}
-}
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
index 0e09fe9ab9909d954da32b3d527aafc037d8baec..28c3ff154c3c57dbd2799cb87b03f3e4f9207d80 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.facade.ts
@@ -1,19 +1,33 @@
-import {Injectable} from '@angular/core';
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {computed, Injectable, Signal, signal} from '@angular/core';
+import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {
+  FormGroupControls,
+  NshmpService,
+  ServiceCallInfo,
+  SpinnerService,
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmMagnitudeResponse,
   GmmMagnitudeUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {select, Store} from '@ngrx/store';
-import {FormGroupState} from 'ngrx-forms';
 import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {Observable} from 'rxjs';
+import {
+  redrawPlots,
+  resetPlotSettings,
+} from 'projects/nshmp-apps/src/shared/utils/facade.utils';
+import {catchError} from 'rxjs';
 
 import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {actions} from './app.actions';
-import {magnitudeAppFeature} from './app.reducer';
+import {
+  DEFAULT_FORM_VALUES,
+  defaultPlots,
+  usageFormValues,
+} from '../utils/app.default-values';
+import {serviceResponseToPlotData} from '../utils/response.handler';
+import {AppState, INITIAL_STATE} from './app.state';
 
 /**
  * Entrypoint to NGRX store for GM vs magnitude application.
@@ -27,89 +41,242 @@ export class AppFacade {
   /** GMM service URL */
   serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmMagnitude}`;
 
-  constructor(private store: Store) {}
+  readonly formGroup = this.formBuilder.group({
+    ...DEFAULT_FORM_VALUES,
+    gmmSource: [],
+    MwMulti: [],
+    vs30Multi: [],
+  }) as FormGroupControls<GmmMagnitudeFormControls>;
 
-  /**
-   * Returns the control panel form.
-   */
-  get controlForm$(): Observable<FormGroupState<GmmMagnitudeFormControls>> {
-    return this.store.pipe(select(magnitudeAppFeature.selectControlForm));
+  readonly state = signal<AppState>(INITIAL_STATE);
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private nshmpService: NshmpService,
+    private spinnerService: SpinnerService
+  ) {
+    this.addValidators(this.formGroup);
+    this.formGroup.controls.gmmSource.setValue([]);
+    this.formGroup.controls.showEpistemicUncertainty.disable();
   }
 
   /**
    * Returns the mean plot state.
    */
-  get meanPlotState$(): Observable<NshmpPlot> {
-    return this.store.pipe(select(magnitudeAppFeature.selectMeanPlot));
+  get meanPlotState(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.MEANS));
   }
 
   /**
    * Returns service call info observable.
    */
-  get serviceCallInfo$(): Observable<ServiceCallInfo> {
-    return this.store.pipe(select(magnitudeAppFeature.selectServiceCallInfo));
+  get serviceCallInfo(): Signal<ServiceCallInfo> {
+    return computed(() => this.state().serviceCallInfo);
   }
 
   /**
    * Returns the Gmm distance service responses.
    */
-  get serviceResponse$(): Observable<GmmMagnitudeResponse[]> {
-    return this.store.pipe(select(magnitudeAppFeature.selectServiceResponses));
+  get serviceResponse(): Signal<GmmMagnitudeResponse[]> {
+    return computed(() => this.state().serviceResponses);
   }
 
   /**
    * Returns the mean plot state.
    */
-  get sigmaPlotState$(): Observable<NshmpPlot> {
-    return this.store.pipe(select(magnitudeAppFeature.selectSigmaPlot));
+  get sigmaPlotState(): Signal<NshmpPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.SIGMA));
   }
 
   /**
    * Returns supported IMTs observable.
    */
-  get supportedImts$(): Observable<EnumParameterValues[]> {
-    return this.store.pipe(select(magnitudeAppFeature.selectSupportedImts));
+  get supportedImts(): Signal<EnumParameterValues[]> {
+    return computed(() => this.state().supportedImts);
   }
 
   /**
    * Returns the Gmm distance usage response.
    */
-  get usage$(): Observable<GmmMagnitudeUsage> {
-    return this.store.pipe(select(magnitudeAppFeature.selectUsageResponses));
+  get usage(): Signal<GmmMagnitudeUsage> {
+    return computed(() => this.state().usageResponse);
   }
 
   /**
    * Calls the Gmm distance service.
    */
   callService(): void {
-    this.store.dispatch(actions.callServices());
+    if (this.formGroup.invalid) {
+      return;
+    }
+
+    this.spinnerService.show(SpinnerService.MESSAGE_SERVICE);
+
+    const urls = gmmUtils.serviceEndpoints(
+      this.serviceUrl,
+      this.formGroup.getRawValue(),
+      this.formGroup.getRawValue().multiSelectableParam
+    );
+
+    this.nshmpService
+      .callServices$<GmmMagnitudeResponse>(urls)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(serviceResponses =>
+        this.handleServiceResponses(serviceResponses)
+      );
+  }
+
+  /**
+   * Create plots based on current state and form group.
+   */
+  createPlots(): void {
+    this.updateState({
+      plots: serviceResponseToPlotData(this.state(), this.formGroup),
+    });
   }
 
   /**
    * Initialize the application.
    */
   init(): void {
-    this.store.dispatch(actions.init());
+    this.spinnerService.show(SpinnerService.MESSAGE_METADATA);
+
+    this.nshmpService
+      .callService$<GmmMagnitudeUsage>(this.serviceUrl)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
   /**
    * Redraw the plot.
    */
   plotRedraw(): void {
-    this.store.dispatch(actions.plotRedraw());
+    this.updateState({
+      plots: redrawPlots(this.state().plots),
+    });
   }
 
   /**
    * Reset the control panel.
    */
   resetControlPanel(): void {
-    this.store.dispatch(actions.resetControlPanel());
+    this.formGroup.reset(
+      usageFormValues(this.state().usageResponse.response.parameters)
+    );
   }
 
   /**
    * Reset the plot settings.
    */
   resetPlotSettings(): void {
-    this.store.dispatch(actions.resetSettings());
+    resetPlotSettings({
+      currentPlots: this.state().plots,
+      defaultPlots: defaultPlots(),
+    });
+  }
+
+  /**
+   * Reset the state.
+   */
+  resetState(): void {
+    const serviceCallInfo = gmmUtils.serviceCallInfo({
+      multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+      serviceName: this.state().serviceCallInfo.serviceName,
+      serviceResponses: this.state().serviceResponses,
+      serviceUrl: this.serviceUrl,
+      values: this.formGroup.getRawValue(),
+    });
+
+    this.updateState({
+      serviceCallInfo,
+      serviceResponses: null,
+    });
+
+    this.createPlots();
+  }
+
+  /**
+   * Update state.
+   *
+   * @param state Partial new state to update
+   */
+  updateState(state: Partial<AppState>): void {
+    this.state.set({
+      ...this.state(),
+      ...state,
+    });
+  }
+
+  /**
+   * Add validators to form controls.
+   *
+   * @param form The form group
+   */
+  private addValidators(
+    form: FormGroupControls<GmmMagnitudeFormControls>
+  ): void {
+    const required = (control: AbstractControl) => Validators.required(control);
+
+    form.controls.Mw.addValidators(required);
+    form.controls.dip.addValidators(required);
+    form.controls.gmmSource.addValidators(required);
+    form.controls.imt.addValidators(required);
+    form.controls.multiSelectableParam.addValidators(required);
+    form.controls.vs30.addValidators(required);
+    form.controls.width.addValidators(required);
+    form.controls.zTor.addValidators(required);
+
+    form.updateValueAndValidity();
+  }
+
+  private handleServiceResponses(
+    serviceResponses: GmmMagnitudeResponse[]
+  ): void {
+    const means = serviceResponses.map(s => s.response.means);
+    const sigmas = serviceResponses.map(s => s.response.sigmas);
+    const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
+
+    if (hasLogicTree) {
+      this.formGroup.controls.showEpistemicUncertainty.enable();
+    } else {
+      this.formGroup.controls.showEpistemicUncertainty.disable();
+    }
+
+    this.updateState({
+      serviceCallInfo: gmmUtils.serviceCallInfo({
+        multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+        serviceName: this.state().serviceCallInfo.serviceName,
+        serviceResponses,
+        serviceUrl: this.serviceUrl,
+        values: this.formGroup.getRawValue(),
+      }),
+      serviceResponses,
+    });
+
+    this.createPlots();
+
+    this.spinnerService.remove();
+  }
+
+  private handleUsageResponse(usageResponse: GmmMagnitudeUsage): void {
+    this.spinnerService.remove();
+    const parameters = usageResponse.response.parameters;
+    const values = usageFormValues(parameters);
+
+    this.formGroup.patchValue({
+      ...values,
+    });
+
+    const serviceCallInfo: ServiceCallInfo = {
+      ...this.state().serviceCallInfo,
+      usage: [this.serviceUrl],
+    };
+
+    this.updateState({
+      serviceCallInfo,
+      usageResponse,
+    });
+
+    this.spinnerService.remove();
   }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts
deleted file mode 100644
index 137ee7d8a54edd50ec5a48e1dac39a64d2d90215..0000000000000000000000000000000000000000
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.reducer.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
-import {createFeature, createReducer, createSelector, on} from '@ngrx/store';
-import {
-  box,
-  createFormGroupState,
-  disable,
-  enable,
-  onNgrxForms,
-  onNgrxFormsAction,
-  setValue,
-  SetValueAction,
-  unbox,
-  updateGroup,
-  wrapReducerWithFormStateUpdate,
-} from 'ngrx-forms';
-import {environment} from 'projects/nshmp-apps/src/environments/environment';
-import {
-  onPlotRedraw,
-  onPlotSettingsForm,
-  onResetSetting,
-} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {
-  defaultFormValues,
-  defaultPlots,
-  MEAN_PLOT_SETTING_ID,
-  SIGMA_PLOT_SETTING_ID,
-} from '../utils/app.default-values';
-import {validateControlPanelForm} from '../utils/control-panel.validators';
-import {serviceResponseToPlotData} from '../utils/response.handler';
-import {actions} from './app.actions';
-import {AppState, CONTROL_FORM_ID, INITIAL_STATE} from './app.state';
-
-/** Form state key ids */
-const formKeys = {
-  control: {
-    gmmSource: `${CONTROL_FORM_ID}.${GmmFormControlIds.GMM_SOURCE}`,
-    multiSelectable: `${CONTROL_FORM_ID}.${GmmFormControlIds.MULTI_SELECTABLE_PARAM}`,
-    Mw: `${CONTROL_FORM_ID}.${GmmFormControlIds.MW}`,
-    showEpistemicUncertainty: `${CONTROL_FORM_ID}.${GmmFormControlIds.SHOW_EPISTEMIC_UNCERTAINTY}`,
-    vs30: `${CONTROL_FORM_ID}.${GmmFormControlIds.VS30}`,
-  },
-  settings: {
-    mean: MEAN_PLOT_SETTING_ID,
-    sigma: SIGMA_PLOT_SETTING_ID,
-  },
-};
-
-const baseUrl = environment.webServices.data.url;
-const endpoint = environment.webServices.data.services.gmmMagnitude;
-const serviceUrl = `${baseUrl}${endpoint}`;
-
-/**
- * NGRX application raw reducer.
- */
-export const magnitudeAppFeature = createFeature({
-  extraSelectors: ({selectPlots}) => ({
-    selectMeanPlot: createSelector(selectPlots, plots =>
-      plots.get(gmmUtils.PlotType.MEANS)
-    ),
-    selectSigmaPlot: createSelector(selectPlots, plots =>
-      plots.get(gmmUtils.PlotType.SIGMA)
-    ),
-  }),
-  name: 'gmmMagnitudeApp',
-  reducer: createReducer(
-    /** Application intitial sate */
-    INITIAL_STATE,
-    /* On NGRX forms */
-    onNgrxForms(),
-    /* On form change */
-    onNgrxFormsAction(SetValueAction, (state, action) => {
-      const clearPlots = new Map<string, NshmpPlot>();
-
-      state.plots.forEach((plot, key) =>
-        clearPlots.set(key, {
-          ...plot,
-          plotData: {
-            ...plot.plotData,
-            data: [
-              ...plot.plotData.data.map(data => {
-                data = {...data};
-                data.x = [];
-                data.y = [];
-                return data;
-              }),
-            ],
-          },
-        })
-      );
-
-      const clearState: AppState = {
-        ...state,
-        controlForm: updateGroup<GmmMagnitudeFormControls>({
-          showEpistemicUncertainty: control =>
-            disable(setValue(control, false)),
-        })(state.controlForm),
-        plots: clearPlots,
-        serviceResponses: null,
-      };
-
-      switch (action.controlId) {
-        case formKeys.control.multiSelectable: {
-          let form = {...clearState.controlForm};
-          const parameters =
-            clearState.usageResponses?.response?.parameters || null;
-
-          if (form.value.multiSelectableParam === GmmFormControlIds.VS30) {
-            form = updateGroup<GmmMagnitudeFormControls>({
-              gmmSource: gmm => setValue(gmm, box([])),
-              vs30: vs30 =>
-                setValue(vs30, (parameters.vs30.value as number) || null),
-              vs30Multi: vs30 => setValue(vs30, box([])),
-            })(clearState.controlForm);
-          }
-
-          return {
-            ...clearState,
-            controlForm: form,
-            serviceCallInfo: gmmUtils.serviceCallInfo({
-              multiSelectableParam: form.value.multiSelectableParam,
-              serviceName: clearState.serviceCallInfo.serviceName,
-              serviceResponses: clearState.serviceResponses,
-              serviceUrl,
-              values: form.value,
-            }),
-          };
-        }
-        case formKeys.control.gmmSource: {
-          const gmm = unbox(clearState.controlForm.value.gmmSource);
-          const serviceCallInfo = gmmUtils.serviceCallInfo({
-            multiSelectableParam:
-              clearState.controlForm.value.multiSelectableParam,
-            serviceName: clearState.serviceCallInfo.serviceName,
-            serviceResponses: clearState.serviceResponses,
-            serviceUrl,
-            values: clearState.controlForm.value,
-          });
-
-          if (
-            clearState.usageResponses &&
-            clearState.controlForm.controls.gmmSource &&
-            gmm.length > 0
-          ) {
-            const supportedImts = gmmUtils.getSupportedImts(
-              gmm,
-              clearState.usageResponses
-            );
-
-            const controlForm = gmmUtils.onGmmChange(
-              clearState.controlForm,
-              supportedImts
-            );
-
-            return {
-              ...clearState,
-              controlForm,
-              serviceCallInfo,
-              supportedImts,
-            };
-          }
-          return {
-            ...clearState,
-            serviceCallInfo,
-          };
-        }
-        case formKeys.control.showEpistemicUncertainty: {
-          return {
-            ...state,
-            plots: serviceResponseToPlotData(state),
-          };
-        }
-        default: {
-          if (
-            action.controlId.includes(formKeys.settings.mean) ||
-            action.controlId.includes(formKeys.settings.sigma)
-          ) {
-            return onPlotSettingsForm(state, action);
-          } else {
-            return {
-              ...clearState,
-              serviceCallInfo: gmmUtils.serviceCallInfo({
-                multiSelectableParam:
-                  clearState.controlForm.value.multiSelectableParam,
-                serviceName: clearState.serviceCallInfo.serviceName,
-                serviceResponses: clearState.serviceResponses,
-                serviceUrl,
-                values: clearState.controlForm.value,
-              }),
-            };
-          }
-        }
-      }
-    }),
-    /* On init action */
-    on(actions.init, () => {
-      return {
-        ...INITIAL_STATE,
-      };
-    }),
-    /* On reset control panel action */
-    on(actions.resetControlPanel, state => {
-      const controlForm = createFormGroupState<GmmMagnitudeFormControls>(
-        CONTROL_FORM_ID,
-        defaultFormValues(state.usageResponses.response.parameters)
-      );
-
-      state = {
-        ...state,
-        controlForm,
-        plots: INITIAL_STATE.plots,
-        serviceCallInfo: INITIAL_STATE.serviceCallInfo,
-        serviceResponses: INITIAL_STATE.serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On reset plot settings action */
-    on(actions.resetSettings, state => {
-      return onResetSetting(state, defaultPlots());
-    }),
-    /* On plots action */
-    on(actions.plots, (state, {plots}) => {
-      return {
-        ...state,
-        plots,
-      };
-    }),
-    /* On plot redraw action */
-    on(actions.plotRedraw, state => {
-      return onPlotRedraw(state);
-    }),
-    /* On service response action */
-    on(actions.serviceResponse, (state, {serviceResponses}) => {
-      const means = serviceResponses.map(s => s.response.means);
-      const sigmas = serviceResponses.map(s => s.response.sigmas);
-      const hasLogicTree = gmmUtils.hasTree([...means, ...sigmas]);
-
-      state = {
-        ...state,
-        controlForm: updateGroup<GmmMagnitudeFormControls>({
-          showEpistemicUncertainty: hasLogicTree ? enable : disable,
-        })(state.controlForm),
-        serviceResponses,
-      };
-
-      return {
-        ...state,
-        serviceCallInfo: gmmUtils.serviceCallInfo({
-          multiSelectableParam: state.controlForm.value.multiSelectableParam,
-          serviceName: state.serviceCallInfo.serviceName,
-          serviceResponses: state.serviceResponses,
-          serviceUrl,
-          values: state.controlForm.value,
-        }),
-      };
-    }),
-    /* On service call info action */
-    on(actions.serviceCallInfo, (state, {serviceCallInfo}) => {
-      return {
-        ...state,
-        serviceCallInfo: {
-          ...state.serviceCallInfo,
-          ...serviceCallInfo,
-        },
-      };
-    }),
-    /* On usage response action */
-    on(actions.usageResponse, (state, {usageResponses}) => {
-      return {
-        ...state,
-        usageResponses,
-      };
-    })
-  ),
-});
-
-/**
- * Application NGRX reducer with validators.
- */
-magnitudeAppFeature.reducer = wrapReducerWithFormStateUpdate(
-  magnitudeAppFeature.reducer,
-  state => state.controlForm,
-  validateControlPanelForm
-);
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
index d76a922d04d83d228d475fb0b084d272f6b74d97..2bddf912dfe3433295db359ab59bdf6429c73832 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/state/app.state.ts
@@ -1,45 +1,18 @@
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmMagnitudeResponse,
   GmmMagnitudeUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {
-  createFormGroupState,
-  disable,
-  FormGroupState,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
-
-import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
-import {DEFAULT_FORM_VALUES, defaultPlots} from '../utils/app.default-values';
-
-/** Control form id for ngrx-forms */
-export const CONTROL_FORM_ID = '[ngrx-forms] GM vs. Magnitude App Control Form';
 
-const formState = createFormGroupState<GmmMagnitudeFormControls>(
-  CONTROL_FORM_ID,
-  {
-    ...DEFAULT_FORM_VALUES,
-  }
-);
-
-/**
- * Initial state for the control panel
- */
-export const INITIAL_CONTROL_FORM_STATE = updateGroup<GmmMagnitudeFormControls>(
-  {
-    showEpistemicUncertainty: disable,
-  }
-)(formState);
+import {defaultPlots} from '../utils/app.default-values';
 
 /**
  * GMM magnitude app state.
  */
-export interface AppState extends SharedAppState {
-  /** Control panel form field state */
-  controlForm: FormGroupState<GmmMagnitudeFormControls>;
+export interface AppState {
+  plots: Map<string, NshmpPlot>;
   /** Service call info */
   serviceCallInfo: ServiceCallInfo;
   /** GMM service response */
@@ -47,21 +20,20 @@ export interface AppState extends SharedAppState {
   /** Supported IMTs */
   supportedImts: EnumParameterValues[];
   /** GMM usages */
-  usageResponses: GmmMagnitudeUsage;
+  usageResponse: GmmMagnitudeUsage;
 }
 
 /**
  * GMM magnitude app inital state.
  */
 export const INITIAL_STATE: AppState = {
-  controlForm: INITIAL_CONTROL_FORM_STATE,
   plots: defaultPlots(),
   serviceCallInfo: {
     serviceCalls: [],
     serviceName: 'Ground Motion vs. Magnitude',
-    usage: null,
+    usage: [],
   },
   serviceResponses: [],
   supportedImts: [],
-  usageResponses: null,
+  usageResponse: null,
 };
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts b/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
index 88b44121b7415fc718b7e3c2056774934b4674b4..fa7be0c7194f8ed5bdfe1e2be6efb6deaf17028f 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/utils/app.default-values.ts
@@ -1,15 +1,14 @@
-import {GmmSource, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpPlot,
   NshmpPlotSettings,
   PlotOptions,
   plotUtils,
-} from '@ghsc/nshmp-lib-ng/plot';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {
   GmmGroupType,
   GmmMagnitudeUsageParameters,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {box, createFormGroupState} from 'ngrx-forms';
 
 import {
   FormControlIds,
@@ -27,7 +26,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
   dip: null,
   distance: null,
   gmmGroupType: GmmGroupType.ACTIVE_CRUST,
-  gmmSource: box([] as GmmSource[]),
+  gmmSource: [],
   imt: 'default',
   mMax: null,
   mMin: null,
@@ -36,7 +35,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
   showEpistemicUncertainty: false,
   step: null,
   vs30: null,
-  vs30Multi: box([] as number[]),
+  vs30Multi: [],
   width: null,
   z1p0: null,
   z2p5: null,
@@ -49,7 +48,7 @@ export const DEFAULT_FORM_VALUES: GmmMagnitudeFormControls = {
  *
  * @param parameters The service parameters
  */
-export function defaultFormValues(
+export function usageFormValues(
   parameters: GmmMagnitudeUsageParameters
 ): GmmMagnitudeFormControls {
   return {
@@ -76,22 +75,12 @@ export const defaultPlots = (): Map<string, NshmpPlot> => {
   plots.set(gmmUtils.PlotType.MEANS, {
     label: 'Response Spectra',
     plotData: meanPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      MEAN_PLOT_SETTING_ID,
-      {
-        ...meanSettingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(meanSettingsForm),
   });
   plots.set(gmmUtils.PlotType.SIGMA, {
     label: 'Standard Deviation',
     plotData: sigmaPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      SIGMA_PLOT_SETTING_ID,
-      {
-        ...sigmaSettingsForm,
-      }
-    ),
+    settingsForm: plotUtils.plotSettingsToFormGroup(sigmaSettingsForm),
   });
   return new Map(plots);
 };
diff --git a/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts b/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
index 22baeb0c99988de1301938bf006b0c2daa55b51a..65712ca717b16813f9336c955f55e3c5b58b8870 100644
--- a/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
+++ b/projects/nshmp-apps/src/app/gmm/magnitude/utils/response.handler.ts
@@ -1,8 +1,11 @@
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {XySequence} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data';
 
+import {GmmMagnitudeFormControls} from '../models/gmm-magnitude-form-controls.model';
 import {AppState} from '../state/app.state';
+import {defaultPlots} from './app.default-values';
 
 /**
  * Transform Gmm distance service responses to plots.
@@ -10,15 +13,17 @@ import {AppState} from '../state/app.state';
  * @param state The application state
  */
 export function serviceResponseToPlotData(
-  state: AppState
+  state: AppState,
+  form: FormGroupControls<GmmMagnitudeFormControls>
 ): Map<string, NshmpPlot> {
-  if (state.serviceResponses.length === 0) {
-    return state.plots;
+  if (state.serviceResponses === null || state.serviceResponses?.length === 0) {
+    return defaultPlots();
   }
+
   const plots = new Map<string, NshmpPlot>();
   const meanPlot = state.plots.get(gmmUtils.PlotType.MEANS);
   const sigmaPlot = state.plots.get(gmmUtils.PlotType.SIGMA);
-  const formValues = state.controlForm.value;
+  const formValues = form.getRawValue();
 
   const responses: gmmUtils.GmmResponse<XySequence, number[]>[] =
     state.serviceResponses.map(serviceResponse => ({