Angular CLI schematics for generating NgRx state management code including actions, reducers, effects, selectors, and feature modules.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
NgRx container component generation schematic that creates smart container components that connect to the NgRx store and manage state interactions. Container components serve as the bridge between the state management layer and presentation components.
Generates Angular components that connect to NgRx store for state management and dispatch actions.
# Basic container component
ng generate @ngrx/schematics:container UserList
# Container with custom state interface
ng generate @ngrx/schematics:container ProductCatalog --stateInterface=CatalogState
# Container with specific state path
ng generate @ngrx/schematics:container OrderDashboard --state=order/**
* Container schematic configuration interface
*/
interface ContainerSchema {
/** Name of the container component */
name: string;
/** Path where container files should be generated */
path?: string;
/** Angular project to target */
project?: string;
/** Specifies if the style will be in the ts file */
inlineStyle?: boolean;
/** Specifies if the template will be in the ts file */
inlineTemplate?: boolean;
/** Specifies the view encapsulation strategy */
viewEncapsulation?: 'Emulated' | 'Native' | 'None';
/** Specifies the change detection strategy */
changeDetection?: 'Default' | 'OnPush';
/** The prefix to apply to generated selectors */
prefix?: string;
/** The file extension or preprocessor to use for style files */
style?: string;
/** When true, does not create test files */
skipTests?: boolean;
/** Generate files without creating a folder */
flat?: boolean;
/** Flag to skip the module import */
skipImport?: boolean;
/** The selector to use for the component */
selector?: string;
/** Allows specification of the declaring module */
module?: string;
/** Specifies if declaring module exports the component */
export?: boolean;
/** State slice to connect to */
state?: string;
/** Name of the state interface */
stateInterface?: string;
/** Specifies whether to create a unit test or an integration test */
testDepth?: 'unit' | 'integration';
/** Whether the generated component is standalone */
standalone?: boolean;
/** Specifies if the style will contain :host { display: block; } */
displayBlock?: boolean;
}Creates a complete Angular component with NgRx store integration:
// Generated container component
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as UserActions from '../state/user.actions';
import * as fromUser from '../state';
import { User } from '../models/user.model';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit {
users$: Observable<User[]>;
loading$: Observable<boolean>;
error$: Observable<string | null>;
selectedUser$: Observable<User | null>;
constructor(private store: Store) {
this.users$ = this.store.select(fromUser.selectAllUsers);
this.loading$ = this.store.select(fromUser.selectUsersLoading);
this.error$ = this.store.select(fromUser.selectUsersError);
this.selectedUser$ = this.store.select(fromUser.selectSelectedUser);
}
ngOnInit(): void {
this.loadUsers();
}
loadUsers(): void {
this.store.dispatch(UserActions.loadUsers());
}
selectUser(user: User): void {
this.store.dispatch(UserActions.selectUser({ userId: user.id }));
}
createUser(user: User): void {
this.store.dispatch(UserActions.createUser({ user }));
}
updateUser(user: User): void {
this.store.dispatch(UserActions.updateUser({ user }));
}
deleteUser(userId: string): void {
this.store.dispatch(UserActions.deleteUser({ id: userId }));
}
clearSelection(): void {
this.store.dispatch(UserActions.clearSelection());
}
}Usage Examples:
# Generate user list container
ng generate @ngrx/schematics:container UserList --state=user
# Generate product catalog container
ng generate @ngrx/schematics:container ProductCatalog --path=src/app/catalog --state=product
# Generate order dashboard with custom state interface
ng generate @ngrx/schematics:container OrderDashboard --stateInterface=OrderStateCreates a complete template with state binding and event handling:
<!-- Generated container template -->
<div class="user-list-container">
<h2>Users</h2>
<!-- Loading indicator -->
<div *ngIf="loading$ | async" class="loading">
Loading users...
</div>
<!-- Error display -->
<div *ngIf="error$ | async as error" class="error">
Error: {{ error }}
<button (click)="loadUsers()">Retry</button>
</div>
<!-- User list -->
<div *ngIf="!(loading$ | async) && !(error$ | async)">
<div class="user-actions">
<button (click)="loadUsers()">Refresh</button>
<button (click)="clearSelection()">Clear Selection</button>
</div>
<div class="user-grid">
<div
*ngFor="let user of users$ | async; trackBy: trackByUserId"
class="user-card"
[class.selected]="(selectedUser$ | async)?.id === user.id"
(click)="selectUser(user)">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>Status: {{ user.active ? 'Active' : 'Inactive' }}</p>
<div class="user-actions">
<button (click)="updateUser(user); $event.stopPropagation()">
Edit
</button>
<button (click)="deleteUser(user.id); $event.stopPropagation()">
Delete
</button>
</div>
</div>
</div>
<!-- No users message -->
<div *ngIf="(users$ | async)?.length === 0" class="no-users">
No users found.
</div>
</div>
<!-- Selected user details -->
<div *ngIf="selectedUser$ | async as selectedUser" class="selected-user">
<h3>Selected User</h3>
<pre>{{ selectedUser | json }}</pre>
</div>
</div>Creates basic styles for the container component:
// Generated container styles
.user-list-container {
padding: 20px;
.loading, .error {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
}
.loading {
background-color: #e3f2fd;
color: #1976d2;
}
.error {
background-color: #ffebee;
color: #c62828;
button {
margin-left: 10px;
padding: 5px 10px;
background-color: #c62828;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
}
.user-actions {
margin: 10px 0;
button {
margin-right: 10px;
padding: 8px 16px;
background-color: #1976d2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: #1565c0;
}
}
}
.user-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
margin: 20px 0;
}
.user-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&.selected {
border-color: #1976d2;
background-color: #e3f2fd;
}
h3 {
margin: 0 0 8px 0;
color: #333;
}
p {
margin: 4px 0;
color: #666;
}
.user-actions {
margin-top: 12px;
button {
margin-right: 8px;
padding: 4px 8px;
font-size: 12px;
}
}
}
.no-users {
text-align: center;
padding: 40px;
color: #666;
font-style: italic;
}
.selected-user {
margin-top: 20px;
padding: 16px;
background-color: #f5f5f5;
border-radius: 8px;
h3 {
margin-top: 0;
}
pre {
background-color: white;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
}
}The container follows the smart/presentational component pattern:
/**
* Container component responsibilities (Smart Component)
*/
interface SmartComponentPattern {
/** State management */
stateSubscription: 'Subscribe to store state via selectors';
/** Action dispatching */
actionDispatching: 'Dispatch actions to modify state';
/** Data fetching */
dataFetching: 'Trigger data loading operations';
/** Business logic */
businessLogic: 'Handle complex business operations';
/** Route handling */
routeHandling: 'React to route changes';
}
/**
* Presentational component pattern (for reference)
*/
interface PresentationalComponentPattern {
/** Pure display */
pureDisplay: 'Display data received via @Input()';
/** Event emission */
eventEmission: 'Emit events via @Output()';
/** No dependencies */
noDependencies: 'No direct dependencies on services';
/** Reusable */
reusable: 'Can be reused in different contexts';
}Generated containers can include advanced patterns:
// Advanced container with view models
export class UserListComponent implements OnInit, OnDestroy {
// View model selector combining multiple state slices
viewModel$ = this.store.select(fromUser.selectUserListViewModel);
// Track subscriptions for cleanup
private destroy$ = new Subject<void>();
constructor(private store: Store) {}
ngOnInit(): void {
this.loadUsers();
// Subscribe to route params for filtering
this.route.params.pipe(
takeUntil(this.destroy$)
).subscribe(params => {
if (params['filter']) {
this.store.dispatch(UserActions.setFilter({ filter: params['filter'] }));
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
// Track by function for performance
trackByUserId(index: number, user: User): string {
return user.id;
}
// Bulk operations
selectMultiple(userIds: string[]): void {
this.store.dispatch(UserActions.selectMultiple({ userIds }));
}
deleteMultiple(userIds: string[]): void {
this.store.dispatch(UserActions.deleteMultiple({ userIds }));
}
}Generated containers include comprehensive testing setup:
// Container component testing
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let store: MockStore;
let mockUserState: UserState;
beforeEach(() => {
mockUserState = {
users: [
{ id: '1', name: 'John', email: 'john@example.com', active: true },
{ id: '2', name: 'Jane', email: 'jane@example.com', active: false }
],
loading: false,
error: null,
selectedUserId: null
};
TestBed.configureTestingModule({
declarations: [UserListComponent],
imports: [CommonModule],
providers: [
provideMockStore({ initialState: { user: mockUserState } })
]
});
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
store = TestBed.inject(MockStore);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should load users on init', () => {
spyOn(store, 'dispatch');
component.ngOnInit();
expect(store.dispatch).toHaveBeenCalledWith(UserActions.loadUsers());
});
it('should select user', () => {
const user = mockUserState.users[0];
spyOn(store, 'dispatch');
component.selectUser(user);
expect(store.dispatch).toHaveBeenCalledWith(
UserActions.selectUser({ userId: user.id })
);
});
it('should display users', fakeAsync(() => {
fixture.detectChanges();
tick();
const userCards = fixture.debugElement.queryAll(By.css('.user-card'));
expect(userCards.length).toBe(2);
expect(userCards[0].nativeElement.textContent).toContain('John');
}));
it('should show loading state', fakeAsync(() => {
store.setState({ user: { ...mockUserState, loading: true } });
fixture.detectChanges();
tick();
const loadingElement = fixture.debugElement.query(By.css('.loading'));
expect(loadingElement).toBeTruthy();
expect(loadingElement.nativeElement.textContent).toContain('Loading');
}));
});Generated containers integrate with Angular routing and modules:
// Module integration
@NgModule({
declarations: [UserListComponent],
imports: [
CommonModule,
RouterModule.forChild([
{ path: '', component: UserListComponent }
])
]
})
export class UserModule {}
// Route integration with resolver
export const UserListRoute: Route = {
path: 'users',
component: UserListComponent,
resolve: {
users: UserResolver
}
};Optimized containers use OnPush change detection strategy:
// Optimized container with OnPush
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent implements OnInit {
// All properties are observables for OnPush compatibility
users$ = this.store.select(fromUser.selectAllUsers);
loading$ = this.store.select(fromUser.selectUsersLoading);
error$ = this.store.select(fromUser.selectUsersError);
constructor(private store: Store) {}
// Methods remain the same - they dispatch actions
loadUsers(): void {
this.store.dispatch(UserActions.loadUsers());
}
}