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 bc789827acc0462dba39e2f6451d85cbc873fcbd..8a71fe50ef4208780e5fae4cd5ede5470abc835e 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
@@ -57,7 +57,7 @@ export class SiteParametersComponent implements OnInit, OnDestroy {
   constructor(private facade: AppFacade) {}
 
   ngOnInit(): void {
-    combineLatest([
+    this.valueSubscription = combineLatest([
       this.controls.vs30Multi.valueChanges,
       this.controls.vs30.valueChanges,
       this.controls.z1p0.valueChanges,
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html
index 3362dfb6b7cc650398c56f8fea8d35732713aa88..a317fdaa3f4be2bfbb8bf8b6cd1f2efaef37f1d3 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/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]="spectraTable$ | async"
+      <nshmp-lib-no-ngrx-export-data-table
+        [table]="spectraTable()"
         filename="spectra-medians.csv"
         buttonText="Export Medians as CSV"
       />
-      <nshmp-lib-ng-data-table [table]="spectraTable$ | async" />
+      <nshmp-lib-no-ngrx-data-table [table]="spectraTable()" />
     </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="spectra-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/spectra/components/content/content.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.ts
index fc2321541ce217d23d1aae366ccf036f7a47b67b..9c4e7ecd980393f27c58433f1db7d956c36bcfdc 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/content/content.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/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';
@@ -43,35 +42,33 @@ export class ContentComponent {
   spectraExp = true;
 
   /** Whether service has been called and data exists */
-  hasData$ = this.facade.serviceResponse$.pipe(
-    map(responseSpectra => responseSpectra?.length > 0)
-  );
+  hasData = computed(() => this.facade.serviceResponse()?.length > 0);
 
   /** Table data for sigma */
-  sigmaTable$ = this.facade.sigmaPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(
-        plot.plotData,
-        this.sigmaExp,
-        this.commonXValues,
-        this.padValues,
-        plot.xValues
-      )
-    )
-  );
+  sigmaTable = computed(() => {
+    const plot = this.facade.sigmaPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.sigmaExp,
+      this.commonXValues,
+      this.padValues,
+      plot.xValues
+    );
+  });
 
   /** Table data for mean */
-  spectraTable$ = this.facade.spectraPlotState$.pipe(
-    map(plot =>
-      gmmUtils.plotToTable(
-        plot.plotData,
-        this.spectraExp,
-        this.commonXValues,
-        this.padValues,
-        plot.xValues
-      )
-    )
-  );
+  spectraTable = computed(() => {
+    const plot = this.facade.spectraPlotState();
+
+    return gmmUtils.plotToTable(
+      plot.plotData,
+      this.spectraExp,
+      this.commonXValues,
+      this.padValues,
+      plot.xValues
+    );
+  });
 
   constructor(public facade: AppFacade) {}
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
index b0a397aef4178dd4d77a320a81c4d05fd1d74e2f..b81fc6684b50efefc2e5197055daec7ba99f676c 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.html
@@ -1,51 +1,47 @@
 <!-- Response Spectra 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 Model</mat-option>
-        <mat-option value="Mw">Magnitude</mat-option>
-        <mat-option value="vs30">Site Class</mat-option>
-      </mat-select>
-    </mat-form-field>
-
-    <!-- GMM menu -->
-    <nshmp-lib-ng-gmm-menu
-      [gmmControlState]="(controls$ | async)?.gmmSource"
-      [gmmGroupTypeState]="(controls$ | async)?.gmmGroupType"
-      [multiple]="(controls$ | async)?.multiSelectableParam.value === 'gmm'"
-      [parameters]="parameters$ | async"
+<form
+  class="settings-section control-panel height-full overflow-auto"
+  [formGroup]="form"
+  (submit)="onSubmit()"
+  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 Model</mat-option>
+      <mat-option value="Mw">Magnitude</mat-option>
+      <mat-option value="vs30">Site Class</mat-option>
+    </mat-select>
+  </mat-form-field>
+
+  <!-- GMM menu -->
+  @if (parameters()) {
+    <nshmp-lib-no-ngrx-gmm-menu
+      [gmmControl]="controls.gmmSource"
+      [gmmGroupTypeControl]="controls.gmmGroupType"
+      [multiple]="controls.multiSelectableParam.value === 'gmm'"
+      [parameters]="parameters()"
     />
+  }
 
-    <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/spectra/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.ts
index c98557c2f7da039620117875e2f4e32765e77529..e99636a3335c8ff4480dc6a1ee293964cef9b634 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/control-panel/control-panel.component.ts
@@ -1,19 +1,19 @@
 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,
   NshmpLibNgGmmMenuComponent,
   NshmpLibNgGmmPlotOptionsControlPanelComponent,
-} from '@ghsc/nshmp-lib-ng/gmm';
+} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 import {
   NshmpLibNgControlPanelButtonsComponent,
-  NshmpNgrxFormsModule,
   NshmpService,
-} from '@ghsc/nshmp-lib-ng/nshmp';
-import {unbox} from 'ngrx-forms';
-import {map} from 'rxjs/operators';
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
+import {Subscription} from 'rxjs';
 
 import {AppFacade} from '../../state/app.facade';
 import {EventParametersComponent} from '../event-parameters/event-parameters.component';
@@ -38,38 +38,63 @@ 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;
+
+  private subs: Subscription[] = [];
 
   constructor(
     public facade: AppFacade,
     public 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()
+      )
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub?.unsubscribe());
+  }
+
   /**
    * On form submit.
    */
@@ -77,4 +102,33 @@ export class ControlPanelComponent {
     this.facade.callService();
     this.nshmpService.selectPlotControl();
   }
+
+  private onGmmSource() {
+    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.facade.resetState();
+  }
+
+  private onShowEpistemicUncertainty(): void {
+    this.facade.createPlots();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
index d86d6face2ada7ca82897e4b7ae46cd55ae846fa..cb3797a9d1948f86da47efe7df05922ccbe1ff0c 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.html
@@ -1,92 +1,89 @@
 <!-- Event parameters -->
-@if (form$ | async; as form) {
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Event Parameters</mat-label>
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Event Parameters</mat-label>
 
-    <div class="settings-subsection--section">
-      <div class="grid-row grid-gap-sm">
-        <!-- Event parameters: Mw input -->
-        <nshmp-lib-ng-gmm-multi-select
-          class="grid-col-6 mw-input"
-          label="M<sub>W</sub>"
-          [multiple]="MwMultiple$ | async"
-          [numberControl]="form?.controls?.Mw"
-          [parameter]="(parameters$ | async)?.Mw"
-          placeholder="Select values..."
-          [selectControl]="form?.controls?.MwMulti"
-          [selectOptions]="MwCommonValues"
-        />
+  <div class="settings-subsection--section">
+    <div class="grid-row grid-gap-sm">
+      <!-- Event parameters: Mw input -->
+      <nshmp-lib-no-ngrx-gmm-multi-select
+        class="grid-col-6 mw-input"
+        label="M<sub>W</sub>"
+        [multiple]="MwMultiple$ | async"
+        [numberControl]="form.controls.Mw"
+        [parameter]="parameters()?.Mw"
+        placeholder="Select values..."
+        [selectControl]="form.controls.MwMulti"
+        [selectOptions]="MwCommonValues"
+      />
 
-        <!-- Event Parameters: Rake -->
-        <mat-form-field class="grid-col-6 rake-input">
-          <mat-label>Rake (°)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.rake?.max"
-            [min]="(parameters$ | async)?.rake?.min"
-            [ngrxFormControlState]="form?.controls?.rake"
-            step="5"
-            type="number"
-            [value]="form?.controls?.rakeButton?.value"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.rake?.min }},
-            {{ (parameters$ | async)?.rake?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-      </div>
+      <!-- Event Parameters: Rake -->
+      <mat-form-field class="grid-col-6 rake-input">
+        <mat-label>Rake (°)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.rake?.max"
+          [min]="parameters()?.rake?.min"
+          [formControl]="form.controls.rake"
+          step="5"
+          type="number"
+          [value]="form?.controls?.rakeButton?.value"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.rake?.min }},
+          {{ parameters()?.rake?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
+    </div>
 
-      <!-- Event Parameters: Rake Buttons -->
-      <mat-button-toggle-group
-        class="grid-col-12 rake-buttons"
-        [ngrxFormControlState]="form?.controls?.rakeButton"
-        [value]="form?.controls?.rake?.value"
-      >
-        <mat-button-toggle class="grid-col-4" [value]="0">
-          Strike-Slip
-        </mat-button-toggle>
-        <mat-button-toggle class="grid-col-4" [value]="-90">
-          Normal
-        </mat-button-toggle>
-        <mat-button-toggle class="grid-col-4" [value]="90">
-          Reverse
-        </mat-button-toggle>
-      </mat-button-toggle-group>
+    <!-- Event Parameters: Rake Buttons -->
+    <mat-button-toggle-group
+      class="grid-col-12 rake-buttons"
+      [formControl]="form.controls.rakeButton"
+      [value]="form.controls.rake.value"
+    >
+      <mat-button-toggle class="grid-col-4" [value]="0">
+        Strike-Slip
+      </mat-button-toggle>
+      <mat-button-toggle class="grid-col-4" [value]="-90">
+        Normal
+      </mat-button-toggle>
+      <mat-button-toggle class="grid-col-4" [value]="90">
+        Reverse
+      </mat-button-toggle>
+    </mat-button-toggle-group>
 
-      <!-- Event Parameters: zHyp -->
-      <div class="grid-row grid-gap-lg zhyp-input">
-        <mat-form-field class="grid-col-4">
-          <mat-label>Z<sub>HYP</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.zHyp?.max"
-            [min]="(parameters$ | async)?.zHyp?.min"
-            [ngrxFormControlState]="form?.controls?.zHyp"
-            step="0.5"
-            type="number"
-            [disabled]="form?.value?.zHypCentered"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.zHyp?.min }},
-            {{ (parameters$ | async)?.zHyp?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
+    <!-- Event Parameters: zHyp -->
+    <div class="grid-row grid-gap-lg zhyp-input">
+      <mat-form-field class="grid-col-4">
+        <mat-label>Z<sub>HYP</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.zHyp?.max"
+          [min]="parameters()?.zHyp?.min"
+          [formControl]="form.controls.zHyp"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.zHyp?.min }},
+          {{ parameters()?.zHyp?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
 
-        <!-- Event Parameters: centered down dip -->
-        <div class="grid-col-8">
-          <mat-checkbox
-            class="down-dip-checkbox"
-            color="primary"
-            [ngrxFormControlState]="form?.controls?.zHypCentered"
-          >
-            Centered down-dip
-          </mat-checkbox>
-        </div>
+      <!-- Event Parameters: centered down dip -->
+      <div class="grid-col-8">
+        <mat-checkbox
+          class="down-dip-checkbox"
+          color="primary"
+          [formControl]="form.controls.zHypCentered"
+        >
+          Centered down-dip
+        </mat-checkbox>
       </div>
     </div>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.ts
index 06354d9895575cbfdb843ff00d379cf88787a2fc..b4097b24c86fd3ad15e8af10f964d19ac42889d1 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/event-parameters/event-parameters.component.ts
@@ -1,5 +1,12 @@
 import {AsyncPipe} from '@angular/common';
-import {Component, ViewEncapsulation} from '@angular/core';
+import {
+  Component,
+  computed,
+  OnDestroy,
+  OnInit,
+  ViewEncapsulation,
+} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {
   MatButtonToggle,
   MatButtonToggleGroup,
@@ -11,11 +18,12 @@ import {
   GmmFormControlIds,
   MW_COMMON_VALUES,
   NshmpLibNgGmmMultiSelectComponent,
-} 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';
+import {calcHypocenterDepth} from '../../utils/app.utils';
 
 /**
  * Control panel form fields for GMM event parameters.
@@ -32,33 +40,66 @@ import {AppFacade} from '../../state/app.facade';
     MatButtonToggle,
     MatCheckbox,
     AsyncPipe,
-    NshmpNgrxFormsModule,
+    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 {
   /** Common Vs30 values */
   MwCommonValues = MW_COMMON_VALUES;
 
   /** Control panel form field state */
-  form$ = this.facade.controlForm$;
+  form = this.facade.formGroup;
 
-  /** Whether Mw is multi-selectable */
-  MwMultiple$ = this.facade.controlForm$.pipe(
-    map(
-      form =>
-        form.controls.multiSelectableParam.value ===
-        GmmFormControlIds.MW.toString()
-    )
-  );
+  /** Whether magnitudes are multi-selectable */
+  MwMultiple$ =
+    this.facade.formGroup.controls.multiSelectableParam.valueChanges.pipe(
+      map(
+        multiSelectableParam =>
+          multiSelectableParam.toString() === GmmFormControlIds.MW.toString()
+      )
+    );
 
   /** Usage parameters */
-  parameters$ = this.facade.usage$.pipe(
-    map(usage => usage?.response?.parameters)
-  );
+  parameters = computed(() => this.facade.usage()?.response.parameters);
+
+  private controls = this.form.controls;
+
+  private subs: Subscription[] = [];
 
   constructor(public facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.subs.push(
+      combineLatest([
+        this.controls.Mw.valueChanges,
+        this.controls.rake.valueChanges,
+        this.controls.rakeButton.valueChanges,
+        this.controls.zHyp.valueChanges,
+      ]).subscribe(() => this.facade.resetState())
+    );
+
+    this.subs.push(
+      this.controls.zHypCentered.valueChanges.subscribe(() =>
+        this.onZHypCentered()
+      )
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub.unsubscribe());
+  }
+
+  private onZHypCentered(): void {
+    if (this.form.getRawValue().zHypCentered) {
+      this.controls.zHyp.disable();
+    } else {
+      this.controls.zHyp.enable();
+    }
+
+    calcHypocenterDepth(this.form);
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.html
index 7f123557e6fa176ca95e9b6aac5d14df661f7c99..e35d058f4bb911531c9ee00daeee41f6c9a34067 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.html
@@ -1,137 +1,135 @@
-@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-col-12 tablet-lg:grid-col-3">
-      <mat-list class="parameter-list">
-        @if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
-          <mat-list-item>
-            <span class="parameter">Magnitude</span>:
-          </mat-list-item>
-
-          @for (Mw of unbox(form?.value?.MwMulti); track Mw) {
-            <mat-list-item class="indent-list">
-              {{ Mw }}
-            </mat-list-item>
-          }
-        } @else {
-          <mat-list-item>
-            <span class="parameter">Magnitude</span>:
-            {{ form?.value?.Mw }}
-          </mat-list-item>
-        }
-
-        <mat-list-item>
-          <span class="parameter">Rake</span>: {{ form?.value?.rake }}°
-        </mat-list-item>
-
-        <mat-list-item>
-          <span class="parameter"> Z<sub>HYP</sub> </span>:
-          {{ form?.value?.zHyp }} km
-        </mat-list-item>
-
-        <mat-list-item>
-          <span class="parameter">Centered Down-Dip</span>:
-          {{ form.value.zHypCentered === true ? 'On' : 'Off' }}
-        </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">Dip</span>: {{ form?.value?.dip }}°
-        </mat-list-item>
-
-        <mat-list-item>
-          <span class="parameter">Width</span>: {{ form?.value?.width }} km
-        </mat-list-item>
-
-        <mat-list-item>
-          <span class="parameter"> R<sub>X</sub> </span>:
-          {{ form?.value?.rX }}
-          &nbsp;km
-        </mat-list-item>
-      </mat-list>
-    </div>
-
-    <div class="grid-col-12 tablet-lg:grid-col-3">
-      <mat-list class="parameter-list">
-        <mat-list-item>
-          <span class="parameter"> R<sub>RUP</sub> </span>:
-          {{ form?.value?.rRup }}
-          &nbsp;km
-        </mat-list-item>
-
-        <mat-list-item>
-          <span class="parameter"> R<sub>JB</sub> </span>:
-          {{ form?.value?.rJB }}
-          &nbsp;km
+<div class="grid-row parameter-summary">
+  <div class="grid-col-12 tablet-lg:grid-col-6">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter">Ground Motion Models</span>:
+      </mat-list-item>
+
+      @for (gmmSource of form?.value?.gmmSource; track gmmSource) {
+        <mat-list-item class="indent-list font-small">
+          {{ gmmSource.gmm.display }}
         </mat-list-item>
+      }
+    </mat-list>
+  </div>
 
+  <div class="grid-col-12 tablet-lg:grid-col-3">
+    <mat-list class="parameter-list">
+      @if (form.value.multiSelectableParam === GmmFormControlIds.MW) {
         <mat-list-item>
-          <span class="parameter">Derive R<sub>JB</sub> & R<sub>RUP</sub></span
-          >:
-          {{ form.value.derivePathParams === true ? 'On' : 'Off' }}
+          <span class="parameter">Magnitude</span>:
         </mat-list-item>
 
+        @for (Mw of form?.value?.MwMulti; track Mw) {
+          <mat-list-item class="indent-list">
+            {{ Mw }}
+          </mat-list-item>
+        }
+      } @else {
         <mat-list-item>
-          <span class="parameter">Wall</span>:
-          {{ form?.value?.hangingWall ? 'Hanging Wall' : 'Footwall' }}
+          <span class="parameter">Magnitude</span>:
+          {{ form?.value?.Mw }}
         </mat-list-item>
+      }
+
+      <mat-list-item>
+        <span class="parameter">Rake</span>: {{ form?.value?.rake }}°
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> Z<sub>HYP</sub> </span>:
+        {{ form?.value?.zHyp }} km
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter">Centered Down-Dip</span>:
+        {{ form.value.zHypCentered === true ? 'On' : 'Off' }}
+      </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">Dip</span>: {{ form?.value?.dip }}°
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter">Width</span>: {{ form?.value?.width }} km
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> R<sub>X</sub> </span>:
+        {{ form?.value?.rX }}
+        &nbsp;km
+      </mat-list-item>
+    </mat-list>
+  </div>
 
-        @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 }}
+  <div class="grid-col-12 tablet-lg:grid-col-3">
+    <mat-list class="parameter-list">
+      <mat-list-item>
+        <span class="parameter"> R<sub>RUP</sub> </span>:
+        {{ form?.value?.rRup }}
+        &nbsp;km
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter"> R<sub>JB</sub> </span>:
+        {{ form?.value?.rJB }}
+        &nbsp;km
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter">Derive R<sub>JB</sub> & R<sub>RUP</sub></span
+        >:
+        {{ form.value.derivePathParams === true ? 'On' : 'Off' }}
+      </mat-list-item>
+
+      <mat-list-item>
+        <span class="parameter">Wall</span>:
+        {{ form?.value?.hangingWall ? 'Hanging Wall' : 'Footwall' }}
+      </mat-list-item>
+
+      @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/spectra/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.ts
index 166e75ceceae0c498c77a8ad634b2e22b42325ea..ea07924b428e1863c7b6cdd4cf9d5a05fae85831 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/parameter-summary/parameter-summary.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/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/spectra/components/path-parameters/path-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
index b527eaf74e1ccd1a1a5721710255b44b9bba3c02..06b0961a45fe468a0aa8c4a2ea9b61a219d79c60 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.html
@@ -1,93 +1,89 @@
-@if (form$ | async; as form) {
-  <!-- Path Parameters -->
-  <div class="settings-subsection control-panel">
-    <mat-label class="settings-subsection--label">Path Parameters</mat-label>
+<!-- Path Parameters -->
+<div class="settings-subsection control-panel">
+  <mat-label class="settings-subsection--label">Path Parameters</mat-label>
 
-    <div class="settings-subsection--section">
-      <div class="grid-row grid-gap-sm">
-        <!-- Path Parameters: rX input -->
-        <mat-form-field class="grid-col-4 rx-input">
-          <mat-label>R<sub>X</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.rX?.max"
-            [min]="(parameters$ | async)?.rX?.min"
-            [ngrxFormControlState]="form?.controls?.rX"
-            step="0.5"
-            type="number"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.rX?.min }},
-            {{ (parameters$ | async)?.rX?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
+  <div class="settings-subsection--section">
+    <div class="grid-row grid-gap-sm">
+      <!-- Path Parameters: rX input -->
+      <mat-form-field class="grid-col-4 rx-input">
+        <mat-label>R<sub>X</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.rX?.max"
+          [min]="parameters()?.rX?.min"
+          [formControl]="form.controls.rX"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.rX?.min }},
+          {{ parameters()?.rX?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
 
-        <!-- Path Parameters: rRup input -->
-        <mat-form-field class="grid-col-4 rrup-input">
-          <mat-label>R<sub>RUP</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.rRup?.max"
-            [min]="(parameters$ | async)?.rRup?.min"
-            [ngrxFormControlState]="form?.controls?.rRup"
-            step="0.5"
-            type="number"
-            [disabled]="form?.value?.derivePathParams"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.rRup?.min }},
-            {{ (parameters$ | async)?.rRup?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
+      <!-- Path Parameters: rRup input -->
+      <mat-form-field class="grid-col-4 rrup-input">
+        <mat-label>R<sub>RUP</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.rRup?.max"
+          [min]="parameters()?.rRup?.min"
+          [formControl]="form.controls.rRup"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.rRup?.min }},
+          {{ parameters()?.rRup?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
 
-        <!-- Path Parameters: rJB input -->
-        <mat-form-field class="grid-col-4 rjb-input">
-          <mat-label>R<sub>JB</sub> (km)</mat-label>
-          <input
-            matInput
-            [max]="(parameters$ | async)?.rJB?.max"
-            [min]="(parameters$ | async)?.rJB?.min"
-            [ngrxFormControlState]="form?.controls?.rJB"
-            step="0.5"
-            type="number"
-            [disabled]="form?.value?.derivePathParams"
-          />
-          <mat-error>
-            [
-            {{ (parameters$ | async)?.rJB?.min }},
-            {{ (parameters$ | async)?.rJB?.max }}
-            ]
-          </mat-error>
-        </mat-form-field>
-      </div>
-
-      <!-- Path Parameters: Derive rJB and rRup -->
-      <div class="grid-row">
-        <mat-checkbox
-          class="grid-col-12 rjb-rrup-checkbox"
-          color="primary"
-          [ngrxFormControlState]="form?.controls?.derivePathParams"
-        >
-          Derive R<sub>JB</sub> and R<sub>RUP</sub>
-        </mat-checkbox>
-      </div>
+      <!-- Path Parameters: rJB input -->
+      <mat-form-field class="grid-col-4 rjb-input">
+        <mat-label>R<sub>JB</sub> (km)</mat-label>
+        <input
+          matInput
+          [max]="parameters()?.rJB?.max"
+          [min]="parameters()?.rJB?.min"
+          [formControl]="form.controls.rJB"
+          step="0.5"
+          type="number"
+        />
+        <mat-error>
+          [
+          {{ parameters()?.rJB?.min }},
+          {{ parameters()?.rJB?.max }}
+          ]
+        </mat-error>
+      </mat-form-field>
+    </div>
 
-      <!-- Path Parameters: Hanging wall or footwall -->
-      <mat-button-toggle-group
-        class="grid-col-12 hanging-wall-button"
-        [ngrxFormControlState]="form?.controls?.hangingWall"
+    <!-- Path Parameters: Derive rJB and rRup -->
+    <div class="grid-row">
+      <mat-checkbox
+        class="grid-col-12 rjb-rrup-checkbox"
+        color="primary"
+        [formControl]="form.controls.derivePathParams"
       >
-        <mat-button-toggle class="grid-col-6" [value]="true">
-          Hanging Wall
-        </mat-button-toggle>
-        <mat-button-toggle class="grid-col-6" [value]="false">
-          Footwall
-        </mat-button-toggle>
-      </mat-button-toggle-group>
+        Derive R<sub>JB</sub> and R<sub>RUP</sub>
+      </mat-checkbox>
     </div>
+
+    <!-- Path Parameters: Hanging wall or footwall -->
+    <mat-button-toggle-group
+      class="grid-col-12 hanging-wall-button"
+      [formControl]="form.controls.hangingWall"
+    >
+      <mat-button-toggle class="grid-col-6" [value]="true">
+        Hanging Wall
+      </mat-button-toggle>
+      <mat-button-toggle class="grid-col-6" [value]="false">
+        Footwall
+      </mat-button-toggle>
+    </mat-button-toggle-group>
   </div>
-}
+</div>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.ts
index 0aa99a26e9a20670894cd843cb4312fc072a54d0..dda358e483b1a4b56404fb9396e7c64862430eb6 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/path-parameters/path-parameters.component.ts
@@ -1,5 +1,5 @@
-import {AsyncPipe} from '@angular/common';
-import {Component} from '@angular/core';
+import {Component, computed, OnDestroy, OnInit} from '@angular/core';
+import {ReactiveFormsModule} from '@angular/forms';
 import {
   MatButtonToggle,
   MatButtonToggleGroup,
@@ -7,10 +7,10 @@ import {
 import {MatCheckbox} from '@angular/material/checkbox';
 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';
+import {calcPathParameters} from '../../utils/app.utils';
 
 /**
  * Control panel form fields for GMM path parameters.
@@ -24,25 +24,86 @@ import {AppFacade} from '../../state/app.facade';
     MatCheckbox,
     MatButtonToggleGroup,
     MatButtonToggle,
-    AsyncPipe,
-    NshmpNgrxFormsModule,
+    ReactiveFormsModule,
   ],
   selector: 'app-path-parameters',
   standalone: true,
   styleUrl: './path-parameters.component.scss',
   templateUrl: './path-parameters.component.html',
 })
-export class PathParametersComponent {
-  /** Form controls state */
-  controls$ = this.facade.controlForm$.pipe(map(form => form?.controls));
-
+export class PathParametersComponent implements OnInit, OnDestroy {
   /** 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);
+
+  subs: Subscription[] = [];
+
+  private controls = this.form.controls;
 
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.subs.push(this.controls.rX.valueChanges.subscribe(() => this.onRx()));
+
+    this.subs.push(
+      this.controls.derivePathParams.valueChanges.subscribe(() =>
+        this.onDerivePathParams()
+      )
+    );
+
+    this.subs.push(
+      this.controls.hangingWall.valueChanges.subscribe(() =>
+        this.onHangingWall()
+      )
+    );
+  }
+
+  ngOnDestroy(): void {
+    this.subs.forEach(sub => sub.unsubscribe());
+  }
+
+  private onDerivePathParams(): void {
+    const {derivePathParams, hangingWall, rX} = this.form.getRawValue();
+
+    if (derivePathParams) {
+      if (rX >= 0 !== hangingWall) {
+        this.controls.hangingWall.setValue(rX >= 0);
+      }
+      this.controls.hangingWall.enable();
+      this.controls.rJB.disable();
+      this.controls.rRup.disable();
+      calcPathParameters(this.form);
+    } else {
+      this.controls.hangingWall.setValue(null);
+      this.controls.hangingWall.disable();
+      this.controls.rJB.enable();
+      this.controls.rRup.enable();
+    }
+
+    this.facade.resetState();
+  }
+
+  private onHangingWall(): void {
+    const values = this.form.getRawValue();
+    const rX = values.hangingWall ? Math.abs(values.rX) : -Math.abs(values.rX);
+
+    if (values.rX !== rX) {
+      this.controls.rX.setValue(rX);
+    }
+    calcPathParameters(this.form);
+    this.facade.resetState();
+  }
+
+  private onRx(): void {
+    const {derivePathParams, hangingWall, rX} = this.form.getRawValue();
+
+    if (derivePathParams && rX >= 0 !== hangingWall) {
+      this.controls.hangingWall.patchValue(rX >= 0);
+    }
+
+    calcPathParameters(this.form);
+    this.facade.resetState();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
index cbc9234323f1cd016e4079196748915a531d91dd..afead4807c59a12b832d54be3f5b96ff84ae2ed9 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.html
@@ -1,27 +1,26 @@
 <div class="height-full overflow-auto">
-  @if (spectraPlot$ | async) {
-    <nshmp-lib-ng-plot-settings
+  @if (spectraPlot()) {
+    <nshmp-lib-no-ngrx-plot-settings
       class="spectra-settings"
-      [plot]="spectraPlot$ | async"
+      [plot]="spectraPlot()"
+      (updatedPlot)="updatePlot(PlotIds.MEANS, $event)"
     />
   }
 
-  @if (sigmaPlot$ | async) {
+  @if (sigmaPlot()) {
     <nshmp-lib-ng-plot-settings
       class="sigma-settings"
-      [plot]="sigmaPlot$ | async"
+      [plot]="sigmaPlot()"
+      (updatedPlot)="updatePlot(PlotIds.SIGMA, $event)"
     />
   }
 
   <div class="padding-y-3"></div>
 
-  <nshmp-lib-ng-plot-reset-plot-settings
+  <nshmp-lib-no-ngrx-plot-reset-plot-settings
     (resetClick)="facade.resetPlotSettings()"
     [resetDisabled]="
-      (spectraPlotSettings$ | async)?.isPristine &&
-      (spectraPlotSettings$ | async)?.isUntouched &&
-      (sigmaPlotSettings$ | async)?.isPristine &&
-      (sigmaPlotSettings$ | async)?.isPristine
+      spectraPlotSettings()?.pristine && sigmaPlotSettings()?.pristine
     "
   />
 </div>
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.ts
index 6b2ad32e2131a0b8865b4be56004a7c354d5eb02..f39fca909e907a766b588c7107883acdf87799ba 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/plots-settings/plots-settings.component.ts
@@ -1,11 +1,12 @@
 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';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
+import {SpectraPlot} from '../../models/spectra-plot.model';
 import {AppFacade} from '../../state/app.facade';
 
 /**
@@ -23,19 +24,29 @@ import {AppFacade} from '../../state/app.facade';
   templateUrl: './plots-settings.component.html',
 })
 export class PlotsSettingsComponent {
-  /** Sigma plot state */
-  sigmaPlot$ = this.facade.sigmaPlotState$;
-  /** Sigma plot settings */
-  sigmaPlotSettings$ = this.facade.sigmaPlotState$.pipe(
-    map(plot => plot.settingsForm)
-  );
+  PlotIds = gmmUtils.PlotType;
 
-  /** Spectra plot */
-  spectraPlot$ = this.facade.spectraPlotState$;
-  /** Spectra plot settings */
-  spectraPlotSettings$ = this.facade.spectraPlotState$.pipe(
-    map(plot => plot.settingsForm)
+  /** Mean plot state */
+  spectraPlot = this.facade.spectraPlotState;
+
+  /** Mean plot settings */
+  spectraPlotSettings = computed(
+    () => this.facade.spectraPlotState().settingsForm
   );
 
+  /** Sigma plot state */
+  sigmaPlot = this.facade.sigmaPlotState;
+
+  /** Sigma plot settings */
+  sigmaPlotSettings = computed(() => this.facade.sigmaPlotState().settingsForm);
+
   constructor(public facade: AppFacade) {}
+
+  updatePlot(id: string, plot: SpectraPlot): 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/spectra/components/plots/plots.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
index 6e73585f8d29ec594e684fab0e9bb5e9cd6ad60d..5c3419366f5f8bf8ab20c1a55e206e61ca7e6a23 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.html
@@ -1,4 +1,4 @@
-<nshmp-lib-ng-plots-container>
+<nshmp-lib-no-ngrx-plots-container>
   <mat-accordion multi>
     <!-- Spectra plot -->
     <mat-expansion-panel expanded>
@@ -8,8 +8,11 @@
 
       <mat-divider />
 
-      @if (spectraPlotData$ | async; as spectraPlot) {
-        <nshmp-lib-ng-plot class="spectra-plot" [plot]="spectraPlot" />
+      @if (spectraPlotData()) {
+        <nshmp-lib-no-ngrx-plot
+          class="spectra-plot"
+          [plot]="spectraPlotData()"
+        />
       }
     </mat-expansion-panel>
 
@@ -21,8 +24,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 +46,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/spectra/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.ts
index 5ef21065f4b79423c302b9d438da138644407a4f..6f493ca397500854a78f7311e17b94d134db0fd9 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/plots/plots.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/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,15 +7,14 @@ import {
   MatExpansionPanelHeader,
   MatExpansionPanelTitle,
 } from '@angular/material/expansion';
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
-import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   NshmpLibNgPlotComponent,
   NshmpLibNgPlotsContainerComponent,
-} from '@ghsc/nshmp-lib-ng/plot';
+} from '@ghsc/nshmp-lib-no-ngrx/plot';
 import {PlotlyPlot} from '@ghsc/nshmp-utils-ts/libs/plotly';
 import {PlotData} from 'plotly.js';
-import {map} from 'rxjs/operators';
 
 import {AppFacade} from '../../state/app.facade';
 import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.component';
@@ -43,19 +42,17 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary.
 })
 export class PlotsComponent {
   /** Sigma plot data */
-  sigmaPlotData$ = this.facade.sigmaPlotState$.pipe(
-    map(plot => this.filterData(plot.plotData))
+  sigmaPlotData = computed(() =>
+    this.filterData(this.facade.sigmaPlotState().plotData)
   );
 
   /** Response spectra plot data */
-  spectraPlotData$ = this.facade.spectraPlotState$.pipe(
-    map(plot => ({...this.filterData(plot.plotData)}))
-  );
+  spectraPlotData = computed(() => ({
+    ...this.filterData(this.facade.spectraPlotState().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/spectra/components/site-parameters/site-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
index ccd874a9f4b036f0dd4912f3eede85de0473e069..66e5d7c34e0f90dc63b7f9c0f3fd0b70962d7df3 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.html
@@ -1,77 +1,75 @@
-@if (form$ | async; as form) {
-  <!-- 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>&frasl;<sub>s</sub>)"
-        [multiple]="vs30Multiple$ | async"
-        [numberControl]="form?.controls?.vs30"
-        [parameter]="(parameters$ | async)?.vs30"
-        placeholder="Select values..."
-        [selectControl]="form?.controls?.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>&frasl;<sub>s</sub>)"
+      [multiple]="vs30Multiple$ | async"
+      [numberControl]="form?.controls?.vs30"
+      [parameter]="parameters()?.vs30"
+      placeholder="Select values..."
+      [selectControl]="form.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]="form?.controls?.z1p0"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z1p0?.min }},
-          {{ (parameters$ | async)?.z1p0?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 1.0 input -->
+    <mat-form-field class="grid-col-4 z1p0-input">
+      <mat-label>Z<sub>1.0</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z1p0?.max"
+        [min]="parameters()?.z1p0?.min"
+        [formControl]="form?.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]="form?.controls?.z2p5"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.z2p5?.min }},
-          {{ (parameters$ | async)?.z2p5?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
+    <!-- Site & Basin: Z 2.5 input -->
+    <mat-form-field class="grid-col-4 z2p5-input">
+      <mat-label>Z<sub>2.5</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.z2p5?.max"
+        [min]="parameters()?.z2p5?.min"
+        [formControl]="form.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]="form?.controls?.zSed"
-          step="1.0"
-          type="number"
-        />
-        <mat-error>
-          [
-          {{ (parameters$ | async)?.zSed?.min }},
-          {{ (parameters$ | async)?.zSed?.max }}
-          ]
-        </mat-error>
-      </mat-form-field>
-    </div>
+    <!-- Site & Basin: zSed input -->
+    <mat-form-field class="grid-col-4 zsed-input">
+      <mat-label>Z<sub>SED</sub> (km)</mat-label>
+      <input
+        matInput
+        [max]="parameters()?.zSed?.max"
+        [min]="parameters()?.zSed?.min"
+        [formControl]="form.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/spectra/components/site-parameters/site-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.ts
index c4100d13ea041fb5cf9e6e30556c5aa42caa1edb..48ea9f9b3e414f282b7c9239635a72fd81c5bd5c 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/site-parameters/site-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/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,33 +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 {
+  /** Form controls state */
+  private controls = this.facade.formGroup.controls;
+
   /** Common vs30 values */
   vs30CommonValues = VS30_COMMON_VALUES;
 
   /** 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.facade.controlForm$.pipe(
+  /** Whether Vs30 is multi-selectable */
+  vs30Multiple$ = this.controls.multiSelectableParam.valueChanges.pipe(
     map(
-      form =>
-        form.controls.multiSelectableParam.value ===
-        GmmFormControlIds.VS30.toString()
+      multiSelectableParam =>
+        multiSelectableParam.toString() === GmmFormControlIds.VS30.toString()
     )
   );
 
+  private valueSubscription = new Subscription();
+
   constructor(private facade: AppFacade) {}
+
+  ngOnInit(): void {
+    this.valueSubscription = 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/spectra/components/source-parameters/source-parameters.component.html b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
index 4f1293cdedec01c099a755c3e403effbb71167c3..cc101f7f91ecb794ea27b792d3934cc1c35ece72 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.html
@@ -1,65 +1,63 @@
-@if (form$ | async; as form) {
-  <!-- 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]="form?.controls?.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]="form.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]="form?.controls?.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]="form.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]="form?.controls?.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]="form.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/spectra/components/source-parameters/source-parameters.component.ts b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.ts
index f9a9e72920adc0ae69716f6cd552e005fe79554b..8bc99a98139fef0e3fe03979d76333244cb8ad44 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/components/source-parameters/source-parameters.component.ts
@@ -1,11 +1,12 @@
 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';
+import {calcHypocenterDepth} from '../../utils/app.utils';
 
 /**
  * Control panel form fields for GMM source parameters.
@@ -17,21 +18,38 @@ 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 {
   /** 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 controls = this.form.controls;
+
+  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();
+      calcHypocenterDepth(this.form);
+    });
+  }
+
+  ngOnDestroy(): void {
+    this.valueSubscription.unsubscribe();
+  }
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/models/form-controls.model.ts b/projects/nshmp-apps/src/app/gmm/spectra/models/form-controls.model.ts
index a2675afc8070099ac2dda910928d901680d25a94..baf700cb25d86b2eeca5d7bd83b62e8ff27e3ed8 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/models/form-controls.model.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/models/form-controls.model.ts
@@ -1,4 +1,4 @@
-import {GmmFormControlIds, GmmFormControls} from '@ghsc/nshmp-lib-ng/gmm';
+import {GmmFormControls} from '@ghsc/nshmp-lib-no-ngrx/gmm';
 
 /**
  * Control panel form fields.
@@ -23,29 +23,3 @@ export interface FormControls extends GmmFormControls {
   /** Whether hypocenter is centered */
   zHypCentered: boolean;
 }
-
-/** Control panel ids */
-enum GmmSpectraFormControlIds {
-  DERIVE_PATH_PARAMS = 'derivePathParams',
-  HANGING_WALL = 'hangingWall',
-  RAKE = 'rake',
-  RAKE_BUTTON = 'rakeButton',
-  RJB = 'rJB',
-  RRUP = 'rRup',
-  RX = 'rX',
-  ZHYP = 'zHyp',
-  ZHYP_CENTERED = 'zHypCentered',
-}
-
-/**
- * Application control panel form field ids.
- */
-export const FormControlIds = {
-  ...GmmFormControlIds,
-  ...GmmSpectraFormControlIds,
-};
-
-/**
- * Application control panel form field ids.
- */
-export type FormControlIds = GmmFormControlIds | GmmSpectraFormControlIds;
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/models/spectra-plot.model.ts b/projects/nshmp-apps/src/app/gmm/spectra/models/spectra-plot.model.ts
index 255351042acf234bea045bf79e646bbf99999607..ee59dc12e278c091ad5db3fcf6b8060a3a52c917 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/models/spectra-plot.model.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/models/spectra-plot.model.ts
@@ -1,4 +1,4 @@
-import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot';
+import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot';
 
 /**
  * Spectra plot
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/state/app.facade.ts b/projects/nshmp-apps/src/app/gmm/spectra/state/app.facade.ts
index 020ed44c40a8462ea8df9bd756ec510c7878a46a..ffbfa2bf25efe6b714ca19c2af2ab4eb437fdbfc 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/state/app.facade.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/state/app.facade.ts
@@ -1,18 +1,33 @@
-import {Injectable} from '@angular/core';
+import {computed, Injectable, Signal, signal} from '@angular/core';
+import {AbstractControl, FormBuilder, Validators} from '@angular/forms';
 import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {
+  FormGroupControls,
+  NshmpService,
+  SpinnerService,
+} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   GmmSpectraResponse,
   GmmSpectraUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {select, Store} from '@ngrx/store';
-import {FormGroupState} from 'ngrx-forms';
-import {Observable} from 'rxjs';
+import {environment} from 'projects/nshmp-apps/src/environments/environment';
+import {
+  redrawPlots,
+  resetPlotSettings,
+} from 'projects/nshmp-apps/src/shared/utils/facade.utils';
+import {catchError} from 'rxjs';
 
 import {FormControls} from '../models/form-controls.model';
 import {SpectraPlot} from '../models/spectra-plot.model';
-import {actions} from './app.actions';
-import {spectraAppFeature} 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 response spectra application.
@@ -21,89 +36,250 @@ import {spectraAppFeature} from './app.reducer';
   providedIn: 'root',
 })
 export class AppFacade {
-  constructor(private store: Store) {}
+  /** nshmp-ws base URL */
+  baseUrl = environment.webServices.data.url;
+  /** GMM service URL */
+  serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmSpectra}`;
 
-  /**
-   * Returns the control panel form.
-   */
-  get controlForm$(): Observable<FormGroupState<FormControls>> {
-    return this.store.pipe(select(spectraAppFeature.selectControlForm));
+  readonly formGroup = this.formBuilder.group({
+    ...DEFAULT_FORM_VALUES,
+    gmmSource: [],
+    MwMulti: [],
+    vs30Multi: [],
+  }) as FormGroupControls<FormControls>;
+
+  readonly state = signal<AppState>(INITIAL_STATE);
+
+  constructor(
+    private formBuilder: FormBuilder,
+    private nshmpService: NshmpService,
+    private spinnerService: SpinnerService
+  ) {
+    this.addValidators(this.formGroup);
+    this.formGroup.controls.gmmSource.setValue([]);
+    this.formGroup.controls.showEpistemicUncertainty.disable();
   }
 
   /**
-   * Returns service call info observable.
+   * Returns the mean plot state.
    */
-  get serviceCallInfo$(): Observable<ServiceCallInfo> {
-    return this.store.pipe(select(spectraAppFeature.selectServiceCallInfo));
+  get spectraPlotState(): Signal<SpectraPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.MEANS));
   }
 
   /**
-   * Returns the Gmm distance service responses.
+   * Returns service call info observable.
    */
-  get serviceResponse$(): Observable<GmmSpectraResponse[]> {
-    return this.store.pipe(select(spectraAppFeature.selectServiceResponses));
+  get serviceCallInfo(): Signal<ServiceCallInfo> {
+    return computed(() => this.state().serviceCallInfo);
   }
 
   /**
-   * Returns the mean plot state.
+   * Returns the Gmm distance service responses.
    */
-  get spectraPlotState$(): Observable<SpectraPlot> {
-    return this.store.pipe(select(spectraAppFeature.selectSpectraPlot));
+  get serviceResponse(): Signal<GmmSpectraResponse[]> {
+    return computed(() => this.state().serviceResponses);
   }
 
   /**
    * Returns the mean plot state.
    */
-  get sigmaPlotState$(): Observable<SpectraPlot> {
-    return this.store.pipe(select(spectraAppFeature.selectSigmaPlot));
+  get sigmaPlotState(): Signal<SpectraPlot> {
+    return computed(() => this.state().plots?.get(gmmUtils.PlotType.SIGMA));
   }
 
   /**
    * Returns supported IMTs observable.
    */
-  get supportedImts$(): Observable<EnumParameterValues[]> {
-    return this.store.pipe(select(spectraAppFeature.selectSupportedImts));
+  get supportedImts(): Signal<EnumParameterValues[]> {
+    return computed(() => this.state().supportedImts);
   }
 
   /**
    * Returns the Gmm distance usage response.
    */
-  get usage$(): Observable<GmmSpectraUsage> {
-    return this.store.pipe(select(spectraAppFeature.selectUsageResponses));
+  get usage(): Signal<GmmSpectraUsage> {
+    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$<GmmSpectraResponse>(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$<GmmSpectraUsage>(this.serviceUrl)
+      .pipe(catchError((error: Error) => this.nshmpService.throwError$(error)))
+      .subscribe(usageResponse => this.handleUsageResponse(usageResponse));
   }
 
   /**
    * Redraw the plot.
    */
   plotRedraw(): void {
-    this.store.dispatch(actions.plotRedraw());
+    this.updateState({
+      plots: redrawPlots(this.state().plots),
+    });
   }
 
   /**
    * Reset the control panel.
    */
   resetControlPanel(): void {
-    this.store.dispatch(actions.resetControlPanel());
+    this.formGroup.reset(
+      usageFormValues(this.state().usageResponse.response.parameters)
+    );
   }
 
   /**
    * Reset the plot settings.
    */
   resetPlotSettings(): void {
-    this.store.dispatch(actions.resetSettings());
+    resetPlotSettings({
+      currentPlots: this.state().plots,
+      defaultPlots: defaultPlots(),
+    });
+  }
+
+  /**
+   * Reset the state.
+   */
+  resetState(): void {
+    const serviceCallInfo = gmmUtils.serviceCallInfo({
+      multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam,
+      serviceName: this.state().serviceCallInfo.serviceName,
+      serviceResponses: this.state().serviceResponses,
+      serviceUrl: this.serviceUrl,
+      values: this.formGroup.getRawValue(),
+    });
+
+    this.updateState({
+      serviceCallInfo,
+      serviceResponses: null,
+    });
+
+    this.createPlots();
+  }
+
+  /**
+   * Update state.
+   *
+   * @param state Partial new state to update
+   */
+  updateState(state: Partial<AppState>): void {
+    this.state.set({
+      ...this.state(),
+      ...state,
+    });
+  }
+
+  /**
+   * Add validators to form controls.
+   *
+   * @param form The form group
+   */
+  private addValidators(form: FormGroupControls<FormControls>): void {
+    const required = (control: AbstractControl) => Validators.required(control);
+
+    form.controls.derivePathParams.addValidators(required);
+    form.controls.dip.addValidators(required);
+    form.controls.gmmSource.addValidators(required);
+    form.controls.hangingWall.addValidators(required);
+    form.controls.multiSelectableParam.addValidators(required);
+    form.controls.Mw.addValidators(required);
+    form.controls.vs30.addValidators(required);
+    form.controls.width.addValidators(required);
+    form.controls.zTor.addValidators(required);
+    form.controls.rake.addValidators(required);
+    form.controls.rJB.addValidators(required);
+    form.controls.rRup.addValidators(required);
+    form.controls.rX.addValidators(required);
+    form.controls.vs30.addValidators(required);
+    form.controls.width.addValidators(required);
+    form.controls.zTor.addValidators(required);
+
+    form.updateValueAndValidity();
+  }
+
+  private handleServiceResponses(serviceResponses: GmmSpectraResponse[]): 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: GmmSpectraUsage): void {
+    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/spectra/state/app.state.ts b/projects/nshmp-apps/src/app/gmm/spectra/state/app.state.ts
index cb9dd0460da9b153fba8a12cf802ad7bb3e86610..290ca784420554c1f56577c5c498ac64cec52952 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/state/app.state.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/state/app.state.ts
@@ -1,41 +1,17 @@
-import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp';
+import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   GmmSpectraResponse,
   GmmSpectraUsage,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata';
-import {
-  createFormGroupState,
-  disable,
-  FormGroupState,
-  updateGroup,
-} from 'ngrx-forms';
-import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared';
 
-import {FormControls} from '../models/form-controls.model';
 import {SpectraPlot} from '../models/spectra-plot.model';
-import {DEFAULT_FORM_VALUES, defaultPlots} from '../utils/app.default-values';
-
-/** Control form id for ngrx-forms */
-export const CONTROL_FORM_ID = '[ngrx-forms] Response Spectra App Control Form';
-
-const formState = createFormGroupState<FormControls>(CONTROL_FORM_ID, {
-  ...DEFAULT_FORM_VALUES,
-});
-
-/**
- * Initial state for the control panel
- */
-export const INITIAL_CONTROL_FORM_STATE = updateGroup<FormControls>({
-  showEpistemicUncertainty: disable,
-})(formState);
+import {defaultPlots} from '../utils/app.default-values';
 
 /**
  * Spectra application state.
  */
-export interface AppState extends SharedAppState {
-  /** Control panel form field state */
-  controlForm: FormGroupState<FormControls>;
+export interface AppState {
   /** Plots */
   plots: Map<string, SpectraPlot>;
   /** Service call info */
@@ -45,21 +21,20 @@ export interface AppState extends SharedAppState {
   /** Supported IMTs */
   supportedImts: EnumParameterValues[];
   /** GMM usages */
-  usageResponses: GmmSpectraUsage;
+  usageResponse: GmmSpectraUsage;
 }
 
 /**
  * Application initial state.
  */
 export const INITIAL_STATE: AppState = {
-  controlForm: INITIAL_CONTROL_FORM_STATE,
   plots: defaultPlots(),
   serviceCallInfo: {
     serviceCalls: [],
     serviceName: 'Response Spectra',
-    usage: null,
+    usage: [],
   },
   serviceResponses: [],
   supportedImts: [],
-  usageResponses: null,
+  usageResponse: null,
 };
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/utils/app.default-values.ts b/projects/nshmp-apps/src/app/gmm/spectra/utils/app.default-values.ts
index b5c1a07119148bb73188d7f35fc235a5a0401181..34752e16fadeac616b19497121ae072a9546b2f8 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/utils/app.default-values.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/utils/app.default-values.ts
@@ -1,15 +1,14 @@
-import {GmmFormControlIds, GmmSource, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {GmmFormControlIds, 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,
   GmmSpectraUsageParameters,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
-import {box, createFormGroupState} from 'ngrx-forms';
 
 import {FormControls} from '../models/form-controls.model';
 import {SpectraPlot} from '../models/spectra-plot.model';
@@ -26,11 +25,11 @@ export const DEFAULT_FORM_VALUES: FormControls = {
   derivePathParams: true,
   dip: null,
   gmmGroupType: GmmGroupType.ACTIVE_CRUST,
-  gmmSource: box([] as GmmSource[]),
+  gmmSource: [],
   hangingWall: true,
   multiSelectableParam: GmmFormControlIds.GMM,
   Mw: null,
-  MwMulti: box([] as number[]),
+  MwMulti: [],
   rake: 0,
   rakeButton: 0,
   rJB: null,
@@ -38,7 +37,7 @@ export const DEFAULT_FORM_VALUES: FormControls = {
   rX: null,
   showEpistemicUncertainty: false,
   vs30: null,
-  vs30Multi: box([] as number[]),
+  vs30Multi: [],
   width: null,
   z1p0: null,
   z2p5: null,
@@ -53,20 +52,20 @@ export const DEFAULT_FORM_VALUES: FormControls = {
  *
  * @param parameters The service parameters
  */
-export function defaultFormValues(
+export function usageFormValues(
   parameters: GmmSpectraUsageParameters
 ): FormControls {
   return {
     ...DEFAULT_FORM_VALUES,
     dip: parameters.dip.value as number,
     Mw: parameters.Mw.value as number,
-    MwMulti: box([parameters.Mw.value as number]),
+    MwMulti: [parameters.Mw.value as number],
     rake: parameters.rake.value as number,
     rJB: parameters.rJB.value as number,
     rRup: parameters.rRup.value as number,
     rX: parameters.rX.value as number,
     vs30: parameters.vs30.value as number,
-    vs30Multi: box([parameters.vs30.value as number]),
+    vs30Multi: [parameters.vs30.value as number],
     width: parameters.width.value as number,
     z1p0: parameters.z1p0.value as number,
     z2p5: parameters.z2p5.value as number,
@@ -84,23 +83,13 @@ export function defaultPlots(): Map<string, SpectraPlot> {
   plots.set(gmmUtils.PlotType.MEANS, {
     label: 'Response Spectra',
     plotData: meanPlotData,
-    settingsForm: createFormGroupState<NshmpPlotSettings>(
-      SPECTRA_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/spectra/utils/app.utils.ts b/projects/nshmp-apps/src/app/gmm/spectra/utils/app.utils.ts
index 0929955f287e3a067f4a171425f524f05ad8a1aa..aaeb3a02070f01a2814050f0ddab7b40483c014a 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/utils/app.utils.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/utils/app.utils.ts
@@ -1,11 +1,6 @@
+import {FormControl} from '@angular/forms';
+import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {Maths} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/calc';
-import {
-  disable,
-  FormControlState,
-  FormGroupState,
-  setValue,
-  updateGroup,
-} from 'ngrx-forms';
 
 import {FormControls} from '../models/form-controls.model';
 
@@ -18,19 +13,18 @@ const precision = 3;
  * @param state The application state
  */
 export function calcHypocenterDepth(
-  state: FormGroupState<FormControls>
-): FormGroupState<FormControls> {
-  if (!state.value.zHypCentered) {
-    return state;
+  form: FormGroupControls<FormControls>
+): void {
+  const values = form.getRawValue();
+
+  if (!values.zHypCentered) {
+    return;
   }
 
-  const values = state.value;
   const dip = toRadians(values.dip);
   const zHyp = values.zTor + (Math.sin(dip) * values.width) / 2.0;
 
-  return updateGroup<FormControls>({
-    zHyp: control => updateControl(control, zHyp, values.zHypCentered),
-  })(state);
+  updateControl(form.controls.zHyp, zHyp, values.zHypCentered);
 }
 
 /**
@@ -39,20 +33,22 @@ export function calcHypocenterDepth(
  * @param state Application state
  */
 export function calcPathParameters(
-  state: FormGroupState<FormControls>
-): FormGroupState<FormControls> {
-  if (!state.value.derivePathParams) {
-    return state;
+  form: FormGroupControls<FormControls>
+): void {
+  const values = form.getRawValue();
+
+  if (!values.derivePathParams) {
+    return;
   }
 
-  const values = state.value;
   const rX = values.rX;
   const zTor = values.zTor;
   const footwall = !values.hangingWall;
   let rRup = Math.hypot(rX, zTor);
 
   if (footwall) {
-    return updatePathFormValues(state, {rJB: Math.abs(rX), rRup, rX});
+    updatePathFormValues(form, {rJB: Math.abs(rX), rRup, rX});
+    return;
   }
 
   const δ = toRadians(values.dip);
@@ -66,7 +62,8 @@ export function calcPathParameters(
   const rCut1 = h1 * sinδ;
 
   if (rX < rCut1) {
-    return updatePathFormValues(state, {rJB, rRup, rX});
+    updatePathFormValues(form, {rJB, rRup, rX});
+    return;
   }
 
   const zBot = zTor + Wz;
@@ -75,7 +72,8 @@ export function calcPathParameters(
 
   if (rX >= rCut2) {
     rRup = Math.hypot(zBot, rJB);
-    return updatePathFormValues(state, {rJB, rRup, rX});
+    updatePathFormValues(form, {rJB, rRup, rX});
+    return;
   }
 
   /*
@@ -83,7 +81,7 @@ export function calcPathParameters(
    * to top and bottom of fault.
    */
   rRup = h1 + (h2 - h1) * ((rX - rCut1) / (rCut2 - rCut1));
-  return updatePathFormValues(state, {rJB, rRup, rX});
+  updatePathFormValues(form, {rJB, rRup, rX});
 }
 
 /**
@@ -103,20 +101,18 @@ function toRadians(value: number): number {
  * @param path Path parameters
  */
 function updatePathFormValues(
-  state: FormGroupState<FormControls>,
+  form: FormGroupControls<FormControls>,
   path: {
     rJB: number;
     rRup: number;
     rX: number;
   }
-): FormGroupState<FormControls> {
-  return updateGroup<FormControls>({
-    rJB: control =>
-      updateControl(control, path.rJB, state.value.derivePathParams),
-    rRup: control =>
-      updateControl(control, path.rRup, state.value.derivePathParams),
-    rX: control => updateControl(control, path.rX),
-  })(state);
+): void {
+  const values = form.getRawValue();
+
+  updateControl(form.controls.rJB, path.rJB, values.derivePathParams);
+  updateControl(form.controls.rRup, path.rRup, values.derivePathParams);
+  updateControl(form.controls.rX, path.rX);
 }
 
 /**
@@ -127,13 +123,17 @@ function updatePathFormValues(
  * @param disabled Whether control is disabled
  */
 function updateControl(
-  control: FormControlState<number>,
+  control: FormControl<number>,
   value: number,
   disabled = false
-): FormControlState<number> {
-  control = setValue(control, value ? Maths.round(value, precision) : value);
+): void {
+  value = value ? Maths.round(value, precision) : value;
+
+  if (!isNaN(value) && control.value !== value) {
+    control.setValue(value);
+  }
+
   if (disabled) {
-    control = disable(control);
+    control.disable();
   }
-  return control;
 }
diff --git a/projects/nshmp-apps/src/app/gmm/spectra/utils/response.handler.ts b/projects/nshmp-apps/src/app/gmm/spectra/utils/response.handler.ts
index ba7262480c89b4a71468e25858bbe09afa7cb38f..85a808715e4b4027d5b64e24a5139ad63104743b 100644
--- a/projects/nshmp-apps/src/app/gmm/spectra/utils/response.handler.ts
+++ b/projects/nshmp-apps/src/app/gmm/spectra/utils/response.handler.ts
@@ -1,4 +1,5 @@
-import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm';
+import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm';
+import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp';
 import {
   GmmSeries,
   GmmSpectraResponse,
@@ -6,8 +7,10 @@ import {
   TreeValues,
 } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services';
 
+import {FormControls} from '../models/form-controls.model';
 import {SpectraPlot} from '../models/spectra-plot.model';
 import {AppState} from '../state/app.state';
+import {defaultPlots} from './app.default-values';
 
 /**
  * Transform Gmm distance service responses to plots.
@@ -15,16 +18,17 @@ import {AppState} from '../state/app.state';
  * @param state The application state
  */
 export function serviceResponseToPlotData(
-  state: AppState
+  state: AppState,
+  form: FormGroupControls<FormControls>
 ): Map<string, SpectraPlot> {
-  if (state.serviceResponses.length === 0) {
-    return state.plots;
+  if (state.serviceResponses === null || state.serviceResponses?.length === 0) {
+    return defaultPlots();
   }
 
   const plots = new Map<string, SpectraPlot>();
   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 xValues = state.serviceResponses
     .map(serviceResponse =>