From 949ac70bf7926b75ebb61ec21d61d82ff0a5f4bf Mon Sep 17 00:00:00 2001 From: Mostafa Sherif Date: Sat, 11 Feb 2023 17:43:10 -0500 Subject: [PATCH 1/6] fix(angular): provide constructor dependencies Closes: - [Bug]: Angular: No provider for XXX #21052 - [Bug]: Angular: ControlValueAccessor is always set to null #21051 This is needed because services that are { providedIn: 'root' } are not being read into the root injector when coupled with standalone components. * [x] provide moduleMetadata.providers into the root injector, still being provided into the standalone component for overrides * [x] provide dependencies injected into components constructors into the root injector, in case they are providedIn: 'root' but they are missed by the standalone injector --- .../client/angular-beta/AbstractRenderer.ts | 7 +- .../angular-beta/StorybookWrapperComponent.ts | 7 +- .../utils/PropertyExtractor.test.ts | 44 ++- .../angular-beta/utils/PropertyExtractor.ts | 299 ++++++++++-------- code/frameworks/angular/src/client/types.ts | 2 + 5 files changed, 209 insertions(+), 150 deletions(-) diff --git a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts index 91e2cc3cf6e9..7b9a36230d97 100644 --- a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts +++ b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts @@ -7,7 +7,7 @@ import { ICollection, Parameters, StoryFnAngularReturnType } from '../types'; import { getApplication } from './StorybookModule'; import { storyPropsProvider } from './StorybookProvider'; import { componentNgModules } from './StorybookWrapperComponent'; -import { extractSingletons } from './utils/PropertyExtractor'; +import { PropertyExtractor } from './utils/PropertyExtractor'; type StoryRenderInfo = { storyFnAngular: StoryFnAngularReturnType; @@ -120,9 +120,12 @@ export abstract class AbstractRenderer { this.initAngularRootElement(targetDOMNode, targetSelector); + const analyzedMetadata = new PropertyExtractor(storyFnAngular.moduleMetadata, component); const providers = [ // Providers for BrowserAnimations & NoopAnimationsModule - extractSingletons(storyFnAngular.moduleMetadata), + analyzedMetadata.singletons, + analyzedMetadata.providers, + analyzedMetadata.dependencies, storyPropsProvider(newStoryProps$), ]; diff --git a/code/frameworks/angular/src/client/angular-beta/StorybookWrapperComponent.ts b/code/frameworks/angular/src/client/angular-beta/StorybookWrapperComponent.ts index 1b3a8693de76..6f2cbf3f932b 100644 --- a/code/frameworks/angular/src/client/angular-beta/StorybookWrapperComponent.ts +++ b/code/frameworks/angular/src/client/angular-beta/StorybookWrapperComponent.ts @@ -16,7 +16,7 @@ import { map, skip } from 'rxjs/operators'; import { ICollection, NgModuleMetadata } from '../types'; import { STORY_PROPS } from './StorybookProvider'; import { ComponentInputsOutputs, getComponentInputsOutputs } from './utils/NgComponentAnalyzer'; -import { extractDeclarations, extractImports, extractProviders } from './utils/PropertyExtractor'; +import { PropertyExtractor } from './utils/PropertyExtractor'; const getNonInputsOutputsProps = ( ngComponentInputsOutputs: ComponentInputsOutputs, @@ -52,9 +52,8 @@ export const createStorybookWrapperComponent = ( // storyComponent was not provided. const viewChildSelector = storyComponent ?? '__storybook-noop'; - const imports = extractImports(moduleMetadata, storyComponent); - const declarations = extractDeclarations(moduleMetadata, storyComponent); - const providers = extractProviders(moduleMetadata); + const analyzedMetadata = new PropertyExtractor(moduleMetadata, storyComponent); + const { imports, declarations, providers } = analyzedMetadata; // Only create a new module if it doesn't already exist // This is to prevent the module from being recreated on every story change diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts index fffe3ace608b..5930115bed4f 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts @@ -7,15 +7,9 @@ import { provideAnimations, provideNoopAnimations, } from '@angular/platform-browser/animations'; -import { HttpClientModule } from '@angular/common/http'; -import { - analyzeMetadata, - extractDeclarations, - extractImports, - extractProviders, - extractSingletons, - REMOVED_MODULES, -} from './PropertyExtractor'; +import { provideHttpClient } from '@angular/common/http'; +import { NgModuleMetadata } from '../../types'; +import { PropertyExtractor, REMOVED_MODULES } from './PropertyExtractor'; import { WithAnimationsModule, WithOfficialModule } from '../__testfixtures__/test.module'; const TEST_TOKEN = new InjectionToken('testToken'); @@ -31,6 +25,26 @@ const TestModuleWithImportsAndProviders = NgModule({ providers: [TestTokenProvider], })(class {}); +const analyzeMetadata = (metadata: NgModuleMetadata, component?: any) => { + return new PropertyExtractor(metadata, component); +}; +const extractImports = (metadata: NgModuleMetadata, component?: any) => { + const { imports } = new PropertyExtractor(metadata, component); + return imports; +}; +const extractDeclarations = (metadata: NgModuleMetadata, component?: any) => { + const { declarations } = new PropertyExtractor(metadata, component); + return declarations; +}; +const extractProviders = (metadata: NgModuleMetadata, component?: any) => { + const { providers } = new PropertyExtractor(metadata, component); + return providers; +}; +const extractSingletons = (metadata: NgModuleMetadata, component?: any) => { + const { singletons } = new PropertyExtractor(metadata, component); + return singletons; +}; + describe('PropertyExtractor', () => { describe('analyzeMetadata', () => { it('should remove BrowserModule', () => { @@ -38,7 +52,7 @@ describe('PropertyExtractor', () => { imports: [BrowserModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(singletons.flat(Number.MAX_VALUE)).toEqual([]); }); @@ -48,7 +62,7 @@ describe('PropertyExtractor', () => { imports: [BrowserAnimationsModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(singletons.flat(Number.MAX_VALUE)).toEqual(provideAnimations()); }); @@ -58,7 +72,7 @@ describe('PropertyExtractor', () => { imports: [NoopAnimationsModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(singletons.flat(Number.MAX_VALUE)).toEqual(provideNoopAnimations()); }); @@ -68,7 +82,7 @@ describe('PropertyExtractor', () => { imports: [WithAnimationsModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(providers.flat(Number.MAX_VALUE)).toEqual([ { provide: REMOVED_MODULES, useValue: WithAnimationsModule, multi: true }, ]); @@ -80,11 +94,11 @@ describe('PropertyExtractor', () => { imports: [WithOfficialModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule, HttpClientModule]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); expect(providers.flat(Number.MAX_VALUE)).toEqual([ { provide: REMOVED_MODULES, useValue: WithOfficialModule, multi: true }, ]); - expect(singletons.flat(Number.MAX_VALUE)).toEqual([]); + expect(singletons.flat(Number.MAX_VALUE)).toEqual([provideHttpClient()]); }); }); diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts index 6484257b4a6e..c68f105e43f0 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts @@ -1,6 +1,17 @@ import { CommonModule } from '@angular/common'; import { HttpClientModule, provideHttpClient } from '@angular/common/http'; -import { InjectionToken, NgModule, Provider } from '@angular/core'; +import { + Component, + Directive, + Injectable, + InjectionToken, + Input, + NgModule, + Output, + Pipe, + Provider, + ɵReflectionCapabilities as ReflectionCapabilities, +} from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule, @@ -9,173 +20,203 @@ import { provideNoopAnimations, } from '@angular/platform-browser/animations'; import { NgModuleMetadata } from '../../types'; -import { isDeclarable, isStandaloneComponent } from './NgComponentAnalyzer'; import { isComponentAlreadyDeclared } from './NgModulesAnalyzer'; -const uniqueArray = (arr: any[]) => { - return arr.flat(Number.MAX_VALUE).filter((value, index, self) => self.indexOf(value) === index); +export const reflectionCapabilities = new ReflectionCapabilities(); +export const REMOVED_MODULES = new InjectionToken('REMOVED_MODULES'); +export const uniqueArray = (arr: any[]) => { + return arr + .flat(Number.MAX_VALUE) + .filter(Boolean) + .filter((value, index, self) => self.indexOf(value) === index); }; -const analyzeRestricted = (ngModule: NgModule) => { - /** - * BrowserModule is restricted, - * because bootstrapApplication API, which mounts the component to the DOM, - * automatically imports BrowserModule - */ - if (ngModule === BrowserModule) { - return [true]; - } - /** - * BrowserAnimationsModule imports BrowserModule, which is restricted, - * because bootstrapApplication API, which mounts the component to the DOM, - * automatically imports BrowserModule - */ - if (ngModule === BrowserAnimationsModule) { - return [true, provideAnimations()]; +export class PropertyExtractor implements NgModuleMetadata { + /* eslint-disable @typescript-eslint/lines-between-class-members */ + declarations?: any[] = []; + imports?: any[]; + providers?: Provider[]; + singletons?: Provider[]; + dependencies?: Provider[]; + /* eslint-enable @typescript-eslint/lines-between-class-members */ + + constructor(private metadata: NgModuleMetadata, private component?: any) { + this.init(); } - /** - * NoopAnimationsModule imports BrowserModule, which is restricted, - * because bootstrapApplication API, which mounts the component to the DOM, - * automatically imports BrowserModule - */ - if (ngModule === NoopAnimationsModule) { - return [true, provideNoopAnimations()]; + + private init() { + const analyzed = this.analyzeMetadata(this.metadata); + this.imports = uniqueArray([CommonModule, analyzed.imports]); + this.providers = uniqueArray(analyzed.providers); + this.singletons = uniqueArray(analyzed.singletons); + this.declarations = uniqueArray(analyzed.declarations); + + if (this.component) { + const { isStandalone, isDeclarable } = PropertyExtractor.analyzeDecorators(this.component); + const isDeclared = isComponentAlreadyDeclared( + this.component, + analyzed.declarations, + this.imports + ); + + if (isStandalone) { + this.imports.push(this.component); + } else if (isDeclarable && !isDeclared) { + this.declarations.push(this.component); + } + } + + this.dependencies = uniqueArray( + this.declarations.map((dec) => { + const params = dec?.ctorParameters; + if (params) { + return params().map((p: any) => p?.type); + } + return null; + }) + ); } /** - * HttpClient has to be provided manually as a singleton + * Analyze NgModule Metadata + * + * - Removes Restricted Imports + * - Extracts providers from ModuleWithProviders + * - Flattens imports + * - Returns a new NgModuleMetadata object + * + * */ - if (ngModule === HttpClientModule) { - return [true, provideHttpClient()]; - } - - return [false]; -}; - -export const REMOVED_MODULES = new InjectionToken('REMOVED_MODULES'); - -/** - * Analyze NgModule Metadata - * - * - Removes Restricted Imports - * - Extracts providers from ModuleWithProviders - * - Flattens imports - * - Returns a new NgModuleMetadata object - * - * - */ -export const analyzeMetadata = (metadata: NgModuleMetadata) => { - const declarations = [...(metadata?.declarations || [])]; - const providers = [...(metadata?.providers || [])]; - const singletons: any[] = []; - const imports = [...(metadata?.imports || [])] - .reduce((acc, ngModule) => { + private analyzeMetadata = (metadata: NgModuleMetadata) => { + const declarations = [...(metadata?.declarations || [])]; + const providers = [...(metadata?.providers || [])]; + const singletons: any[] = [...(metadata?.singletons || [])]; + const dependencies: any[] = [...(metadata?.dependencies || [])]; + const imports = [...(metadata?.imports || [])].reduce((acc, imported) => { // remove ngModule and use only its providers if it is restricted // (e.g. BrowserModule, BrowserAnimationsModule, NoopAnimationsModule, ...etc) - const [isRestricted, restrictedProviders] = analyzeRestricted(ngModule); + const [isRestricted, restrictedProviders] = PropertyExtractor.analyzeRestricted(imported); if (isRestricted) { singletons.unshift(restrictedProviders || []); return acc; } // destructure into ngModule & providers if it is a ModuleWithProviders - if (ngModule?.providers) { - providers.unshift(ngModule.providers || []); + if (imported?.providers) { + providers.unshift(imported.providers || []); // eslint-disable-next-line no-param-reassign - ngModule = ngModule.ngModule; + imported = imported.ngModule; } // extract providers, declarations, singletons from ngModule // eslint-disable-next-line no-underscore-dangle - const ngMetadata = ngModule?.__annotations__?.[0]; + const ngMetadata = imported?.__annotations__?.[0]; if (ngMetadata) { - const newMetadata = analyzeMetadata(ngMetadata); + const newMetadata = this.analyzeMetadata(ngMetadata); acc.unshift(...newMetadata.imports); providers.unshift(...newMetadata.providers); singletons.unshift(...newMetadata.singletons); + dependencies.unshift(...newMetadata.dependencies); declarations.unshift(...newMetadata.declarations); if (ngMetadata.standalone === true) { - acc.push(ngModule); + acc.push(imported); } // keeping a copy of the removed module - providers.push({ provide: REMOVED_MODULES, useValue: ngModule, multi: true }); + providers.push({ provide: REMOVED_MODULES, useValue: imported, multi: true }); return acc; } // include Angular official modules as-is - if (ngModule.ɵmod) { - acc.push(ngModule); + if (imported.ɵmod) { + acc.push(imported); return acc; } return acc; - }, []) - .flat(Number.MAX_VALUE); - - return { ...metadata, imports, providers, singletons, declarations }; -}; - -/** - * Extract Imports from NgModule - * - * CommonModule is always imported - * Only standalone components are imported - * - */ -export const extractImports = (metadata: NgModuleMetadata, storyComponent?: any) => { - const { imports } = analyzeMetadata(metadata); - - if (isStandaloneComponent(storyComponent)) { - imports.push(storyComponent); - } - - return uniqueArray([CommonModule, imports]); -}; - -/** - * Extract providers from NgModule - * - * - A new array is returned with: - * - metadata.providers - * - providers from each **ModuleWithProviders** (e.g. forRoot() & forChild() ) - * - * - */ -export const extractProviders = (metadata: NgModuleMetadata): Provider[] => { - const { providers } = analyzeMetadata(metadata); - - return uniqueArray(providers); -}; - -export const extractSingletons = (metadata: NgModuleMetadata): Provider[] => { - const { singletons } = analyzeMetadata(metadata); - - return uniqueArray(singletons); -}; + }, []); + + return { ...metadata, imports, providers, singletons, dependencies, declarations }; + }; + + static analyzeRestricted = (ngModule: NgModule) => { + /** + * BrowserModule is restricted, + * because bootstrapApplication API, which mounts the component to the DOM, + * automatically imports BrowserModule + */ + if (ngModule === BrowserModule) { + return [true]; + } + /** + * BrowserAnimationsModule imports BrowserModule, which is restricted, + * because bootstrapApplication API, which mounts the component to the DOM, + * automatically imports BrowserModule + */ + if (ngModule === BrowserAnimationsModule) { + return [true, provideAnimations()]; + } + /** + * NoopAnimationsModule imports BrowserModule, which is restricted, + * because bootstrapApplication API, which mounts the component to the DOM, + * automatically imports BrowserModule + */ + if (ngModule === NoopAnimationsModule) { + return [true, provideNoopAnimations()]; + } -/** - * Extract declarations from NgModule - * - * - If a story component is provided, it will be added to the declarations array if: - * - It is a component or directive or pipe - * - It is not already declared - * - It is not a standalone component - * - */ -export const extractDeclarations = (metadata: NgModuleMetadata, storyComponent?: any) => { - const { declarations } = analyzeMetadata(metadata); - - if (storyComponent) { - const isStandalone = isStandaloneComponent(storyComponent); - const isDeclared = isComponentAlreadyDeclared(storyComponent, declarations, metadata.imports); - - const requiresDeclaration = isDeclarable(storyComponent) && !isDeclared && !isStandalone; - - if (requiresDeclaration) { - declarations.push(storyComponent); + /** + * HttpClient has to be provided manually as a singleton + */ + if (ngModule === HttpClientModule) { + return [true, provideHttpClient()]; } - } - return uniqueArray(declarations); -}; + return [false]; + }; + + static analyzeDecorators = (component: any) => { + const decorators = reflectionCapabilities.annotations(component); + + const isComponent = decorators.some((d) => this.isDecoratorInstanceOf(d, 'Component')); + const isDirective = decorators.some((d) => this.isDecoratorInstanceOf(d, 'Directive')); + const isPipe = decorators.some((d) => this.isDecoratorInstanceOf(d, 'Pipe')); + + const isDeclarable = isComponent || isDirective || isPipe; + const isStandalone = isComponent && decorators.some((d) => d.standalone); + + return { isComponent, isDirective, isPipe, isDeclarable, isStandalone }; + }; + + static getDecoratorByType = (component: any, type: string) => { + const decorators = reflectionCapabilities.annotations(component); + return decorators.find((d) => this.isDecoratorInstanceOf(d, type)); + }; + + static isDecoratorInstanceOf = (decorator: any, name: string) => { + let factory; + switch (name) { + case 'Component': + factory = Component; + break; + case 'Directive': + factory = Directive; + break; + case 'Pipe': + factory = Pipe; + break; + case 'Injectable': + factory = Injectable; + break; + case 'Input': + factory = Input; + break; + case 'Output': + factory = Output; + break; + default: + throw new Error(`Unknown decorator type: ${name}`); + } + return decorator instanceof factory || decorator.ngMetadataName === name; + }; +} diff --git a/code/frameworks/angular/src/client/types.ts b/code/frameworks/angular/src/client/types.ts index 2818ec33353f..892ca2205f40 100644 --- a/code/frameworks/angular/src/client/types.ts +++ b/code/frameworks/angular/src/client/types.ts @@ -10,6 +10,8 @@ export interface NgModuleMetadata { imports?: any[]; schemas?: any[]; providers?: any[]; + singletons?: any[]; + dependencies?: any[]; } export interface ICollection { [p: string]: any; From a47f791ce2350fe93d7095e21b2dcd56a1d554da Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 16 Feb 2023 16:49:45 +0100 Subject: [PATCH 2/6] importProviders from ngModules on boostrapApplication --- code/frameworks/angular/package.json | 1 + .../client/angular-beta/AbstractRenderer.ts | 6 +-- .../angular-beta/utils/PropertyExtractor.ts | 46 +------------------ code/frameworks/angular/src/client/types.ts | 1 - .../provided-in.stories.ts | 24 ++++++++++ code/yarn.lock | 13 ++++++ 6 files changed, 43 insertions(+), 48 deletions(-) create mode 100644 code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 9ae9f114c1e0..b3c1964e18db 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -77,6 +77,7 @@ "@angular/forms": "^15.1.1", "@angular/platform-browser": "^15.1.1", "@angular/platform-browser-dynamic": "^15.1.1", + "@ngxs/store": "^3.7.6", "@types/rimraf": "^3.0.2", "@types/tmp": "^0.2.3", "cross-spawn": "^7.0.3", diff --git a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts index 7b9a36230d97..b72ba0e5581e 100644 --- a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts +++ b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts @@ -1,4 +1,4 @@ -import { ApplicationRef, enableProdMode, NgModule } from '@angular/core'; +import { ApplicationRef, enableProdMode, importProvidersFrom, NgModule } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { BehaviorSubject, Subject } from 'rxjs'; @@ -125,9 +125,9 @@ export abstract class AbstractRenderer { // Providers for BrowserAnimations & NoopAnimationsModule analyzedMetadata.singletons, analyzedMetadata.providers, - analyzedMetadata.dependencies, + importProvidersFrom(...analyzedMetadata.imports), storyPropsProvider(newStoryProps$), - ]; + ].filter(Boolean); const application = getApplication({ storyFnAngular, component, targetSelector }); diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts index c68f105e43f0..42cc4e70fc6d 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts @@ -37,7 +37,6 @@ export class PropertyExtractor implements NgModuleMetadata { imports?: any[]; providers?: Provider[]; singletons?: Provider[]; - dependencies?: Provider[]; /* eslint-enable @typescript-eslint/lines-between-class-members */ constructor(private metadata: NgModuleMetadata, private component?: any) { @@ -65,16 +64,6 @@ export class PropertyExtractor implements NgModuleMetadata { this.declarations.push(this.component); } } - - this.dependencies = uniqueArray( - this.declarations.map((dec) => { - const params = dec?.ctorParameters; - if (params) { - return params().map((p: any) => p?.type); - } - return null; - }) - ); } /** @@ -91,7 +80,6 @@ export class PropertyExtractor implements NgModuleMetadata { const declarations = [...(metadata?.declarations || [])]; const providers = [...(metadata?.providers || [])]; const singletons: any[] = [...(metadata?.singletons || [])]; - const dependencies: any[] = [...(metadata?.dependencies || [])]; const imports = [...(metadata?.imports || [])].reduce((acc, imported) => { // remove ngModule and use only its providers if it is restricted // (e.g. BrowserModule, BrowserAnimationsModule, NoopAnimationsModule, ...etc) @@ -101,42 +89,12 @@ export class PropertyExtractor implements NgModuleMetadata { return acc; } - // destructure into ngModule & providers if it is a ModuleWithProviders - if (imported?.providers) { - providers.unshift(imported.providers || []); - // eslint-disable-next-line no-param-reassign - imported = imported.ngModule; - } - - // extract providers, declarations, singletons from ngModule - // eslint-disable-next-line no-underscore-dangle - const ngMetadata = imported?.__annotations__?.[0]; - if (ngMetadata) { - const newMetadata = this.analyzeMetadata(ngMetadata); - acc.unshift(...newMetadata.imports); - providers.unshift(...newMetadata.providers); - singletons.unshift(...newMetadata.singletons); - dependencies.unshift(...newMetadata.dependencies); - declarations.unshift(...newMetadata.declarations); - - if (ngMetadata.standalone === true) { - acc.push(imported); - } - // keeping a copy of the removed module - providers.push({ provide: REMOVED_MODULES, useValue: imported, multi: true }); - return acc; - } - - // include Angular official modules as-is - if (imported.ɵmod) { - acc.push(imported); - return acc; - } + acc.push(imported); return acc; }, []); - return { ...metadata, imports, providers, singletons, dependencies, declarations }; + return { ...metadata, imports, providers, singletons, declarations }; }; static analyzeRestricted = (ngModule: NgModule) => { diff --git a/code/frameworks/angular/src/client/types.ts b/code/frameworks/angular/src/client/types.ts index 892ca2205f40..656ce3e43dab 100644 --- a/code/frameworks/angular/src/client/types.ts +++ b/code/frameworks/angular/src/client/types.ts @@ -11,7 +11,6 @@ export interface NgModuleMetadata { schemas?: any[]; providers?: any[]; singletons?: any[]; - dependencies?: any[]; } export interface ICollection { [p: string]: any; diff --git a/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts new file mode 100644 index 000000000000..a611182ead1a --- /dev/null +++ b/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { NgxsModule } from '@ngxs/store'; +import { Meta, moduleMetadata, StoryFn } from '@storybook/angular'; + +@Component({ + selector: 'app-test', + template: `hello`, +}) +class TestComponent {} + +export default { + component: TestComponent, + decorators: [ + moduleMetadata({ + imports: [NgxsModule.forRoot([])], + }), + ], +} as Meta; + +const Template: StoryFn = (args: TestComponent) => ({ + props: args, +}); + +export const Ngxs = Template.bind({}); diff --git a/code/yarn.lock b/code/yarn.lock index db8153fd60ac..df58c0558dc3 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -3382,6 +3382,18 @@ __metadata: languageName: node linkType: hard +"@ngxs/store@npm:^3.7.6": + version: 3.7.6 + resolution: "@ngxs/store@npm:3.7.6" + dependencies: + tslib: ^1.9.0 + peerDependencies: + "@angular/core": ">=6.1.0 <16.0.0" + rxjs: ">=6.5.5" + checksum: 38e9c0e9830712e9dfa0de6c0226e16fd5cf0cf9960d0eb1ebf67f35228225685536be12c920c0755298cc0e95f2eb673ede91f23ed78d8f0cdde04ae3c51cdd + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5052,6 +5064,7 @@ __metadata: "@angular/forms": ^15.1.1 "@angular/platform-browser": ^15.1.1 "@angular/platform-browser-dynamic": ^15.1.1 + "@ngxs/store": ^3.7.6 "@storybook/builder-webpack5": 7.0.0-beta.48 "@storybook/cli": 7.0.0-beta.48 "@storybook/client-logger": 7.0.0-beta.48 From 0b54569d2c3760d2507170652d2a2854155c23ea Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 16 Feb 2023 16:49:45 +0100 Subject: [PATCH 3/6] importProviders from ngModules on boostrapApplication --- code/frameworks/angular/package.json | 1 + .../client/angular-beta/AbstractRenderer.ts | 14 +++-- .../angular-beta/utils/PropertyExtractor.ts | 54 +++---------------- code/frameworks/angular/src/client/types.ts | 1 - .../provided-in.stories.ts | 24 +++++++++ code/yarn.lock | 13 +++++ 6 files changed, 55 insertions(+), 52 deletions(-) create mode 100644 code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts diff --git a/code/frameworks/angular/package.json b/code/frameworks/angular/package.json index 9ae9f114c1e0..b3c1964e18db 100644 --- a/code/frameworks/angular/package.json +++ b/code/frameworks/angular/package.json @@ -77,6 +77,7 @@ "@angular/forms": "^15.1.1", "@angular/platform-browser": "^15.1.1", "@angular/platform-browser-dynamic": "^15.1.1", + "@ngxs/store": "^3.7.6", "@types/rimraf": "^3.0.2", "@types/tmp": "^0.2.3", "cross-spawn": "^7.0.3", diff --git a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts index 7b9a36230d97..477aedc75050 100644 --- a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts +++ b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts @@ -1,4 +1,10 @@ -import { ApplicationRef, enableProdMode, NgModule } from '@angular/core'; +import { + ApplicationRef, + enableProdMode, + importProvidersFrom, + isStandalone, + NgModule, +} from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { BehaviorSubject, Subject } from 'rxjs'; @@ -125,9 +131,11 @@ export abstract class AbstractRenderer { // Providers for BrowserAnimations & NoopAnimationsModule analyzedMetadata.singletons, analyzedMetadata.providers, - analyzedMetadata.dependencies, + importProvidersFrom( + ...analyzedMetadata.imports.filter((imported) => !isStandalone(imported)) + ), storyPropsProvider(newStoryProps$), - ]; + ].filter(Boolean); const application = getApplication({ storyFnAngular, component, targetSelector }); diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts index c68f105e43f0..0d1381ce7ee2 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts @@ -6,6 +6,7 @@ import { Injectable, InjectionToken, Input, + isStandalone, NgModule, Output, Pipe, @@ -37,7 +38,6 @@ export class PropertyExtractor implements NgModuleMetadata { imports?: any[]; providers?: Provider[]; singletons?: Provider[]; - dependencies?: Provider[]; /* eslint-enable @typescript-eslint/lines-between-class-members */ constructor(private metadata: NgModuleMetadata, private component?: any) { @@ -52,29 +52,19 @@ export class PropertyExtractor implements NgModuleMetadata { this.declarations = uniqueArray(analyzed.declarations); if (this.component) { - const { isStandalone, isDeclarable } = PropertyExtractor.analyzeDecorators(this.component); + const { isDeclarable } = PropertyExtractor.analyzeDecorators(this.component); const isDeclared = isComponentAlreadyDeclared( this.component, analyzed.declarations, this.imports ); - if (isStandalone) { + if (isStandalone(this.component)) { this.imports.push(this.component); } else if (isDeclarable && !isDeclared) { this.declarations.push(this.component); } } - - this.dependencies = uniqueArray( - this.declarations.map((dec) => { - const params = dec?.ctorParameters; - if (params) { - return params().map((p: any) => p?.type); - } - return null; - }) - ); } /** @@ -91,7 +81,6 @@ export class PropertyExtractor implements NgModuleMetadata { const declarations = [...(metadata?.declarations || [])]; const providers = [...(metadata?.providers || [])]; const singletons: any[] = [...(metadata?.singletons || [])]; - const dependencies: any[] = [...(metadata?.dependencies || [])]; const imports = [...(metadata?.imports || [])].reduce((acc, imported) => { // remove ngModule and use only its providers if it is restricted // (e.g. BrowserModule, BrowserAnimationsModule, NoopAnimationsModule, ...etc) @@ -101,42 +90,12 @@ export class PropertyExtractor implements NgModuleMetadata { return acc; } - // destructure into ngModule & providers if it is a ModuleWithProviders - if (imported?.providers) { - providers.unshift(imported.providers || []); - // eslint-disable-next-line no-param-reassign - imported = imported.ngModule; - } - - // extract providers, declarations, singletons from ngModule - // eslint-disable-next-line no-underscore-dangle - const ngMetadata = imported?.__annotations__?.[0]; - if (ngMetadata) { - const newMetadata = this.analyzeMetadata(ngMetadata); - acc.unshift(...newMetadata.imports); - providers.unshift(...newMetadata.providers); - singletons.unshift(...newMetadata.singletons); - dependencies.unshift(...newMetadata.dependencies); - declarations.unshift(...newMetadata.declarations); - - if (ngMetadata.standalone === true) { - acc.push(imported); - } - // keeping a copy of the removed module - providers.push({ provide: REMOVED_MODULES, useValue: imported, multi: true }); - return acc; - } - - // include Angular official modules as-is - if (imported.ɵmod) { - acc.push(imported); - return acc; - } + acc.push(imported); return acc; }, []); - return { ...metadata, imports, providers, singletons, dependencies, declarations }; + return { ...metadata, imports, providers, singletons, declarations }; }; static analyzeRestricted = (ngModule: NgModule) => { @@ -183,9 +142,8 @@ export class PropertyExtractor implements NgModuleMetadata { const isPipe = decorators.some((d) => this.isDecoratorInstanceOf(d, 'Pipe')); const isDeclarable = isComponent || isDirective || isPipe; - const isStandalone = isComponent && decorators.some((d) => d.standalone); - return { isComponent, isDirective, isPipe, isDeclarable, isStandalone }; + return { isDeclarable }; }; static getDecoratorByType = (component: any, type: string) => { diff --git a/code/frameworks/angular/src/client/types.ts b/code/frameworks/angular/src/client/types.ts index 892ca2205f40..656ce3e43dab 100644 --- a/code/frameworks/angular/src/client/types.ts +++ b/code/frameworks/angular/src/client/types.ts @@ -11,7 +11,6 @@ export interface NgModuleMetadata { schemas?: any[]; providers?: any[]; singletons?: any[]; - dependencies?: any[]; } export interface ICollection { [p: string]: any; diff --git a/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts new file mode 100644 index 000000000000..a611182ead1a --- /dev/null +++ b/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { NgxsModule } from '@ngxs/store'; +import { Meta, moduleMetadata, StoryFn } from '@storybook/angular'; + +@Component({ + selector: 'app-test', + template: `hello`, +}) +class TestComponent {} + +export default { + component: TestComponent, + decorators: [ + moduleMetadata({ + imports: [NgxsModule.forRoot([])], + }), + ], +} as Meta; + +const Template: StoryFn = (args: TestComponent) => ({ + props: args, +}); + +export const Ngxs = Template.bind({}); diff --git a/code/yarn.lock b/code/yarn.lock index db8153fd60ac..df58c0558dc3 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -3382,6 +3382,18 @@ __metadata: languageName: node linkType: hard +"@ngxs/store@npm:^3.7.6": + version: 3.7.6 + resolution: "@ngxs/store@npm:3.7.6" + dependencies: + tslib: ^1.9.0 + peerDependencies: + "@angular/core": ">=6.1.0 <16.0.0" + rxjs: ">=6.5.5" + checksum: 38e9c0e9830712e9dfa0de6c0226e16fd5cf0cf9960d0eb1ebf67f35228225685536be12c920c0755298cc0e95f2eb673ede91f23ed78d8f0cdde04ae3c51cdd + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5052,6 +5064,7 @@ __metadata: "@angular/forms": ^15.1.1 "@angular/platform-browser": ^15.1.1 "@angular/platform-browser-dynamic": ^15.1.1 + "@ngxs/store": ^3.7.6 "@storybook/builder-webpack5": 7.0.0-beta.48 "@storybook/cli": 7.0.0-beta.48 "@storybook/client-logger": 7.0.0-beta.48 From aa95dc47f0ae1bad584f34f49b3eace4f5707e86 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 16 Feb 2023 18:14:16 +0100 Subject: [PATCH 4/6] Fix tests and remove obsolete ones --- .../__testfixtures__/test.module.ts | 11 -- .../utils/PropertyExtractor.test.ts | 106 ++---------------- .../angular-beta/utils/PropertyExtractor.ts | 8 -- code/yarn.lock | 12 -- 4 files changed, 12 insertions(+), 125 deletions(-) diff --git a/code/frameworks/angular/src/client/angular-beta/__testfixtures__/test.module.ts b/code/frameworks/angular/src/client/angular-beta/__testfixtures__/test.module.ts index 439a93b31493..36536f3873e1 100644 --- a/code/frameworks/angular/src/client/angular-beta/__testfixtures__/test.module.ts +++ b/code/frameworks/angular/src/client/angular-beta/__testfixtures__/test.module.ts @@ -1,17 +1,6 @@ import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; - -@NgModule({ - imports: [ - // - BrowserModule, - BrowserAnimationsModule, - ], -}) -export class WithAnimationsModule {} @NgModule({ imports: [CommonModule, HttpClientModule], diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts index 5930115bed4f..e5ea91131602 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.test.ts @@ -7,10 +7,9 @@ import { provideAnimations, provideNoopAnimations, } from '@angular/platform-browser/animations'; -import { provideHttpClient } from '@angular/common/http'; import { NgModuleMetadata } from '../../types'; import { PropertyExtractor, REMOVED_MODULES } from './PropertyExtractor'; -import { WithAnimationsModule, WithOfficialModule } from '../__testfixtures__/test.module'; +import { WithOfficialModule } from '../__testfixtures__/test.module'; const TEST_TOKEN = new InjectionToken('testToken'); const TestTokenProvider = { provide: TEST_TOKEN, useValue: 123 }; @@ -79,13 +78,11 @@ describe('PropertyExtractor', () => { it('should remove Browser/Animations modules recursively', () => { const metadata = { - imports: [WithAnimationsModule], + imports: [BrowserAnimationsModule, BrowserModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); - expect(providers.flat(Number.MAX_VALUE)).toEqual([ - { provide: REMOVED_MODULES, useValue: WithAnimationsModule, multi: true }, - ]); + expect(providers.flat(Number.MAX_VALUE)).toEqual([]); expect(singletons.flat(Number.MAX_VALUE)).toEqual(provideAnimations()); }); @@ -94,18 +91,16 @@ describe('PropertyExtractor', () => { imports: [WithOfficialModule], }; const { imports, providers, singletons } = analyzeMetadata(metadata); - expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule]); - expect(providers.flat(Number.MAX_VALUE)).toEqual([ - { provide: REMOVED_MODULES, useValue: WithOfficialModule, multi: true }, - ]); - expect(singletons.flat(Number.MAX_VALUE)).toEqual([provideHttpClient()]); + expect(imports.flat(Number.MAX_VALUE)).toEqual([CommonModule, WithOfficialModule]); + expect(providers.flat(Number.MAX_VALUE)).toEqual([]); + expect(singletons.flat(Number.MAX_VALUE)).toEqual([]); }); }); describe('extractImports', () => { it('should return Angular official modules', () => { const imports = extractImports({ imports: [TestModuleWithImportsAndProviders] }); - expect(imports).toEqual([CommonModule]); + expect(imports).toEqual([CommonModule, TestModuleWithImportsAndProviders]); }); it('should return standalone components', () => { @@ -115,17 +110,11 @@ describe('PropertyExtractor', () => { }, StandaloneTestComponent ); - expect(imports).toEqual([CommonModule, StandaloneTestComponent]); - }); - - it('should not return any regular modules (they get destructured)', () => { - const imports = extractImports({ - imports: [ - TestModuleWithDeclarations, - { ngModule: TestModuleWithImportsAndProviders, providers: [] }, - ], - }); - expect(imports).toEqual([CommonModule]); + expect(imports).toEqual([ + CommonModule, + TestModuleWithImportsAndProviders, + StandaloneTestComponent, + ]); }); }); @@ -134,44 +123,6 @@ describe('PropertyExtractor', () => { const declarations = extractDeclarations({ declarations: [TestComponent1] }, TestComponent2); expect(declarations).toEqual([TestComponent1, TestComponent2]); }); - - it('should ignore `storyComponent` if it is already declared', () => { - const declarations = extractDeclarations( - { - imports: [TestModuleWithImportsAndProviders], - declarations: [TestComponent2, StandaloneTestComponent, TestDirective], - }, - TestComponent1 - ); - expect(declarations).toEqual([ - TestComponent1, - TestComponent2, - StandaloneTestComponent, - TestDirective, - ]); - }); - - it('should ignore `storyComponent` if it is standalone', () => { - const declarations = extractDeclarations( - { - imports: [TestModuleWithImportsAndProviders], - declarations: [TestComponent1, TestComponent2, TestDirective], - }, - StandaloneTestComponent - ); - expect(declarations).toEqual([TestComponent1, TestComponent2, TestDirective]); - }); - - it('should ignore `storyComponent` if it is not a component/directive/pipe', () => { - const declarations = extractDeclarations( - { - imports: [TestModuleWithImportsAndProviders], - declarations: [TestComponent1, TestComponent2, StandaloneTestComponent], - }, - TestService - ); - expect(declarations).toEqual([TestComponent1, TestComponent2, StandaloneTestComponent]); - }); }); describe('extractProviders', () => { @@ -182,39 +133,6 @@ describe('PropertyExtractor', () => { expect(providers).toEqual([TestService]); }); - it('should return an array of providers extracted from ModuleWithProviders', () => { - const providers = extractProviders({ - imports: [{ ngModule: TestModuleWithImportsAndProviders, providers: [TestService] }], - }); - expect(providers).toEqual([ - { provide: TEST_TOKEN, useValue: 123 }, - { provide: REMOVED_MODULES, useValue: TestModuleWithDeclarations, multi: true }, - TestService, - { provide: REMOVED_MODULES, useValue: TestModuleWithImportsAndProviders, multi: true }, - ]); - }); - - it('should return an array of unique providers', () => { - const providers = extractProviders({ - imports: [{ ngModule: TestModuleWithImportsAndProviders, providers: [TestService] }], - providers: [TestService, { provide: TEST_TOKEN, useValue: 123 }], - }); - expect(providers).toEqual([ - { provide: TEST_TOKEN, useValue: 123 }, - { provide: REMOVED_MODULES, useValue: TestModuleWithDeclarations, multi: true }, - TestService, - { - provide: new InjectionToken('testToken'), - useValue: 123, - }, - { - provide: REMOVED_MODULES, - useValue: TestModuleWithImportsAndProviders, - multi: true, - }, - ]); - }); - it('should return an array of singletons extracted', () => { const singeltons = extractSingletons({ imports: [BrowserAnimationsModule], diff --git a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts index 0d1381ce7ee2..a2c82071368b 100644 --- a/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts +++ b/code/frameworks/angular/src/client/angular-beta/utils/PropertyExtractor.ts @@ -1,5 +1,4 @@ import { CommonModule } from '@angular/common'; -import { HttpClientModule, provideHttpClient } from '@angular/common/http'; import { Component, Directive, @@ -124,13 +123,6 @@ export class PropertyExtractor implements NgModuleMetadata { return [true, provideNoopAnimations()]; } - /** - * HttpClient has to be provided manually as a singleton - */ - if (ngModule === HttpClientModule) { - return [true, provideHttpClient()]; - } - return [false]; }; diff --git a/code/yarn.lock b/code/yarn.lock index e80149cdcae3..605cc4fdbb6f 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -3395,18 +3395,6 @@ __metadata: languageName: node linkType: hard -"@ngxs/store@npm:^3.7.6": - version: 3.7.6 - resolution: "@ngxs/store@npm:3.7.6" - dependencies: - tslib: ^1.9.0 - peerDependencies: - "@angular/core": ">=6.1.0 <16.0.0" - rxjs: ">=6.5.5" - checksum: 38e9c0e9830712e9dfa0de6c0226e16fd5cf0cf9960d0eb1ebf67f35228225685536be12c920c0755298cc0e95f2eb673ede91f23ed78d8f0cdde04ae3c51cdd - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" From e483723de57bbc9fea4b63a8d3f8d3559a678486 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 17 Feb 2023 09:51:45 +0100 Subject: [PATCH 5/6] Add some stories for angular --- .../client/angular-beta/AbstractRenderer.ts | 2 +- .../provided-in.stories.ts | 24 ------------- .../app-initializer-use-factory.stories.ts | 34 +++++++++++++++++++ 3 files changed, 35 insertions(+), 25 deletions(-) delete mode 100644 code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts create mode 100644 code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts diff --git a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts index 477aedc75050..5088bba3313f 100644 --- a/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts +++ b/code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts @@ -130,10 +130,10 @@ export abstract class AbstractRenderer { const providers = [ // Providers for BrowserAnimations & NoopAnimationsModule analyzedMetadata.singletons, - analyzedMetadata.providers, importProvidersFrom( ...analyzedMetadata.imports.filter((imported) => !isStandalone(imported)) ), + analyzedMetadata.providers, storyPropsProvider(newStoryProps$), ].filter(Boolean); diff --git a/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts b/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts deleted file mode 100644 index a611182ead1a..000000000000 --- a/code/frameworks/angular/template/stories/basics/component-with-provided-in/provided-in.stories.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from '@angular/core'; -import { NgxsModule } from '@ngxs/store'; -import { Meta, moduleMetadata, StoryFn } from '@storybook/angular'; - -@Component({ - selector: 'app-test', - template: `hello`, -}) -class TestComponent {} - -export default { - component: TestComponent, - decorators: [ - moduleMetadata({ - imports: [NgxsModule.forRoot([])], - }), - ], -} as Meta; - -const Template: StoryFn = (args: TestComponent) => ({ - props: args, -}); - -export const Ngxs = Template.bind({}); diff --git a/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts b/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts new file mode 100644 index 000000000000..2dc73b7ad3a0 --- /dev/null +++ b/code/frameworks/angular/template/stories/others/app-initializer-use-factory/app-initializer-use-factory.stories.ts @@ -0,0 +1,34 @@ +import { moduleMetadata, Meta } from '@storybook/angular'; +import { APP_INITIALIZER } from '@angular/core'; +import { action } from '@storybook/addon-actions'; +import Button from '../../button.component'; + +const meta: Meta