Implement NgRx store with actions and reducers, build selectors, create effects for async operations, configure entity adapters, and integrate HTTP APIs with state management.
From angular-development-assistantnpx claudepluginhub pluginagentmarketplace/custom-plugin-angular --plugin angular-development-assistantThis skill uses the workspace's default tool permissions.
assets/README.mdassets/config.yamlassets/store.template.tsreferences/GUIDE.mdreferences/README.mdreferences/SELECTORS.mdscripts/README.mdscripts/generate-store.shscripts/helper.pySearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
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) {}
}
// 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());
}
}
// 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]) => {
// ...
});
// 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' });
// 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
) {}
}
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
});
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);
@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();
}
}
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);
}
}
@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 { }
@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
);
}
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();
});
});
});
// 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
) {}
}
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>