or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-generation.mdcomponent-store.mdcontainer-components.mddata-services.mdeffect-generation.mdentity-management.mdfeature-generation.mdindex.mdngrx-push-migration.mdreducer-generation.mdselector-generation.mdstore-setup.mdutility-functions.md

container-components.mddocs/

0

# Container Components

1

2

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.

3

4

## Capabilities

5

6

### Container Schematic

7

8

Generates Angular components that connect to NgRx store for state management and dispatch actions.

9

10

```bash

11

# Basic container component

12

ng generate @ngrx/schematics:container UserList

13

14

# Container with custom state interface

15

ng generate @ngrx/schematics:container ProductCatalog --stateInterface=CatalogState

16

17

# Container with specific state path

18

ng generate @ngrx/schematics:container OrderDashboard --state=order

19

```

20

21

```typescript { .api }

22

/**

23

* Container schematic configuration interface

24

*/

25

interface ContainerSchema {

26

/** Name of the container component */

27

name: string;

28

/** Path where container files should be generated */

29

path?: string;

30

/** Angular project to target */

31

project?: string;

32

/** Specifies if the style will be in the ts file */

33

inlineStyle?: boolean;

34

/** Specifies if the template will be in the ts file */

35

inlineTemplate?: boolean;

36

/** Specifies the view encapsulation strategy */

37

viewEncapsulation?: 'Emulated' | 'Native' | 'None';

38

/** Specifies the change detection strategy */

39

changeDetection?: 'Default' | 'OnPush';

40

/** The prefix to apply to generated selectors */

41

prefix?: string;

42

/** The file extension or preprocessor to use for style files */

43

style?: string;

44

/** When true, does not create test files */

45

skipTests?: boolean;

46

/** Generate files without creating a folder */

47

flat?: boolean;

48

/** Flag to skip the module import */

49

skipImport?: boolean;

50

/** The selector to use for the component */

51

selector?: string;

52

/** Allows specification of the declaring module */

53

module?: string;

54

/** Specifies if declaring module exports the component */

55

export?: boolean;

56

/** State slice to connect to */

57

state?: string;

58

/** Name of the state interface */

59

stateInterface?: string;

60

/** Specifies whether to create a unit test or an integration test */

61

testDepth?: 'unit' | 'integration';

62

/** Whether the generated component is standalone */

63

standalone?: boolean;

64

/** Specifies if the style will contain :host { display: block; } */

65

displayBlock?: boolean;

66

}

67

```

68

69

### Generated Container Component

70

71

Creates a complete Angular component with NgRx store integration:

72

73

```typescript

74

// Generated container component

75

import { Component, OnInit } from '@angular/core';

76

import { Store } from '@ngrx/store';

77

import { Observable } from 'rxjs';

78

import * as UserActions from '../state/user.actions';

79

import * as fromUser from '../state';

80

import { User } from '../models/user.model';

81

82

@Component({

83

selector: 'app-user-list',

84

templateUrl: './user-list.component.html',

85

styleUrls: ['./user-list.component.scss']

86

})

87

export class UserListComponent implements OnInit {

88

users$: Observable<User[]>;

89

loading$: Observable<boolean>;

90

error$: Observable<string | null>;

91

selectedUser$: Observable<User | null>;

92

93

constructor(private store: Store) {

94

this.users$ = this.store.select(fromUser.selectAllUsers);

95

this.loading$ = this.store.select(fromUser.selectUsersLoading);

96

this.error$ = this.store.select(fromUser.selectUsersError);

97

this.selectedUser$ = this.store.select(fromUser.selectSelectedUser);

98

}

99

100

ngOnInit(): void {

101

this.loadUsers();

102

}

103

104

loadUsers(): void {

105

this.store.dispatch(UserActions.loadUsers());

106

}

107

108

selectUser(user: User): void {

109

this.store.dispatch(UserActions.selectUser({ userId: user.id }));

110

}

111

112

createUser(user: User): void {

113

this.store.dispatch(UserActions.createUser({ user }));

114

}

115

116

updateUser(user: User): void {

117

this.store.dispatch(UserActions.updateUser({ user }));

118

}

119

120

deleteUser(userId: string): void {

121

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

122

}

123

124

clearSelection(): void {

125

this.store.dispatch(UserActions.clearSelection());

126

}

127

}

128

```

129

130

**Usage Examples:**

131

132

```bash

133

# Generate user list container

134

ng generate @ngrx/schematics:container UserList --state=user

135

136

# Generate product catalog container

137

ng generate @ngrx/schematics:container ProductCatalog --path=src/app/catalog --state=product

138

139

# Generate order dashboard with custom state interface

140

ng generate @ngrx/schematics:container OrderDashboard --stateInterface=OrderState

141

```

142

143

### Generated Template

144

145

Creates a complete template with state binding and event handling:

146

147

```html

148

<!-- Generated container template -->

149

<div class="user-list-container">

150

<h2>Users</h2>

151

152

<!-- Loading indicator -->

153

<div *ngIf="loading$ | async" class="loading">

154

Loading users...

155

</div>

156

157

<!-- Error display -->

158

<div *ngIf="error$ | async as error" class="error">

159

Error: {{ error }}

160

<button (click)="loadUsers()">Retry</button>

161

</div>

162

163

<!-- User list -->

164

<div *ngIf="!(loading$ | async) && !(error$ | async)">

165

<div class="user-actions">

166

<button (click)="loadUsers()">Refresh</button>

167

<button (click)="clearSelection()">Clear Selection</button>

168

</div>

169

170

<div class="user-grid">

171

<div

172

*ngFor="let user of users$ | async; trackBy: trackByUserId"

173

class="user-card"

174

[class.selected]="(selectedUser$ | async)?.id === user.id"

175

(click)="selectUser(user)">

176

177

<h3>{{ user.name }}</h3>

178

<p>{{ user.email }}</p>

179

<p>Status: {{ user.active ? 'Active' : 'Inactive' }}</p>

180

181

<div class="user-actions">

182

<button (click)="updateUser(user); $event.stopPropagation()">

183

Edit

184

</button>

185

<button (click)="deleteUser(user.id); $event.stopPropagation()">

186

Delete

187

</button>

188

</div>

189

</div>

190

</div>

191

192

<!-- No users message -->

193

<div *ngIf="(users$ | async)?.length === 0" class="no-users">

194

No users found.

195

</div>

196

</div>

197

198

<!-- Selected user details -->

199

<div *ngIf="selectedUser$ | async as selectedUser" class="selected-user">

200

<h3>Selected User</h3>

201

<pre>{{ selectedUser | json }}</pre>

202

</div>

203

</div>

204

```

205

206

### Generated Styles

207

208

Creates basic styles for the container component:

209

210

```scss

211

// Generated container styles

212

.user-list-container {

213

padding: 20px;

214

215

.loading, .error {

216

padding: 10px;

217

margin: 10px 0;

218

border-radius: 4px;

219

}

220

221

.loading {

222

background-color: #e3f2fd;

223

color: #1976d2;

224

}

225

226

.error {

227

background-color: #ffebee;

228

color: #c62828;

229

230

button {

231

margin-left: 10px;

232

padding: 5px 10px;

233

background-color: #c62828;

234

color: white;

235

border: none;

236

border-radius: 4px;

237

cursor: pointer;

238

}

239

}

240

241

.user-actions {

242

margin: 10px 0;

243

244

button {

245

margin-right: 10px;

246

padding: 8px 16px;

247

background-color: #1976d2;

248

color: white;

249

border: none;

250

border-radius: 4px;

251

cursor: pointer;

252

253

&:hover {

254

background-color: #1565c0;

255

}

256

}

257

}

258

259

.user-grid {

260

display: grid;

261

grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));

262

gap: 16px;

263

margin: 20px 0;

264

}

265

266

.user-card {

267

border: 1px solid #ddd;

268

border-radius: 8px;

269

padding: 16px;

270

cursor: pointer;

271

transition: all 0.2s ease;

272

273

&:hover {

274

box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

275

}

276

277

&.selected {

278

border-color: #1976d2;

279

background-color: #e3f2fd;

280

}

281

282

h3 {

283

margin: 0 0 8px 0;

284

color: #333;

285

}

286

287

p {

288

margin: 4px 0;

289

color: #666;

290

}

291

292

.user-actions {

293

margin-top: 12px;

294

295

button {

296

margin-right: 8px;

297

padding: 4px 8px;

298

font-size: 12px;

299

}

300

}

301

}

302

303

.no-users {

304

text-align: center;

305

padding: 40px;

306

color: #666;

307

font-style: italic;

308

}

309

310

.selected-user {

311

margin-top: 20px;

312

padding: 16px;

313

background-color: #f5f5f5;

314

border-radius: 8px;

315

316

h3 {

317

margin-top: 0;

318

}

319

320

pre {

321

background-color: white;

322

padding: 10px;

323

border-radius: 4px;

324

overflow-x: auto;

325

}

326

}

327

}

328

```

329

330

### Smart vs Presentational Pattern

331

332

The container follows the smart/presentational component pattern:

333

334

```typescript { .api }

335

/**

336

* Container component responsibilities (Smart Component)

337

*/

338

interface SmartComponentPattern {

339

/** State management */

340

stateSubscription: 'Subscribe to store state via selectors';

341

/** Action dispatching */

342

actionDispatching: 'Dispatch actions to modify state';

343

/** Data fetching */

344

dataFetching: 'Trigger data loading operations';

345

/** Business logic */

346

businessLogic: 'Handle complex business operations';

347

/** Route handling */

348

routeHandling: 'React to route changes';

349

}

350

351

/**

352

* Presentational component pattern (for reference)

353

*/

354

interface PresentationalComponentPattern {

355

/** Pure display */

356

pureDisplay: 'Display data received via @Input()';

357

/** Event emission */

358

eventEmission: 'Emit events via @Output()';

359

/** No dependencies */

360

noDependencies: 'No direct dependencies on services';

361

/** Reusable */

362

reusable: 'Can be reused in different contexts';

363

}

364

```

365

366

### Advanced Container Patterns

367

368

Generated containers can include advanced patterns:

369

370

```typescript

371

// Advanced container with view models

372

export class UserListComponent implements OnInit, OnDestroy {

373

// View model selector combining multiple state slices

374

viewModel$ = this.store.select(fromUser.selectUserListViewModel);

375

376

// Track subscriptions for cleanup

377

private destroy$ = new Subject<void>();

378

379

constructor(private store: Store) {}

380

381

ngOnInit(): void {

382

this.loadUsers();

383

384

// Subscribe to route params for filtering

385

this.route.params.pipe(

386

takeUntil(this.destroy$)

387

).subscribe(params => {

388

if (params['filter']) {

389

this.store.dispatch(UserActions.setFilter({ filter: params['filter'] }));

390

}

391

});

392

}

393

394

ngOnDestroy(): void {

395

this.destroy$.next();

396

this.destroy$.complete();

397

}

398

399

// Track by function for performance

400

trackByUserId(index: number, user: User): string {

401

return user.id;

402

}

403

404

// Bulk operations

405

selectMultiple(userIds: string[]): void {

406

this.store.dispatch(UserActions.selectMultiple({ userIds }));

407

}

408

409

deleteMultiple(userIds: string[]): void {

410

this.store.dispatch(UserActions.deleteMultiple({ userIds }));

411

}

412

}

413

```

414

415

### Container Testing

416

417

Generated containers include comprehensive testing setup:

418

419

```typescript

420

// Container component testing

421

describe('UserListComponent', () => {

422

let component: UserListComponent;

423

let fixture: ComponentFixture<UserListComponent>;

424

let store: MockStore;

425

let mockUserState: UserState;

426

427

beforeEach(() => {

428

mockUserState = {

429

users: [

430

{ id: '1', name: 'John', email: 'john@example.com', active: true },

431

{ id: '2', name: 'Jane', email: 'jane@example.com', active: false }

432

],

433

loading: false,

434

error: null,

435

selectedUserId: null

436

};

437

438

TestBed.configureTestingModule({

439

declarations: [UserListComponent],

440

imports: [CommonModule],

441

providers: [

442

provideMockStore({ initialState: { user: mockUserState } })

443

]

444

});

445

446

fixture = TestBed.createComponent(UserListComponent);

447

component = fixture.componentInstance;

448

store = TestBed.inject(MockStore);

449

});

450

451

it('should create', () => {

452

expect(component).toBeTruthy();

453

});

454

455

it('should load users on init', () => {

456

spyOn(store, 'dispatch');

457

component.ngOnInit();

458

expect(store.dispatch).toHaveBeenCalledWith(UserActions.loadUsers());

459

});

460

461

it('should select user', () => {

462

const user = mockUserState.users[0];

463

spyOn(store, 'dispatch');

464

465

component.selectUser(user);

466

467

expect(store.dispatch).toHaveBeenCalledWith(

468

UserActions.selectUser({ userId: user.id })

469

);

470

});

471

472

it('should display users', fakeAsync(() => {

473

fixture.detectChanges();

474

tick();

475

476

const userCards = fixture.debugElement.queryAll(By.css('.user-card'));

477

expect(userCards.length).toBe(2);

478

expect(userCards[0].nativeElement.textContent).toContain('John');

479

}));

480

481

it('should show loading state', fakeAsync(() => {

482

store.setState({ user: { ...mockUserState, loading: true } });

483

fixture.detectChanges();

484

tick();

485

486

const loadingElement = fixture.debugElement.query(By.css('.loading'));

487

expect(loadingElement).toBeTruthy();

488

expect(loadingElement.nativeElement.textContent).toContain('Loading');

489

}));

490

});

491

```

492

493

### Container Integration

494

495

Generated containers integrate with Angular routing and modules:

496

497

```typescript

498

// Module integration

499

@NgModule({

500

declarations: [UserListComponent],

501

imports: [

502

CommonModule,

503

RouterModule.forChild([

504

{ path: '', component: UserListComponent }

505

])

506

]

507

})

508

export class UserModule {}

509

510

// Route integration with resolver

511

export const UserListRoute: Route = {

512

path: 'users',

513

component: UserListComponent,

514

resolve: {

515

users: UserResolver

516

}

517

};

518

```

519

520

### OnPush Change Detection

521

522

Optimized containers use OnPush change detection strategy:

523

524

```typescript

525

// Optimized container with OnPush

526

@Component({

527

selector: 'app-user-list',

528

templateUrl: './user-list.component.html',

529

styleUrls: ['./user-list.component.scss'],

530

changeDetection: ChangeDetectionStrategy.OnPush

531

})

532

export class UserListComponent implements OnInit {

533

// All properties are observables for OnPush compatibility

534

users$ = this.store.select(fromUser.selectAllUsers);

535

loading$ = this.store.select(fromUser.selectUsersLoading);

536

error$ = this.store.select(fromUser.selectUsersError);

537

538

constructor(private store: Store) {}

539

540

// Methods remain the same - they dispatch actions

541

loadUsers(): void {

542

this.store.dispatch(UserActions.loadUsers());

543

}

544

}

545

```