Skip to content

resource: obsolete loads keep the application unstable when params become unavailable #71765

Closed

Description

@shoutoutuoadi325

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Not established. A related streaming-resource cancellation report (#64625) was closed by its author without an associated fix.

Description

When a resource has an outstanding loader and its params become unavailable, the resource immediately changes status but does not abort the obsolete loader or release that loader's pending task. If the obsolete promise never settles, ApplicationRef.whenStable() remains pending indefinitely even though the resource is now idle or in a params-error state.

This affects params returning undefined, throwing ResourceParamsStatus.IDLE or ResourceParamsStatus.LOADING, and throwing an error. In the LOADING case, the resource is waiting on upstream params; its obsolete downstream request should no longer own a pending task.

The resource guide says outstanding loads are aborted when params change. This differs from the expected behavior of blocking stability while a valid current request is loading (as discussed in #60742).

Minimal reproduction

Executable regression tests: resource_spec.ts, with unavailable/error status cases in params_status_spec.ts. Run pnpm bazel test //packages/core/test/resource:resource; keeping the tests and restoring the pre-fix resource.ts reproduces the blocked stability calls.

Use the following as src/main.ts in an Angular application, wait for loader started in the console, then click Stop:

import {ApplicationRef, Component, inject, resource, signal} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="stop()">Stop</button>
    <p>Status: {{data.status()}}; aborted: {{aborted()}}</p>
  `,
})
class App {
  readonly params = signal<number | undefined>(1);
  readonly aborted = signal(false);
  private readonly app = inject(ApplicationRef);
  readonly data = resource({
    params: this.params,
    loader: ({abortSignal}) => {
      console.log('loader started');
      abortSignal.addEventListener('abort', () => this.aborted.set(true));
      // Cancellation must release stability even if a loader never settles.
      return new Promise<string>(() => {});
    },
  });

  stop() {
    this.params.set(undefined);
    void this.app.whenStable().then(() => console.log('stable after stop'));
  }
}

void bootstrapApplication(App);

Expected: Status: idle; aborted: true, and stable after stop is logged.

Actual: Status: idle; aborted: false, and the stability promise never resolves.

Environment

Angular main source commit fdb4517612cf191da3c3e2df251f9f79f026448b (22.3.0-next.3), macOS arm64. The repository's Node/Bazel resource suite is used to verify the behavior.

Anything else?

ResourceImpl.loadEffect() checks unavailable/stale params before calling abortInProgressLoad(). Cancelling before those early returns fixes the omitted cleanup while preserving the existing request identity checks. Regression tests also cover an obsolete promise resolving/rejecting after a new load starts, ensuring the new load retains its pending task and value.

Related: #64625. PR #71557 changes abort reasons/controller retention, but does not fix this early-return path.

Activity

  1. added this to the needsTriage milestone on Nov 2, 2026
  2. added this to the needsTriage milestone on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtime

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions