From 7658440bcc4acdd6be14f3eda202a35025649322 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 13 Apr 2023 09:01:11 +0200 Subject: [PATCH] fix(material/schematics): use provider functions in ng add Switches to generating `provideAnimations` and `provideNoopAnimations` instead of `importProvidersFrom(BrowserAnimationsModule)` in `ng add`. --- src/material/schematics/ng-add/index.spec.ts | 47 ++++++++----- .../schematics/ng-add/setup-project.ts | 67 ++++++++++--------- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/material/schematics/ng-add/index.spec.ts b/src/material/schematics/ng-add/index.spec.ts index 32b271573694..8b902cafbe83 100644 --- a/src/material/schematics/ng-add/index.spec.ts +++ b/src/material/schematics/ng-add/index.spec.ts @@ -219,39 +219,56 @@ describe('ng-add schematic', () => { expect(errorOutput[0]).toMatch(/Could not set up "BrowserAnimationsModule"/); }); - it('should add the BrowserAnimationsModule to a bootstrapApplication call', async () => { + it('should add the provideAnimations to a bootstrapApplication call', async () => { appTree.delete('/projects/material/src/app/app.module.ts'); + appTree.create( + '/projects/material/src/app/app.config.ts', + ` + export const appConfig = { + providers: [{ provide: 'foo', useValue: 1 }] + }; + `, + ); appTree.overwrite( '/projects/material/src/main.ts', ` - import { importProvidersFrom } from '@angular/core'; - import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; + import { bootstrapApplication } from '@angular/platform-browser'; import { AppComponent } from './app/app.component'; + import { appConfig } from './app/app.config'; - bootstrapApplication(AppComponent, { - providers: [{provide: 'foo', useValue: 1}, importProvidersFrom(BrowserModule)] - }); + bootstrapApplication(AppComponent, appConfig); `, ); const tree = await runner.runSchematic('ng-add-setup-project', baseOptions, appTree); - const fileContent = getFileContent(tree, '/projects/material/src/main.ts'); - expect(fileContent).toContain('importProvidersFrom(BrowserModule, BrowserAnimationsModule)'); + const fileContent = getFileContent(tree, '/projects/material/src/app/app.config.ts'); + + expect(fileContent).toContain( + `import { provideAnimations } from '@angular/platform-browser/animations';`, + ); + expect(fileContent).toContain(`[{ provide: 'foo', useValue: 1 }, provideAnimations()]`); }); - it('should not add BrowserAnimationsModule if NoopAnimationsModule is set up in a bootstrapApplication call', async () => { + it('should not add provideAnimations if provideNoopAnimations is set up in a bootstrapApplication call', async () => { appTree.delete('/projects/material/src/app/app.module.ts'); + appTree.create( + '/projects/material/src/app/app.config.ts', + ` + import { provideNoopAnimations } from '@angular/platform-browser/animations'; + + export const appConfig = { + providers: [{ provide: 'foo', useValue: 1 }, provideNoopAnimations()] + }; + `, + ); appTree.overwrite( '/projects/material/src/main.ts', ` - import { importProvidersFrom } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; - import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app/app.component'; + import { appConfig } from './app/app.config'; - bootstrapApplication(AppComponent, { - providers: [{provide: 'foo', useValue: 1}, importProvidersFrom(NoopAnimationsModule)] - }); + bootstrapApplication(AppComponent, appConfig); `, ); @@ -259,7 +276,7 @@ describe('ng-add schematic', () => { expect(errorOutput.length).toBe(1); expect(errorOutput[0]).toMatch( - /Could not set up "BrowserAnimationsModule" because "NoopAnimationsModule" is already imported/, + /Could not add "provideAnimations" because "provideNoopAnimations" is already provided/, ); }); }); diff --git a/src/material/schematics/ng-add/setup-project.ts b/src/material/schematics/ng-add/setup-project.ts index 34df871ef834..0a1da9438c97 100644 --- a/src/material/schematics/ng-add/setup-project.ts +++ b/src/material/schematics/ng-add/setup-project.ts @@ -17,19 +17,16 @@ import { } from '@angular/cdk/schematics'; import { importsProvidersFrom, - addModuleImportToStandaloneBootstrap, + findBootstrapApplicationCall, + addFunctionalProvidersToStandaloneBootstrap, + callsProvidersFunction, } from '@schematics/angular/private/components'; import {getWorkspace, ProjectDefinition} from '@schematics/angular/utility/workspace'; import {ProjectType} from '@schematics/angular/utility/workspace-models'; import {addFontsToIndex} from './fonts/material-fonts'; import {Schema} from './schema'; import {addThemeToAppStyles, addTypographyClass} from './theming/theming'; - -/** Name of the Angular module that enables Angular browser animations. */ -const browserAnimationsModuleName = 'BrowserAnimationsModule'; - -/** Name of the module that switches Angular animations to a noop implementation. */ -const noopAnimationsModuleName = 'NoopAnimationsModule'; +import * as ts from 'typescript'; /** * Scaffolds the basics of a Angular Material application, this includes: @@ -70,57 +67,60 @@ function addAnimationsModule(options: Schema) { return async (host: Tree, context: SchematicContext) => { const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); + const mainFilePath = getProjectMainFile(project); + const mainSourceFile = ts.createSourceFile( + mainFilePath, + host.readText(mainFilePath), + ts.ScriptTarget.Latest, + ); - try { - addAnimationsModuleToNonStandaloneApp(host, project, context, options); - } catch (e) { - if ((e as {message?: string}).message?.includes('Bootstrap call not found')) { - addAnimationsModuleToStandaloneApp(host, project, context, options); - } else { - throw e; - } + if (findBootstrapApplicationCall(mainSourceFile)) { + addAnimationsToStandaloneApp(host, mainFilePath, context, options); + } else { + addAnimationsToNonStandaloneApp(host, project, mainFilePath, context, options); } }; } /** Adds the animations module to an app that is bootstrap using the standalone component APIs. */ -function addAnimationsModuleToStandaloneApp( +function addAnimationsToStandaloneApp( host: Tree, - project: ProjectDefinition, + mainFile: string, context: SchematicContext, options: Schema, ) { - const mainFile = getProjectMainFile(project); + const animationsFunction = 'provideAnimations'; + const noopAnimationsFunction = 'provideNoopAnimations'; if (options.animations === 'enabled') { - // In case the project explicitly uses the NoopAnimationsModule, we should print a warning + // In case the project explicitly uses provideNoopAnimations, we should print a warning // message that makes the user aware of the fact that we won't automatically set up - // animations. If we would add the BrowserAnimationsModule while the NoopAnimationsModule + // animations. If we would add provideAnimations while provideNoopAnimations // is already configured, we would cause unexpected behavior and runtime exceptions. - if (importsProvidersFrom(host, mainFile, noopAnimationsModuleName)) { + if (callsProvidersFunction(host, mainFile, noopAnimationsFunction)) { context.logger.error( - `Could not set up "${browserAnimationsModuleName}" ` + - `because "${noopAnimationsModuleName}" is already imported.`, + `Could not add "${animationsFunction}" ` + + `because "${noopAnimationsFunction}" is already provided.`, ); context.logger.info(`Please manually set up browser animations.`); } else { - addModuleImportToStandaloneBootstrap( + addFunctionalProvidersToStandaloneBootstrap( host, mainFile, - browserAnimationsModuleName, + animationsFunction, '@angular/platform-browser/animations', ); } } else if ( options.animations === 'disabled' && - !importsProvidersFrom(host, mainFile, browserAnimationsModuleName) + !importsProvidersFrom(host, mainFile, animationsFunction) ) { - // Do not add the NoopAnimationsModule module if the project already explicitly uses - // the BrowserAnimationsModule. - addModuleImportToStandaloneBootstrap( + // Do not add the provideNoopAnimations if the project already explicitly uses + // the provideAnimations. + addFunctionalProvidersToStandaloneBootstrap( host, mainFile, - noopAnimationsModuleName, + noopAnimationsFunction, '@angular/platform-browser/animations', ); } @@ -130,13 +130,16 @@ function addAnimationsModuleToStandaloneApp( * Adds the animations module to an app that is bootstrap * using the non-standalone component APIs. */ -function addAnimationsModuleToNonStandaloneApp( +function addAnimationsToNonStandaloneApp( host: Tree, project: ProjectDefinition, + mainFile: string, context: SchematicContext, options: Schema, ) { - const appModulePath = getAppModulePath(host, getProjectMainFile(project)); + const browserAnimationsModuleName = 'BrowserAnimationsModule'; + const noopAnimationsModuleName = 'NoopAnimationsModule'; + const appModulePath = getAppModulePath(host, mainFile); if (options.animations === 'enabled') { // In case the project explicitly uses the NoopAnimationsModule, we should print a warning