Spoosh
Injects

injectRead

Fetch data with automatic caching and triggering using Signals

Fetch data with automatic caching. Auto-fetches on mount. Returns Angular Signals for reactive data binding.

Basic Usage

@Component({
  selector: "app-user-list",
  template: `
    @if (loading()) {
      <div>Loading...</div>
    } @else if (error()) {
      <div>Error: {{ error()?.message }}</div>
    } @else {
      <ul>
        @for (user of data(); track user.id) {
          <li>{{ user.name }}</li>
        }
      </ul>
    }
  `,
})
export class UserListComponent {
  private users = injectRead((api) => api("users").GET());

  data = this.users.data;
  loading = this.users.loading;
  error = this.users.error;
}

With Query Parameters

@Component({
  selector: "app-user-list",
  template: `...`,
})
export class UserListComponent {
  private isReady = signal(false);

  private users = injectRead(
    (api) => api("users").GET({ query: { page: 1, limit: 10 } }),
    {
      enabled: this.isReady,
      tags: ["all", "custom-tag"],
    }
  );

  data = this.users.data;
  input = this.users.input;
}

Tag Modes

// Mode only - 'all' generates full hierarchy
injectRead((api) => api("users/:id/posts").GET({ params: { id: "123" } }), {
  tags: "all", // ['users', 'users/123', 'users/123/posts']
});

// Mode only - 'self' generates only exact path
injectRead((api) => api("users/:id/posts").GET({ params: { id: "123" } }), {
  tags: "self", // ['users/123/posts']
});

// Mode only - 'none' generates no tags
injectRead((api) => api("posts").GET(), { tags: "none" }); // []

// Custom tags only - replaces auto-generated tags
injectRead((api) => api("posts").GET(), {
  tags: ["custom", "dashboard"], // ['custom', 'dashboard']
});

// Mode + custom tags - 'all' mode combined with custom tags
injectRead((api) => api("users/:id/posts").GET({ params: { id: "123" } }), {
  tags: ["all", "dashboard"], // ['users', 'users/123', 'users/123/posts', 'dashboard']
});

// Mode + custom tags - 'self' mode combined with custom tags
injectRead((api) => api("users/:id/posts").GET({ params: { id: "123" } }), {
  tags: ["self", "dashboard"], // ['users/123/posts', 'dashboard']
});

Options

OptionTypeDefaultDescription
enabledboolean | Signal<bool>trueWhether to fetch automatically
tags'all' | 'self' | 'none' | string[]'all'Tag mode or custom tags
+ plugin options--Options from installed plugins

Returns

PropertyTypeDescription
dataSignal<TData | undefined>Response data signal
errorSignal<TError | undefined>Error signal if request failed
loadingSignal<boolean>Signal true during initial load
fetchingSignal<boolean>Signal true during any fetch
trigger(options?) => PromiseManually trigger fetch with optional overrides
abort() => voidAbort current request
inputSignal<object>The request input (query, body, params)
metaSignal<object>Plugin-provided metadata

Trigger with Override Options

The trigger function accepts optional parameters to override the request:

@Component({
  selector: "app-user-detail",
  template: `
    <button (click)="loadUser('1')">Load User 1</button>
    <button (click)="loadUser('2')">Load User 2</button>
    <button (click)="refresh()">Refresh</button>
    @if (data()) {
      <div>{{ data()?.name }}</div>
    }
  `,
})
export class UserDetailComponent {
  private user = injectRead((api) =>
    api("users/:id").GET({ params: { id: "1" } })
  );

  data = this.user.data;
  trigger = this.user.trigger;

  loadUser(id: string) {
    // Fetch with different params
    this.trigger({ params: { id } });
  }

  refresh() {
    // Force refetch (bypass cache)
    this.trigger({ force: true });
  }
}

You should avoid using custom options with trigger as much as possible. Most of the time, you can achieve the same effect by changing the enabled and input parameters in the initial request.

Trigger Options

OptionTypeDefaultDescription
paramsobject-Override URL path parameters
queryobject-Override query string parameters
bodyunknown-Override request body
forcebooleanfalseBypass cache and force fresh fetch

On-Demand Fetching

For cases where you don't want to fetch on mount (like download or print), use enabled: false and call trigger manually:

@Component({
  selector: "app-download-report",
  template: `
    <button (click)="download('monthly')" [disabled]="loading()">
      Download Monthly Report
    </button>
    <button (click)="download('yearly')" [disabled]="loading()">
      Download Yearly Report
    </button>
  `,
})
export class DownloadReportComponent {
  private report = injectRead(
    (api) => api("reports/:id").GET({ params: { id: "" } }), // placeholder
    { enabled: false }
  );

  loading = this.report.loading;

  async download(reportId: string) {
    const { data } = await this.report.trigger({ params: { id: reportId } });
    if (data) {
      // Process the downloaded report
      this.downloadFile(data.url, data.filename);
    }
  }

  private downloadFile(url: string, filename: string) {
    // Implementation
  }
}

On this page