Claude Code Plugins

Community-maintained marketplace

Feedback

Implement NgRx store with actions and reducers, build selectors, create effects for async operations, configure entity adapters, and integrate HTTP APIs with state management.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name state-implementation
description Implement NgRx store with actions and reducers, build selectors, create effects for async operations, configure entity adapters, and integrate HTTP APIs with state management.

State Implementation Skill

Quick Start

Simple Service-Based State

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class UserStore {
  private usersSubject = new BehaviorSubject<User[]>([]);
  users$ = this.usersSubject.asObservable();

  constructor(private http: HttpClient) {}

  loadUsers() {
    this.http.get<User[]>('/api/users').subscribe(
      users => this.usersSubject.next(users)
    );
  }

  addUser(user: User) {
    this.http.post<User>('/api/users', user).subscribe(
      newUser => {
        const current = this.usersSubject.value;
        this.usersSubject.next([...current, newUser]);
      }
    );
  }
}

// Usage
export class UserListComponent {
  users$ = this.userStore.users$;

  constructor(private userStore: UserStore) {}
}

NgRx Basics

// 1. Define actions
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction(
  '[User] Load Users Success',
  props<{ users: User[] }>()
);
export const loadUsersError = createAction(
  '[User] Load Users Error',
  props<{ error: string }>()
);

// 2. Create reducer
const initialState: UserState = { users: [], loading: false };

export const userReducer = createReducer(
  initialState,
  on(loadUsers, state => ({ ...state, loading: true })),
  on(loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  on(loadUsersError, (state, { error }) => ({
    ...state,
    error,
    loading: false
  }))
);

// 3. Create effect
@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => loadUsersSuccess({ users })),
          catchError(error => of(loadUsersError({ error })))
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

// 4. Use in component
@Component({...})
export class UserListComponent {
  users$ = this.store.select(selectUsers);
  loading$ = this.store.select(selectLoading);

  constructor(private store: Store) {
    this.store.dispatch(loadUsers());
  }
}

NgRx Core Concepts

Store

// Dispatch action
this.store.dispatch(loadUsers());

// Select state
this.store.select(selectUsers).subscribe(users => {
  console.log(users);
});

// Select with observable
this.users$ = this.store.select(selectUsers);

// Multiple selects
this.store.select(selectUsers, selectLoading).subscribe(([users, loading]) => {
  // ...
});

Selectors

// Feature selector
export const selectUserState = createFeatureSelector<UserState>('users');

// Select from feature
export const selectUsers = createSelector(
  selectUserState,
  state => state.users
);

// Selector composition
export const selectActiveUsers = createSelector(
  selectUsers,
  users => users.filter(u => u.active)
);

// Memoized selector
export const selectUserById = (id: number) => createSelector(
  selectUsers,
  users => users.find(u => u.id === id)
);

// With props
export const selectUsersByRole = createSelector(
  selectUsers,
  (users: User[], { role }: { role: string }) =>
    users.filter(u => u.role === role)
);

// Usage with props
this.store.select(selectUsersByRole, { role: 'admin' });

Effects

// Side effect - HTTP call
@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => UserActions.loadUsersSuccess({ users })),
          catchError(error => of(UserActions.loadUsersError({ error })))
        )
      )
    )
  );

  // Non-dispatching effect
  logActions$ = createEffect(
    () => this.actions$.pipe(
      tap(action => console.log(action))
    ),
    { dispatch: false }
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

Entity Adapter

Setup

export interface User {
  id: number;
  name: string;
  email: string;
}

export const adapter = createEntityAdapter<User>({
  selectId: (user: User) => user.id,
  sortComparer: (a: User, b: User) => a.name.localeCompare(b.name)
});

export interface UserState extends EntityState<User> {
  loading: boolean;
  error: string | null;
}

const initialState = adapter.getInitialState({
  loading: false,
  error: null
});

Reducer with Adapter

export const userReducer = createReducer(
  initialState,
  on(loadUsers, state => ({ ...state, loading: true })),
  on(loadUsersSuccess, (state, { users }) =>
    adapter.setAll(users, { ...state, loading: false })
  ),
  on(addUserSuccess, (state, { user }) =>
    adapter.addOne(user, state)
  ),
  on(updateUserSuccess, (state, { user }) =>
    adapter.updateOne({ id: user.id, changes: user }, state)
  ),
  on(deleteUserSuccess, (state, { id }) =>
    adapter.removeOne(id, state)
  )
);

// Export selectors
export const {
  selectIds,
  selectEntities,
  selectAll,
  selectTotal
} = adapter.getSelectors(selectUserState);

Facade Pattern

@Injectable()
export class UserFacade {
  users$ = this.store.select(selectAllUsers);
  loading$ = this.store.select(selectUsersLoading);
  error$ = this.store.select(selectUsersError);

  constructor(private store: Store) {}

  loadUsers() {
    this.store.dispatch(loadUsers());
  }

  addUser(user: User) {
    this.store.dispatch(addUser({ user }));
  }

  updateUser(id: number, changes: Partial<User>) {
    this.store.dispatch(updateUser({ id, changes }));
  }

  deleteUser(id: number) {
    this.store.dispatch(deleteUser({ id }));
  }
}

// Component usage simplified
@Component({...})
export class UserListComponent {
  users$ = this.userFacade.users$;
  loading$ = this.userFacade.loading$;

  constructor(private userFacade: UserFacade) {
    this.userFacade.loadUsers();
  }
}

Angular Signals

import { signal, computed, effect } from '@angular/core';

// Create signal
const count = signal(0);

// Read value
console.log(count()); // 0

// Update value
count.set(1);
count.update(c => c + 1);

// Computed value
const doubled = computed(() => count() * 2);

// Effect
effect(() => {
  console.log(`Count is ${count()}`);
  console.log(`Doubled is ${doubled()}`);
});

// Signal-based state
@Component({...})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(c => c + 1);
  }
}

HTTP Integration

HttpClient with Interceptor

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    const authReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next.handle(authReq);
  }
}

// Register
@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule { }

Caching Strategy

@Injectable()
export class CachingService {
  private cache = new Map<string, any>();

  get<T>(key: string, request: Observable<T>, ttl: number = 3600000): Observable<T> {
    if (this.cache.has(key)) {
      return of(this.cache.get(key));
    }

    return request.pipe(
      tap(data => {
        this.cache.set(key, data);
        setTimeout(() => this.cache.delete(key), ttl);
      })
    );
  }
}

// Usage
getUsers() {
  return this.caching.get(
    'users',
    this.http.get<User[]>('/api/users'),
    5 * 60 * 1000 // 5 minutes
  );
}

Testing State

describe('User Store', () => {
  let store: MockStore;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [StoreModule.forRoot({ users: userReducer })]
    });
    store = TestBed.inject(Store) as MockStore;
  });

  it('should load users', () => {
    const action = loadUsers();
    const completion = loadUsersSuccess({ users: mockUsers });

    const effect$ = new UserEffects(
      hot('a', { a: action }),
      mockUserService
    ).loadUsers$;

    const result = cold('b', { b: completion });
    expect(effect$).toBeObservable(result);
  });

  it('should select users', (done) => {
    store.setState({ users: { users: mockUsers } });
    store.select(selectUsers).subscribe(users => {
      expect(users).toEqual(mockUsers);
      done();
    });
  });
});

Best Practices

  1. Normalize State: Flat structure, avoid nesting
  2. Single Responsibility: Each reducer handles one feature
  3. Use Facades: Simplify component-store interaction
  4. Memoize Selectors: Prevent unnecessary recalculations
  5. Handle Errors: Always include error states
  6. Lazy Load Stores: Register feature stores when needed
  7. Time-Travel Debugging: Use Redux DevTools

Advanced Patterns

Composition Pattern

// Combine multiple stores
@Injectable()
export class AppFacade {
  users$ = this.userFacade.users$;
  products$ = this.productFacade.products$;
  cart$ = this.cartFacade.cart$;

  constructor(
    private userFacade: UserFacade,
    private productFacade: ProductFacade,
    private cartFacade: CartFacade
  ) {}
}

Feature Flags

export const selectFeatureFlags = createFeatureSelector<FeatureFlags>('features');
export const selectFeatureEnabled = (feature: string) => createSelector(
  selectFeatureFlags,
  flags => flags[feature]?.enabled ?? false
);

// Component
<div *ngIf="featureEnabled$ | async">New Feature</div>

Resources