0
# Angular CLI Adapter
1
2
Bridge functionality for running Angular schematics, migrations, and builders within Nx workspaces. Provides compatibility layer for Angular DevKit tools and enables seamless integration with Angular CLI workflows.
3
4
## Capabilities
5
6
### Target Execution
7
8
Schedule and execute Angular CLI targets (builders) within Nx workspaces.
9
10
```typescript { .api }
11
/**
12
* Schedules Angular CLI target execution
13
* @param root - Workspace root directory
14
* @param opts - Target execution options
15
* @param verbose - Enable verbose logging
16
* @returns Promise resolving to Observable of build results
17
*/
18
export function scheduleTarget(
19
root: string,
20
opts: {
21
/** Project name to execute target for */
22
project: string;
23
/** Target name to execute */
24
target: string;
25
/** Configuration name (e.g., 'production', 'development') */
26
configuration: string;
27
/** Additional options to pass to the target */
28
runOptions: any;
29
},
30
verbose: boolean
31
): Promise<Observable<BuilderOutput>>;
32
33
/** Build output interface from Angular DevKit */
34
export interface BuilderOutput {
35
success: boolean;
36
error?: string;
37
target?: Target;
38
outputPath?: string;
39
}
40
41
/** Target reference interface */
42
export interface Target {
43
project: string;
44
target: string;
45
configuration?: string;
46
}
47
48
/** Observable interface for reactive programming */
49
export interface Observable<T> {
50
subscribe(observer: {
51
next?: (value: T) => void;
52
error?: (error: any) => void;
53
complete?: () => void;
54
}): { unsubscribe(): void };
55
}
56
```
57
58
### Schematic Generation
59
60
Run Angular schematics for code generation and project setup.
61
62
```typescript { .api }
63
/**
64
* Runs Angular schematic generation
65
* @param root - Workspace root directory
66
* @param opts - Generation options
67
* @param verbose - Enable verbose logging
68
* @returns Promise resolving to exit code (0 for success)
69
*/
70
export function generate(root: string, opts: GenerateOptions, verbose: boolean): Promise<number>;
71
72
/** Options for schematic generation */
73
export interface GenerateOptions {
74
/** Collection name (e.g., '@angular/core', '@nx/angular') */
75
collection: string;
76
/** Schematic name to run */
77
name: string;
78
/** Options to pass to the schematic */
79
options: Record<string, any>;
80
/** Whether to perform a dry run */
81
dryRun?: boolean;
82
/** Whether to force overwrite existing files */
83
force?: boolean;
84
}
85
```
86
87
### Migration Execution
88
89
Execute package migrations for workspace updates and transformations.
90
91
```typescript { .api }
92
/**
93
* Executes migrations for package updates
94
* @param root - Workspace root directory
95
* @param packageName - Name of the package to migrate
96
* @param migrationName - Name of the specific migration to run
97
* @param isVerbose - Enable verbose logging
98
* @returns Promise with migration results
99
*/
100
export function runMigration(
101
root: string,
102
packageName: string,
103
migrationName: string,
104
isVerbose: boolean
105
): Promise<{
106
/** Log messages from the migration */
107
loggingQueue: string[];
108
/** Whether the migration made any changes */
109
madeChanges: boolean;
110
}>;
111
```
112
113
### Schematic Wrapping
114
115
Utilities for wrapping Angular DevKit schematics for use within Nx.
116
117
```typescript { .api }
118
/**
119
* Wraps Angular DevKit schematic for Nx usage
120
* @param collectionName - Name of the schematic collection
121
* @param generatorName - Name of the generator/schematic
122
* @returns Wrapped schematic function compatible with Nx Tree API
123
*/
124
export function wrapAngularDevkitSchematic(
125
collectionName: string,
126
generatorName: string
127
): (host: Tree, generatorOptions: { [k: string]: any }) => Promise<void>;
128
```
129
130
### Virtual File System Integration
131
132
Classes for integrating Angular DevKit with Nx file system operations.
133
134
```typescript { .api }
135
/**
136
* Virtual filesystem host for Angular DevKit integration
137
*/
138
export class NxScopedHost {
139
constructor(root: string);
140
141
/** Read file as string */
142
readFile(path: string): Promise<string>;
143
/** Write file content */
144
writeFile(path: string, content: string): Promise<void>;
145
/** Check if file exists */
146
exists(path: string): Promise<boolean>;
147
/** Delete file */
148
delete(path: string): Promise<void>;
149
/** Rename file */
150
rename(from: string, to: string): Promise<void>;
151
/** List directory contents */
152
list(path: string): Promise<string[]>;
153
}
154
155
/**
156
* Specialized host for wrapped schematics
157
*/
158
export class NxScopeHostUsedForWrappedSchematics extends NxScopedHost {
159
constructor(root: string, host: Tree);
160
}
161
```
162
163
### Logging and Utilities
164
165
Utilities for logging and converting data formats.
166
167
```typescript { .api }
168
/**
169
* Creates Angular DevKit logger
170
* @param isVerbose - Enable verbose logging
171
* @returns Logger instance compatible with Angular DevKit
172
*/
173
export function getLogger(isVerbose?: boolean): logging.Logger;
174
175
/**
176
* Converts ArrayBuffer to string
177
* @param buffer - ArrayBuffer to convert
178
* @returns String representation of the buffer
179
*/
180
export function arrayBufferToString(buffer: any): string;
181
```
182
183
### Testing Utilities
184
185
Functions for testing and mocking schematic operations.
186
187
```typescript { .api }
188
/**
189
* Override collection resolution for testing
190
* @param collections - Map of collection names to paths
191
*/
192
export function overrideCollectionResolutionForTesting(collections: { [name: string]: string }): void;
193
194
/**
195
* Mock schematics for testing
196
* @param schematics - Map of schematic names to mock implementations
197
*/
198
export function mockSchematicsForTesting(schematics: {
199
[name: string]: (host: Tree, generatorOptions: { [k: string]: any }) => Promise<void>
200
}): void;
201
```
202
203
## Usage Examples
204
205
### Running Angular CLI Targets
206
207
```typescript
208
import { scheduleTarget } from "@nrwl/tao/commands/ngcli-adapter";
209
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
210
211
async function buildProject(projectName: string, configuration: string = 'production') {
212
try {
213
const buildResult$ = await scheduleTarget(
214
workspaceRoot,
215
{
216
project: projectName,
217
target: 'build',
218
configuration: configuration,
219
runOptions: {
220
optimization: true,
221
sourceMap: false
222
}
223
},
224
true // verbose logging
225
);
226
227
// Subscribe to build results
228
buildResult$.subscribe({
229
next: (result) => {
230
if (result.success) {
231
console.log(`Build completed successfully`);
232
if (result.outputPath) {
233
console.log(`Output: ${result.outputPath}`);
234
}
235
} else {
236
console.error(`Build failed: ${result.error}`);
237
}
238
},
239
error: (error) => {
240
console.error('Build error:', error);
241
},
242
complete: () => {
243
console.log('Build process completed');
244
}
245
});
246
247
} catch (error) {
248
console.error('Failed to schedule build target:', error);
249
}
250
}
251
252
// Usage
253
buildProject('my-app', 'production');
254
```
255
256
### Running Schematics
257
258
```typescript
259
import { generate, type GenerateOptions } from "@nrwl/tao/commands/ngcli-adapter";
260
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
261
import { logger } from "@nrwl/tao/shared/logger";
262
263
async function generateComponent(name: string, project: string) {
264
const options: GenerateOptions = {
265
collection: '@angular/core',
266
name: 'component',
267
options: {
268
name: name,
269
project: project,
270
style: 'scss',
271
skipTests: false,
272
changeDetection: 'OnPush'
273
},
274
dryRun: false,
275
force: false
276
};
277
278
try {
279
const exitCode = await generate(workspaceRoot, options, true);
280
281
if (exitCode === 0) {
282
logger.info(`Component '${name}' generated successfully`);
283
} else {
284
logger.error(`Component generation failed with exit code: ${exitCode}`);
285
}
286
287
return exitCode;
288
} catch (error) {
289
logger.error('Failed to generate component:', error);
290
return 1;
291
}
292
}
293
294
// Generate library
295
async function generateLibrary(name: string) {
296
const options: GenerateOptions = {
297
collection: '@nx/angular',
298
name: 'library',
299
options: {
300
name: name,
301
buildable: true,
302
publishable: false,
303
routing: false,
304
lazy: false
305
}
306
};
307
308
const exitCode = await generate(workspaceRoot, options, true);
309
return exitCode === 0;
310
}
311
312
// Usage
313
generateComponent('user-profile', 'my-app');
314
generateLibrary('shared-utils');
315
```
316
317
### Running Migrations
318
319
```typescript
320
import { runMigration } from "@nrwl/tao/commands/ngcli-adapter";
321
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
322
import { logger } from "@nrwl/tao/shared/logger";
323
324
async function migratePackage(packageName: string, migrationName: string) {
325
try {
326
logger.info(`Running migration: ${packageName}:${migrationName}`);
327
328
const result = await runMigration(
329
workspaceRoot,
330
packageName,
331
migrationName,
332
true // verbose
333
);
334
335
// Log migration output
336
result.loggingQueue.forEach(message => {
337
console.log(message);
338
});
339
340
if (result.madeChanges) {
341
logger.info('Migration completed with changes');
342
} else {
343
logger.info('Migration completed without changes');
344
}
345
346
return result;
347
} catch (error) {
348
logger.error(`Migration failed: ${error.message}`);
349
throw error;
350
}
351
}
352
353
// Example: Migrate Angular to newer version
354
async function migrateAngular() {
355
try {
356
// Run Angular update migration
357
await migratePackage('@angular/core', 'migration-v15');
358
359
// Run additional migrations if needed
360
await migratePackage('@angular/cli', 'update-workspace-config');
361
362
logger.info('Angular migration completed successfully');
363
} catch (error) {
364
logger.error('Angular migration failed:', error);
365
}
366
}
367
368
migrateAngular();
369
```
370
371
### Custom Schematic Wrapping
372
373
```typescript
374
import {
375
wrapAngularDevkitSchematic,
376
NxScopeHostUsedForWrappedSchematics
377
} from "@nrwl/tao/commands/ngcli-adapter";
378
import { FsTree } from "@nrwl/tao/shared/tree";
379
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
380
381
// Wrap an Angular schematic for use with Nx Tree API
382
const wrappedComponentSchematic = wrapAngularDevkitSchematic(
383
'@angular/core',
384
'component'
385
);
386
387
async function generateComponentWithNxTree(name: string, path: string) {
388
const tree = new FsTree(workspaceRoot);
389
390
try {
391
// Use the wrapped schematic with Nx Tree
392
await wrappedComponentSchematic(tree, {
393
name: name,
394
path: path,
395
style: 'scss',
396
skipTests: false
397
});
398
399
// Apply changes to file system
400
const changes = tree.listChanges();
401
console.log(`Generated ${changes.length} files for component '${name}'`);
402
403
// You can review changes before applying
404
changes.forEach(change => {
405
console.log(`${change.type}: ${change.path}`);
406
});
407
408
// Apply changes
409
flushChanges(tree.root, changes);
410
411
} catch (error) {
412
console.error('Failed to generate component:', error);
413
}
414
}
415
416
generateComponentWithNxTree('feature-component', 'src/app/features');
417
```
418
419
### Testing with Mocked Schematics
420
421
```typescript
422
import {
423
mockSchematicsForTesting,
424
overrideCollectionResolutionForTesting
425
} from "@nrwl/tao/commands/ngcli-adapter";
426
import { FsTree } from "@nrwl/tao/shared/tree";
427
428
// Set up testing environment
429
function setupSchematicTesting() {
430
// Override collection paths for testing
431
overrideCollectionResolutionForTesting({
432
'@nx/angular': '/path/to/test/collections/nx-angular',
433
'@angular/core': '/path/to/test/collections/angular-core'
434
});
435
436
// Mock specific schematics
437
mockSchematicsForTesting({
438
'component': async (tree, options) => {
439
// Mock component generation
440
const componentPath = `${options.path}/${options.name}.component.ts`;
441
tree.write(componentPath, `
442
import { Component } from '@angular/core';
443
444
@Component({
445
selector: 'app-${options.name}',
446
template: '<div>Mock ${options.name} component</div>'
447
})
448
export class ${options.name}Component {}
449
`);
450
},
451
452
'service': async (tree, options) => {
453
// Mock service generation
454
const servicePath = `${options.path}/${options.name}.service.ts`;
455
tree.write(servicePath, `
456
import { Injectable } from '@angular/core';
457
458
@Injectable({
459
providedIn: 'root'
460
})
461
export class ${options.name}Service {}
462
`);
463
}
464
});
465
}
466
467
// Use in tests
468
describe('Schematic Integration', () => {
469
beforeEach(() => {
470
setupSchematicTesting();
471
});
472
473
it('should generate component using mocked schematic', async () => {
474
const tree = new FsTree('/test-workspace');
475
476
// This will use the mocked schematic
477
const wrappedSchematic = wrapAngularDevkitSchematic('@angular/core', 'component');
478
await wrappedSchematic(tree, {
479
name: 'test-component',
480
path: 'src/app'
481
});
482
483
expect(tree.exists('src/app/test-component.component.ts')).toBe(true);
484
});
485
});
486
```
487
488
### Logging Integration
489
490
```typescript
491
import { getLogger, arrayBufferToString } from "@nrwl/tao/commands/ngcli-adapter";
492
import { logger as nxLogger } from "@nrwl/tao/shared/logger";
493
494
// Create Angular DevKit logger that integrates with Nx logging
495
const angularLogger = getLogger(true); // verbose logging
496
497
// Use Angular logger for DevKit operations
498
angularLogger.info('Starting Angular DevKit operation');
499
angularLogger.warn('This is a warning from Angular DevKit');
500
501
// Convert binary data for logging
502
const binaryData = new ArrayBuffer(8);
503
const stringData = arrayBufferToString(binaryData);
504
nxLogger.debug('Binary data as string:', stringData);
505
506
// Custom logging bridge
507
function bridgeAngularToNxLogging(angularLogger: any, nxLogger: any) {
508
const originalInfo = angularLogger.info;
509
const originalWarn = angularLogger.warn;
510
const originalError = angularLogger.error;
511
512
angularLogger.info = (message: string) => {
513
nxLogger.info(`[Angular] ${message}`);
514
return originalInfo.call(angularLogger, message);
515
};
516
517
angularLogger.warn = (message: string) => {
518
nxLogger.warn(`[Angular] ${message}`);
519
return originalWarn.call(angularLogger, message);
520
};
521
522
angularLogger.error = (message: string) => {
523
nxLogger.error(`[Angular] ${message}`);
524
return originalError.call(angularLogger, message);
525
};
526
}
527
528
bridgeAngularToNxLogging(angularLogger, nxLogger);
529
```
530
531
## Integration with Workspace Management
532
533
The Angular CLI adapter works seamlessly with other @nrwl/tao APIs:
534
535
```typescript
536
import { scheduleTarget } from "@nrwl/tao/commands/ngcli-adapter";
537
import { Workspaces } from "@nrwl/tao/shared/workspace";
538
import { logger } from "@nrwl/tao/shared/logger";
539
import { workspaceRoot } from "@nrwl/tao/utils/app-root";
540
541
async function buildAllApps() {
542
const workspaces = new Workspaces(workspaceRoot);
543
const projects = workspaces.readProjectsConfigurations();
544
545
// Find all application projects
546
const apps = Object.entries(projects.projects)
547
.filter(([_, config]) => config.projectType === 'application')
548
.map(([name]) => name);
549
550
logger.info(`Found ${apps.length} applications to build`);
551
552
// Build each app
553
for (const appName of apps) {
554
try {
555
logger.info(`Building ${appName}...`);
556
557
const buildResult$ = await scheduleTarget(
558
workspaceRoot,
559
{
560
project: appName,
561
target: 'build',
562
configuration: 'production',
563
runOptions: {}
564
},
565
false // not verbose for batch operation
566
);
567
568
await new Promise((resolve, reject) => {
569
buildResult$.subscribe({
570
next: (result) => {
571
if (result.success) {
572
logger.info(`✓ ${appName} built successfully`);
573
} else {
574
logger.error(`✗ ${appName} build failed: ${result.error}`);
575
}
576
},
577
error: reject,
578
complete: resolve
579
});
580
});
581
582
} catch (error) {
583
logger.error(`Failed to build ${appName}:`, error);
584
}
585
}
586
587
logger.info('Batch build completed');
588
}
589
```