Skip to content

Commit

Permalink
feat(@schematics/angular): add --standalone to ng generate
Browse files Browse the repository at this point in the history
Adds the `--standalone` flag when generating components, directives or pipes through `ng generate`.
  • Loading branch information
crisbeto authored and clydin committed Apr 27, 2022
1 parent 7abe212 commit 7b78b78
Show file tree
Hide file tree
Showing 15 changed files with 69 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/design/analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Note: There's a limit of 20 custom dimensions.
| 12 | `Flag: --skip-tests` | `boolean` |
| 13 | `Flag: --aot` | `boolean` |
| 14 | `Flag: --minimal` | `boolean` |
| 15 | `Flag: --standalone` | `boolean` |
| 16 | `Flag: --optimization` | `boolean` |
| 17 | `Flag: --routing` | `boolean` |
| 18 | `Flag: --skip-import` | `boolean` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('<%= classify(name) %><%= classify(type) %>', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ <%= classify(name) %><%= classify(type) %> ]
<%= standalone ? 'imports' : 'declarations' %>: [ <%= classify(name) %><%= classify(type) %> ]
})
.compileComponents();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';

@Component({<% if(!skipSelector) {%>
selector: '<%= selector %>',<%}%><% if(inlineTemplate) { %>
selector: '<%= selector %>',<%}%><% if(standalone) {%>
standalone: true,<%}%><% if(inlineTemplate) { %>
template: `
<p>
<%= dasherize(name) %> works!
Expand Down
2 changes: 1 addition & 1 deletion packages/schematics/angular/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile {

function addDeclarationToNgModule(options: ComponentOptions): Rule {
return (host: Tree) => {
if (options.skipImport || !options.module) {
if (options.skipImport || options.standalone || !options.module) {
return host;
}

Expand Down
18 changes: 18 additions & 0 deletions packages/schematics/angular/component/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,22 @@ describe('Component Schematic', () => {
expect(content).toMatch(/template: `(\n(.|)*){3}\n\s*`,\n/);
expect(content).toMatch(/changeDetection: ChangeDetectionStrategy.OnPush/);
});

it('should create a standalone component', async () => {
const options = { ...defaultOptions, standalone: true };
const tree = await schematicRunner.runSchematicAsync('component', options, appTree).toPromise();
const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
const componentContent = tree.readContent('/projects/bar/src/app/foo/foo.component.ts');
expect(componentContent).toContain('standalone: true');
expect(componentContent).toContain('class FooComponent');
expect(moduleContent).not.toContain('FooComponent');
});

it('should declare standalone components in the `imports` of a test', async () => {
const options = { ...defaultOptions, standalone: true };
const tree = await schematicRunner.runSchematicAsync('component', options, appTree).toPromise();
const testContent = tree.readContent('/projects/bar/src/app/foo/foo.component.spec.ts');
expect(testContent).toContain('imports: [ FooComponent ]');
expect(testContent).not.toContain('declarations');
});
});
6 changes: 6 additions & 0 deletions packages/schematics/angular/component/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
"alias": "t",
"x-user-analytics": 10
},
"standalone": {
"description": "Whether the generated component is standalone.",
"type": "boolean",
"default": false,
"x-user-analytics": 15
},
"viewEncapsulation": {
"description": "The view encapsulation strategy to use in the new component.",
"enum": ["Emulated", "None", "ShadowDom"],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Directive } from '@angular/core';

@Directive({
selector: '[<%= selector %>]'
selector: '[<%= selector %>]'<% if(standalone) {%>,
standalone: true<%}%>
})
export class <%= classify(name) %>Directive {

Expand Down
2 changes: 1 addition & 1 deletion packages/schematics/angular/directive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Schema as DirectiveOptions } from './schema';

function addDeclarationToNgModule(options: DirectiveOptions): Rule {
return (host: Tree) => {
if (options.skipImport || !options.module) {
if (options.skipImport || options.standalone || !options.module) {
return host;
}

Expand Down
10 changes: 10 additions & 0 deletions packages/schematics/angular/directive/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,14 @@ describe('Directive Schematic', () => {
expect(files).toContain('/projects/bar/src/app/foo.directive.ts');
expect(files).not.toContain('/projects/bar/src/app/foo.directive.spec.ts');
});

it('should create a standalone directive', async () => {
const options = { ...defaultOptions, standalone: true };
const tree = await schematicRunner.runSchematicAsync('directive', options, appTree).toPromise();
const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
const directiveContent = tree.readContent('/projects/bar/src/app/foo.directive.ts');
expect(directiveContent).toContain('standalone: true');
expect(directiveContent).toContain('class FooDirective');
expect(moduleContent).not.toContain('FooDirective');
});
});
6 changes: 6 additions & 0 deletions packages/schematics/angular/directive/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
"format": "html-selector",
"description": "The HTML selector to use for this directive."
},
"standalone": {
"description": "Whether the generated directive is standalone.",
"type": "boolean",
"default": false,
"x-user-analytics": 15
},
"flat": {
"type": "boolean",
"description": "When true (the default), creates the new files at the top level of the current project.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: '<%= camelize(name) %>'
name: '<%= camelize(name) %>'<% if(standalone) {%>,
standalone: true<%}%>
})
export class <%= classify(name) %>Pipe implements PipeTransform {

Expand Down
2 changes: 1 addition & 1 deletion packages/schematics/angular/pipe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { Schema as PipeOptions } from './schema';

function addDeclarationToNgModule(options: PipeOptions): Rule {
return (host: Tree) => {
if (options.skipImport || !options.module) {
if (options.skipImport || options.standalone || !options.module) {
return host;
}

Expand Down
10 changes: 10 additions & 0 deletions packages/schematics/angular/pipe/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,14 @@ describe('Pipe Schematic', () => {
expect(files).not.toContain('/projects/bar/src/app/foo.pipe.spec.ts');
expect(files).toContain('/projects/bar/src/app/foo.pipe.ts');
});

it('should create a standalone pipe', async () => {
const options = { ...defaultOptions, standalone: true };
const tree = await schematicRunner.runSchematicAsync('pipe', options, appTree).toPromise();
const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
const pipeContent = tree.readContent('/projects/bar/src/app/foo.pipe.ts');
expect(pipeContent).toContain('standalone: true');
expect(pipeContent).toContain('class FooPipe');
expect(moduleContent).not.toContain('FooPipe');
});
});
6 changes: 6 additions & 0 deletions packages/schematics/angular/pipe/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
"description": "Do not import this pipe into the owning NgModule.",
"x-user-analytics": 18
},
"standalone": {
"description": "Whether the generated pipe is standalone.",
"type": "boolean",
"default": false,
"x-user-analytics": 15
},
"module": {
"type": "string",
"description": "The declaring NgModule.",
Expand Down
4 changes: 2 additions & 2 deletions packages/schematics/angular/utility/find-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ModuleOptions {
skipImport?: boolean;
moduleExt?: string;
routingModuleExt?: string;
standalone?: boolean;
}

export const MODULE_EXT = '.module.ts';
Expand All @@ -26,8 +27,7 @@ export const ROUTING_MODULE_EXT = '-routing.module.ts';
* Find the module referred by a set of options passed to the schematics.
*/
export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined {
// eslint-disable-next-line no-prototype-builtins
if (options.hasOwnProperty('skipImport') && options.skipImport) {
if (options.standalone || options.skipImport) {
return undefined;
}

Expand Down

0 comments on commit 7b78b78

Please sign in to comment.