Adding extra metadata to module/components

This commit is contained in:
Carlos Vega 2017-12-11 19:21:49 -06:00
parent 9504ef8415
commit 82c1642923
9 changed files with 189 additions and 53 deletions

View File

@ -3,7 +3,7 @@
import { Component, SimpleChange, ChangeDetectorRef } from '@angular/core';
import { getParameters, getAnnotations, getPropMetadata } from './utils';
const getComponentMetadata = ({ component, props = {}, pipes = [] }) => {
const getComponentMetadata = ({ component, props = {}, moduleMetadata }) => {
if (!component || typeof component !== 'function') throw new Error('No valid component provided');
const componentMeta = getAnnotations(component)[0] || {};
@ -13,9 +13,9 @@ const getComponentMetadata = ({ component, props = {}, pipes = [] }) => {
return {
component,
props,
pipes,
componentMeta,
propsMeta,
moduleMetadata,
params: paramsMetadata,
};
};
@ -35,7 +35,7 @@ const getAnnotatedComponent = ({ componentMeta, component, params, knobStore, ch
KnobWrapperComponent.prototype.constructor = KnobWrapperComponent;
KnobWrapperComponent.prototype.ngOnInit = function onInit() {
if (component.prototype.ngOnInit) {
component.prototype.ngOnInit();
component.prototype.ngOnInit.call(this);
}
channel.on('addon:knobs:knobChange', this.knobChanged);
@ -46,7 +46,7 @@ const getAnnotatedComponent = ({ componentMeta, component, params, knobStore, ch
KnobWrapperComponent.prototype.ngOnDestroy = function onDestroy() {
if (component.prototype.ngOnDestroy) {
component.prototype.ngOnDestroy();
component.prototype.ngOnDestroy.call(this);
}
channel.removeListener('addon:knobs:knobChange', this.knobChanged);
@ -56,7 +56,7 @@ const getAnnotatedComponent = ({ componentMeta, component, params, knobStore, ch
KnobWrapperComponent.prototype.ngOnChanges = function onChanges(changes) {
if (component.prototype.ngOnChanges) {
component.prototype.ngOnChanges(changes);
component.prototype.ngOnChanges.call(this, changes);
}
};
@ -99,9 +99,14 @@ const resetKnobs = (knobStore, channel) => {
export function prepareComponent({ getStory, context, channel, knobStore }) {
resetKnobs(knobStore, channel);
const { component, componentMeta, props, pipes, propsMeta, params } = getComponentMetadata(
getStory(context)
);
const {
component,
componentMeta,
props,
propsMeta,
params,
moduleMetadata,
} = getComponentMetadata(getStory(context));
if (!componentMeta) throw new Error('No component metadata available');
@ -116,7 +121,7 @@ export function prepareComponent({ getStory, context, channel, knobStore }) {
return {
component: AnnotatedComponent,
props,
pipes,
propsMeta,
moduleMetadata,
};
}

View File

@ -1,10 +1,4 @@
import { InjectionToken, PipeTransform } from "@angular/core";
import { InjectionToken } from "@angular/core";
import { Data } from "./types";
export const STORY = new InjectionToken<Data>("story");
export type Data = {
component: any;
props: object;
propsMeta: object;
pipes: PipeTransform[];
}

View File

@ -13,7 +13,8 @@ import {
OnDestroy,
EventEmitter
} from "@angular/core";
import { STORY, Data } from "../app.token";
import { STORY } from "../app.token";
import { Data } from "../types";
@Component({
selector: "my-app",

View File

@ -1,5 +1,6 @@
import { Component, Inject } from "@angular/core";
import { STORY, Data } from "../app.token";
import { STORY } from "../app.token";
import { Data } from "../types";
@Component({
selector: "my-app",

View File

@ -2,7 +2,9 @@ import {
enableProdMode,
NgModule,
Component,
CUSTOM_ELEMENTS_SCHEMA
CUSTOM_ELEMENTS_SCHEMA,
NgModuleRef,
ApplicationRef
} from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
@ -12,6 +14,7 @@ import { ErrorComponent } from "./components/error.component";
import { NoPreviewComponent } from "./components/no-preview.component";
import { STORY } from "./app.token";
import { getAnnotations, getParameters, getPropMetadata } from './utils';
import { NgModuleMetadata, NgStory } from "./types";
let platform = null;
let promises = [];
@ -34,7 +37,9 @@ const debounce = (func, wait = 100, immediate = false) => {
};
};
const getComponentMetadata = ({ component, props = {}, propsMeta = {}, pipes = [] }) => {
const getComponentMetadata = (
{ component, props = {}, propsMeta = {}, moduleMetadata }: NgStory
) => {
if (!component || typeof component !== "function")
throw new Error("No valid component provided");
@ -46,13 +51,25 @@ const getComponentMetadata = ({ component, props = {}, propsMeta = {}, pipes = [
propsMetadata[key] = propsMeta[key];
});
const {
imports = [],
schemas = [],
declarations = [],
providers = []
} = moduleMetadata || {};
return {
component,
props,
pipes,
componentMeta: componentMetadata,
propsMeta: propsMetadata,
params: paramsMetadata
params: paramsMetadata,
moduleMeta: {
imports,
schemas,
declarations,
providers
}
};
};
@ -69,13 +86,18 @@ const getAnnotatedComponent = (meta, component, propsMeta, params) => {
return NewComponent;
};
const getModule = (declarations, entryComponents, bootstrap, data) => {
const getModule = (declarations, entryComponents, bootstrap, data, moduleMetadata: NgModuleMetadata = {
imports: [],
schemas: [],
declarations: [],
providers: []
}) => {
const moduleMeta = new NgModule({
declarations: [...declarations],
imports: [BrowserModule],
providers: [{ provide: STORY, useValue: Object.assign({}, data) }],
declarations: [...declarations, ...moduleMetadata.declarations],
imports: [BrowserModule, ...moduleMetadata.imports],
providers: [{ provide: STORY, useValue: Object.assign({}, data) }, ...moduleMetadata.providers],
entryComponents: [...entryComponents],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
schemas: [...moduleMetadata.schemas],
bootstrap: [...bootstrap]
});
@ -91,9 +113,9 @@ const initModule = (currentStory, context, reRender) => {
component,
componentMeta,
props,
pipes,
propsMeta,
params
params,
moduleMeta
} = getComponentMetadata(currentStory(context));
if (!componentMeta) throw new Error("No component metadata available");
@ -102,7 +124,7 @@ const initModule = (currentStory, context, reRender) => {
componentMeta,
component,
propsMeta,
params
[...params, ...moduleMeta.providers.map(provider => [provider])]
);
const story = {
@ -110,12 +132,15 @@ const initModule = (currentStory, context, reRender) => {
props,
propsMeta
};
const Module = getModule(
[AppComponent, AnnotatedComponent, ...pipes],
[AppComponent, AnnotatedComponent],
[AnnotatedComponent],
[AppComponent],
story
story,
moduleMeta
);
return Module;
};

View File

@ -0,0 +1,24 @@
export type NgModuleMetadata = {
declarations: Array<any>,
imports: Array<any>,
schemas: Array<any>,
providers: Array<any>
}
export type NgStory = {
component: any;
props: object;
propsMeta: object;
moduleMetadata: NgModuleMetadata
}
export type Data = {
component: any;
props: ErrorProps | object;
propsMeta: object;
}
type ErrorProps = {
message: string
stack: string
}

View File

@ -20,28 +20,11 @@ import { Welcome, Button } from '@storybook/angular/demo';
import { SimpleKnobsComponent } from './knobs.component';
import { AllKnobsComponent } from './all-knobs.component';
import { AppComponent } from '../app/app.component';
import { DummyService } from './moduleMetadata/dummy.service';
import { ServiceComponent } from './moduleMetadata/service.component'
import { NameComponent } from './name.component';
import { CustomPipePipe } from './custom.pipe';
storiesOf('Custom Pipe', module)
.add('Default', () => ({
component: NameComponent,
props: {
field: 'foobar',
},
pipes: [ CustomPipePipe ],
}));
storiesOf('Custom Pipe/With Knobs', module)
.addDecorator(withKnobs)
.add('NameComponent', () => ({
component: NameComponent,
props: {
field: text('field', 'foobar'),
},
pipes: [ CustomPipePipe ],
}));
storiesOf('Welcome', module)
.add('to Storybook', () => ({
component: Welcome,
@ -135,7 +118,6 @@ storiesOf('Addon Knobs', module)
.add('Simple', () => {
const name = text('Name', 'John Doe');
const age = number('Age', 44);
const content = `I am ${name} and I'm ${age} years old.`;
return {
component: SimpleKnobsComponent,
@ -182,3 +164,63 @@ storiesOf('Addon Knobs', module)
}
};
});
storiesOf('Custom ngModule metadata', module)
.add('simple', () => ({
component: ServiceComponent,
props: {
name: 'Static name'
},
moduleMetadata: {
imports: [],
schemas: [],
declarations: [],
providers: [DummyService]
}
}))
.addDecorator(withKnobs)
.add('with knobs', () => {
const name = text('Name', 'Dynamic knob');
return {
component: ServiceComponent,
props: {
name
},
moduleMetadata: {
imports: [],
schemas: [],
declarations: [],
providers: [DummyService]
}
};
});
storiesOf('Custom Pipe', module)
.add('Default', () => ({
component: NameComponent,
props: {
field: 'foobar',
},
moduleMetadata: {
imports: [],
schemas: [],
declarations: [CustomPipePipe],
providers: []
}
}));
storiesOf('Custom Pipe/With Knobs', module)
.addDecorator(withKnobs)
.add('NameComponent', () => ({
component: NameComponent,
props: {
field: text('field', 'foobar'),
},
moduleMetadata: {
imports: [],
schemas: [],
declarations: [CustomPipePipe],
providers: []
}
}));

View File

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
@Injectable()
export class DummyService {
constructor() { }
getItems() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
"Joe",
"Jane"
])
}, 2000)
})
}
}

View File

@ -0,0 +1,27 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { DummyService } from './dummy.service';
@Component({
selector: 'simple-knobs-component',
template: `
<p>{{ name }}:</p>
<ul>
<li *ngFor="let item of items">
{{ item }}
</li>
</ul>
`
})
export class ServiceComponent {
items;
@Input() name;
constructor(private dummy: DummyService) {
console.log(DummyService);
console.log(this.dummy);
}
async ngOnInit() {
this.items = await this.dummy.getItems();
}
}