---
name: ngrx-resource-api
description: This skill should be used when implementing Angular's Resource API with NgRx Signal Store for reactive data management. Use when creating signal stores that load data reactively, need automatic race condition prevention, or require declarative resource management without RxJS. Applies to data-access libraries, feature stores with API integration, and components needing reactive filtering or pagination.
---

# NgRx Resource API

## Overview

This skill enables integration of Angular's Resource API with NgRx Signal Store to create reactive data flows without RxJS while automatically preventing race conditions. The Resource API handles concurrent request management declaratively, eliminating manual `switchMap` or `takeUntilDestroyed` patterns.

## Core Architectural Concepts

### Reactive Flow Graph

Establish three clear interaction points in the store:

1. **Filter signals trigger resource loading** - Parameter changes automatically reload resources
2. **Methods explicitly invoke operations** - Use `signalMethod` for user-triggered actions
3. **Computed signals derive view models** - Transform loaded data for component consumption

### The withProps Pattern for Dependency Injection

Inject services via `withProps` with underscore-prefixed properties to mark them as internal implementation details:

```typescript
withProps(() => ({
  _dataService: inject(DataService),
  _notificationService: inject(ToastService),
}))
```

**Benefits:**
- Centralizes dependency injection in one location
- Clear distinction between internal (prefixed) and public properties
- Services available to all subsequent feature sections

## Implementation Steps

### Step 1: Inject Services with withProps

Create the initial `withProps` section to inject required services:

```typescript
export const MyStore = signalStore(
  withProps(() => ({
    _dataService: inject(DataService),
    _toastService: inject(ToastService),
  })),
  // ... additional features
);
```

**Naming convention:** Prefix all injected services with underscore (`_`) to indicate internal use.

### Step 2: Define Filter State

Add state properties that will serve as resource parameters:

```typescript
withState({
  filter: {
    searchTerm: '',
    category: '',
  } as MyFilter,
})
```

### Step 3: Configure Resources

In a subsequent `withProps` section, create resources that reference the injected services and state:

```typescript
withProps((store) => ({
  _itemsResource: resource({
    params: store.filter,
    loader: (loaderParams) => {
      const filter = loaderParams.params;
      const abortSignal = loaderParams.abortSignal;
      return store._dataService.loadItems(filter, abortSignal);
    }
  })
}))
```

**Key points:**
- Resources automatically reload when `params` signal changes
- `abortSignal` enables automatic cancellation of in-flight requests
- Loader must return a Promise (use `.findPromise()` if service returns Observable)

### Step 4: Expose Read-Only Resources (Optional)

If the resource should be accessible to consumers, expose it as read-only:

```typescript
withProps((store) => ({
  itemsResource: store._itemsResource.asReadonly(),
}))
```

**Pattern:** Internal resources use underscore prefix, public versions are read-only without prefix.

### Step 5: Create Signal Methods for Updates

Use `signalMethod` for actions that update state and trigger resource reloads:

```typescript
withMethods((store) => ({
  updateFilter: signalMethod<MyFilter>((filter) => {
    patchState(store, { filter });
  }),

  refresh: () => {
    store._itemsResource.reload();
  }
}))
```

**Important:** `signalMethod` implementations are **untracked by convention** - they don't re-execute when signals change. This provides explicit control flow.

### Step 6: Add Error Handling

Use `withHooks` to react to resource errors:

```typescript
withHooks({
  onInit: (store) => {
    effect(() => {
      const error = store._itemsResource.error();
      if (error) {
        store._toastService.show('Error: ' + getMessage(error));
      }
    });
  }
})
```

**Pattern:** Error effects should be read-only - they observe errors and trigger side effects, but don't modify state.

## Common Patterns

### Template Integration with linkedSignal

For two-way form binding that synchronizes with the store:

```typescript
export class MyComponent {
  #store = inject(MyStore);

  // Create linked signal for form field
  searchTerm = linkedSignal(() => this.#store.filter().searchTerm);

  // Combine form fields into filter object
  #linkedFilter = computed(() => ({
    searchTerm: this.searchTerm(),
    // ... other fields
  }));

  constructor() {
    // Sync form changes back to store
    this.#store.updateFilter(this.#linkedFilter);
  }
}
```

**Benefits:**
- Two-way binding for forms
- Automatic store synchronization
- Type-safe filter construction

### Working with Resource Data

Access resource data through resource signals:

```typescript
withComputed((store) => ({
  items: computed(() => store._itemsResource.value() ?? []),
  isLoading: computed(() => store._itemsResource.isLoading()),
  hasError: computed(() => store._itemsResource.hasError()),
}))
```

**Available signals:**
- `value()` - The loaded data (undefined while loading)
- `isLoading()` - Loading state boolean
- `hasError()` - Error state boolean
- `error()` - Error object if present
- `status()` - Overall status: 'idle' | 'loading' | 'resolved' | 'error'

### Updating Resource Data Locally

For temporary working copies before server writes:

```typescript
withMethods((store) => ({
  updateLocalItem: (id: string, changes: Partial<Item>) => {
    store._itemsResource.update((currentItems) => {
      return currentItems.map(item =>
        item.id === id ? { ...item, ...changes } : item
      );
    });
  }
}))
```

**Note:** This pattern feels unconventional but aligns with maintaining temporary working copies before server persistence.

### Multiple Resources in One Store

Combine multiple resources for complex data requirements:

```typescript
withProps((store) => ({
  _itemsResource: resource({
    params: store.filter,
    loader: (params) => store._dataService.loadItems(params.params, params.abortSignal)
  }),

  _detailsResource: resource({
    params: store.selectedId,
    loader: (params) => {
      if (!params.params) return Promise.resolve(null);
      return store._dataService.loadDetails(params.params, params.abortSignal);
    }
  })
}))
```

**Pattern:** Each resource has independent params and loading state, but can share service instances.

## Important Considerations

### Race Condition Prevention

The Resource API automatically handles race conditions:
- New requests automatically cancel pending requests
- No need for `switchMap`, `takeUntilDestroyed`, or manual abort handling
- Declarative parameter changes trigger clean cancellation

### Untracked Signal Methods

`signalMethod` implementations deliberately skip reactive tracking:
- Provides explicit, predictable control flow
- Prevents unexpected re-executions from signal changes
- Makes side effects obvious at call sites

### Loader Function Requirements

Loaders must:
- Return a `Promise` (not Observable)
- Accept and pass through the `abortSignal` to enable cancellation
- Handle the signal in the underlying HTTP call

**Converting Observables:**
```typescript
loader: (params) => {
  return firstValueFrom(
    this._service.load$(params.params)
      .pipe(takeUntilDestroyed(this._destroyRef))
  );
}
```

### Resource Lifecycle

Resources maintain their own state machine:
1. **Idle** - Initial state before first load
2. **Loading** - Request in progress
3. **Resolved** - Data loaded successfully
4. **Error** - Request failed

State transitions automatically trigger reactive updates to dependent computeds and effects.

## When to Use This Pattern

**Use Resource API with Signal Store when:**
- Loading data based on reactive filter/search parameters
- Need automatic race condition handling for concurrent requests
- Want declarative data loading without RxJS subscriptions
- Building stores with frequently changing query parameters
- Implementing pagination, filtering, or search features

**Consider alternatives when:**
- Simple one-time data loads (use `rxMethod` or direct service calls)
- Complex Observable chains with multiple operators needed
- Need fine-grained control over request timing/caching
- Working with WebSocket or SSE streams (use `rxMethod` instead)
