diff --git a/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts.template b/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts.template index 54b1bef1995b..a05712a1fa66 100644 --- a/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts.template +++ b/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.spec.ts.template @@ -1,11 +1,10 @@ -import { LayoutModule } from '@angular/cdk/layout'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations';<% if(!standalone) { %> import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatListModule } from '@angular/material/list'; import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatToolbarModule } from '@angular/material/toolbar';<% } %> import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component'; @@ -14,17 +13,17 @@ describe('<%= classify(name) %>Component', () => { let fixture: ComponentFixture<<%= classify(name) %>Component>; beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ + TestBed.configureTestingModule({<% if(standalone) { %> + imports: [NoopAnimationsModule]<% } else { %> declarations: [<%= classify(name) %>Component], imports: [ NoopAnimationsModule, - LayoutModule, MatButtonModule, MatIconModule, MatListModule, MatSidenavModule, MatToolbarModule, - ] + ]<% } %> }).compileComponents(); })); diff --git a/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template b/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template index b535d75affa2..12d6ffa84894 100644 --- a/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template +++ b/src/material/schematics/ng-generate/navigation/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.component.ts.template @@ -1,5 +1,10 @@ -import { Component<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core'; -import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { Component, inject<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';<% if(standalone) { %> +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatListModule } from '@angular/material/list'; +import { MatIconModule } from '@angular/material/icon';<% } %> import { Observable } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; @@ -14,16 +19,22 @@ import { map, shareReplay } from 'rxjs/operators'; `]<% } else { %> styleUrls: ['./<%= dasherize(name) %>.component.<%= style %>']<% } %><% if(!!viewEncapsulation) { %>, encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>, - changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %> + changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %><% if(standalone) { %>, + standalone: true, + imports: [ + MatToolbarModule, + MatButtonModule, + MatSidenavModule, + MatListModule, + MatIconModule + ]<% } %> }) export class <%= classify(name) %>Component { + private breakpointObserver = inject(BreakpointObserver); isHandset$: Observable = this.breakpointObserver.observe(Breakpoints.Handset) .pipe( map(result => result.matches), shareReplay() ); - - constructor(private breakpointObserver: BreakpointObserver) {} - } diff --git a/src/material/schematics/ng-generate/navigation/index.spec.ts b/src/material/schematics/ng-generate/navigation/index.spec.ts index 138119d2f764..d46c906253ab 100644 --- a/src/material/schematics/ng-generate/navigation/index.spec.ts +++ b/src/material/schematics/ng-generate/navigation/index.spec.ts @@ -18,13 +18,11 @@ describe('material-navigation-schematic', () => { function expectNavigationSchematicModuleImports(tree: UnitTestTree) { const moduleContent = getFileContent(tree, '/projects/material/src/app/app.module.ts'); - expect(moduleContent).toMatch(/LayoutModule,\s+/); expect(moduleContent).toMatch(/MatToolbarModule,\s+/); expect(moduleContent).toMatch(/MatButtonModule,\s+/); expect(moduleContent).toMatch(/MatSidenavModule,\s+/); expect(moduleContent).toMatch(/MatIconModule,\s+/); expect(moduleContent).toMatch(/MatListModule\s+],/); - expect(moduleContent).toContain(`import { LayoutModule } from '@angular/cdk/layout';`); expect(moduleContent).toContain(`import { MatButtonModule } from '@angular/material/button';`); expect(moduleContent).toContain(`import { MatIconModule } from '@angular/material/icon';`); expect(moduleContent).toContain(`import { MatListModule } from '@angular/material/list';`); @@ -71,6 +69,44 @@ describe('material-navigation-schematic', () => { ).toBeRejectedWithError(/required property 'name'/); }); + describe('standalone option', () => { + it('should generate a standalone component', async () => { + const app = await createTestApp(runner); + const tree = await runner.runSchematic('navigation', {...baseOptions, standalone: true}, app); + const module = getFileContent(tree, '/projects/material/src/app/app.module.ts'); + const component = getFileContent(tree, '/projects/material/src/app/foo/foo.component.ts'); + const requiredModules = [ + 'MatToolbarModule', + 'MatButtonModule', + 'MatSidenavModule', + 'MatListModule', + 'MatIconModule', + ]; + + requiredModules.forEach(name => { + expect(module).withContext('Module should not import dependencies').not.toContain(name); + expect(component).withContext('Component should import dependencies').toContain(name); + }); + + expect(module).not.toContain('FooComponent'); + expect(component).toContain('standalone: true'); + expect(component).toContain('imports: ['); + }); + + it('should infer the standalone option from the project structure', async () => { + const app = await createTestApp(runner, {standalone: true}); + const tree = await runner.runSchematic('navigation', baseOptions, app); + const componentContent = getFileContent( + tree, + '/projects/material/src/app/foo/foo.component.ts', + ); + + expect(tree.exists('/projects/material/src/app/app.module.ts')).toBe(false); + expect(componentContent).toContain('standalone: true'); + expect(componentContent).toContain('imports: ['); + }); + }); + describe('style option', () => { it('should respect the option value', async () => { const tree = await runner.runSchematic( diff --git a/src/material/schematics/ng-generate/navigation/index.ts b/src/material/schematics/ng-generate/navigation/index.ts index 5b63c30ff34f..ae6d3943c667 100644 --- a/src/material/schematics/ng-generate/navigation/index.ts +++ b/src/material/schematics/ng-generate/navigation/index.ts @@ -11,6 +11,7 @@ import { addModuleImportToModule, buildComponent, findModuleFromOptions, + isStandaloneSchematic, } from '@angular/cdk/schematics'; import {Schema} from './schema'; @@ -38,12 +39,15 @@ export default function (options: Schema): Rule { */ function addNavModulesToModule(options: Schema) { return async (host: Tree) => { - const modulePath = (await findModuleFromOptions(host, options))!; - addModuleImportToModule(host, modulePath, 'LayoutModule', '@angular/cdk/layout'); - addModuleImportToModule(host, modulePath, 'MatToolbarModule', '@angular/material/toolbar'); - addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); - addModuleImportToModule(host, modulePath, 'MatSidenavModule', '@angular/material/sidenav'); - addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material/icon'); - addModuleImportToModule(host, modulePath, 'MatListModule', '@angular/material/list'); + const isStandalone = await isStandaloneSchematic(host, options); + + if (!isStandalone) { + const modulePath = (await findModuleFromOptions(host, options))!; + addModuleImportToModule(host, modulePath, 'MatToolbarModule', '@angular/material/toolbar'); + addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); + addModuleImportToModule(host, modulePath, 'MatSidenavModule', '@angular/material/sidenav'); + addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material/icon'); + addModuleImportToModule(host, modulePath, 'MatListModule', '@angular/material/list'); + } }; } diff --git a/src/material/schematics/ng-generate/navigation/schema.json b/src/material/schematics/ng-generate/navigation/schema.json index d1dc5e06bcd3..31abbc330b08 100644 --- a/src/material/schematics/ng-generate/navigation/schema.json +++ b/src/material/schematics/ng-generate/navigation/schema.json @@ -39,6 +39,10 @@ "type": "boolean", "alias": "t" }, + "standalone": { + "description": "Whether the generated component is standalone.", + "type": "boolean" + }, "viewEncapsulation": { "description": "Specifies the view encapsulation strategy.", "enum": ["Emulated", "None"],