CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngrx--schematics

Angular CLI schematics for generating NgRx state management code including actions, reducers, effects, selectors, and feature modules.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

container-components.mddocs/

Container Components

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.

Capabilities

Container Schematic

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;
}

Generated Container Component

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=OrderState

Generated Template

Creates 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>

Generated Styles

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;
    }
  }
}

Smart vs Presentational Pattern

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';
}

Advanced Container Patterns

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 }));
  }
}

Container Testing

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');
  }));
});

Container Integration

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
  }
};

OnPush Change Detection

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());
  }
}

docs

action-generation.md

component-store.md

container-components.md

data-services.md

effect-generation.md

entity-management.md

feature-generation.md

index.md

ngrx-push-migration.md

reducer-generation.md

selector-generation.md

store-setup.md

utility-functions.md

tile.json