New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Angular: Overhaul preview renderer #13215
Conversation
@ThibaudAV can you explain the goals of the rewrite? |
yes @shilman I wanted to check with CI . but I plan to do it azap |
0f35630
to
bcc6277
Compare
This looks like what I have been considering, so I will be going through it later(hopefully this afternoon) and seeing how it works. I have only looked at the code so far, but I will list questions/suggestions I have so far.
|
bb4d593
to
6f0bd1e
Compare
I have corrected some errors I made when creating the test 😅. now is ok in netlify
I speak only in component case. In template case there is no problem. But I let you choose :) I probably don't have all the context
haha I thought about it, but I think it would be a loss in performance. Load the component twice to be able to filter on properties :/
Completely. As we get as close as possible to a real use of a component (I hope) this should solve this problem in part.
With this solution it works without any problems. If my quick test is correct
Not sure to understand exactly 🤔
What's the problem? we can use another observable one. it's just that this one made more sense to me for this situation.
If I understand correctly. Yes, I don't think this solution closes doors for these features. On the contrary, I think it could make these improvements easier. What do you think about it ? I would be interested in trying to do it but in another PR, no ? I can see something like that 🤔 export default {
component: YourComponent,
decorators: [
// directly return the template according to whether it is a template | component (or both)
templateDecorator((story) => `<div style="margin: 3em">${story}</div>`)
],
} as Meta; |
9f33c20
to
1c04762
Compare
I could be over thinking this and may need additional feedback from other to see what would be expected. It may be configurable globally using the
It's been a while since I looked at what
I forgot to reference the PR: #12382 |
I propose you an alternative solution in PR #12382 |
For input/output recovery, I can propose you this solution which doesn't require the angular engine and is based on Typescript. I don't think the Input/Output convention will change for a long time. what do you think about it ? const getComponentIOPropertiesName = (
compType: any
): { inputs: string[]; outputs: string[] } | undefined => {
const decoratorKey = '__prop__metadata__';
const propsDecorators: Record<string, any[]> =
Reflect &&
Reflect.getOwnPropertyDescriptor &&
Reflect.getOwnPropertyDescriptor(compType, decoratorKey)
? Reflect.getOwnPropertyDescriptor(compType, decoratorKey).value
: compType[decoratorKey];
if (!propsDecorators) {
return undefined;
}
return Object.entries(propsDecorators).reduce(
(previousValue, [key, value]) => {
if (value[0].ngMetadataName === 'Input') {
const propertyName = value[0].bindingPropertyName ?? key;
return {
...previousValue,
inputs: [...previousValue.inputs, propertyName],
};
}
if (value[0].ngMetadataName === 'Output') {
const propertyName = value[0].bindingPropertyName ?? key;
return {
...previousValue,
outputs: [...previousValue.outputs, propertyName],
};
}
return previousValue;
},
{
inputs: [],
outputs: [],
}
);
};
console.log(getComponentIOPropertiesName(storyObj.component)); if it suits you. We will have to define what to do with it because I'm not sure I understand all your concerns 🙃 |
When I mentioned filtering, I was just meaning to only select the props that are an input or output, to avoid the That @Component({ selector: '' })
class BaseExample2 {
@Input() baseInp2: string
}
@Component({ selector: '' })
class BaseExample extends BaseExample2 {
@Input() baseInp1: string
}
@Component({
selector: 'story-example',
template: ``,
inputs: [ 'inp2' ]
})
class StoryExample extends BaseExample {
@Input() inp1: string
@Input('reInp3') inp3: string
} I do see them in other props on the object though, so it should be able to work with some adjustments, since that is probably what they are doing to initialize the factory in JIT also. Looks like the inputs/outputs from |
app/angular/src/client/preview/angular/ComponentClassFromStoryComponent.ts
Outdated
Show resolved
Hide resolved
@Marklb Do you think this is the proper approach to use? |
Yes. At first I was leaning toward just using the factory, but now I am liking your approach more. One reason is because it could potentially be used by other features that aren't running in Angular, like #12438 |
1c04762
to
7370887
Compare
@Marklb I've added 2 commits on the last discussed subjects I have also added more "complete" tests. In order to be able to be more safe about the link between the component and the props. Tell me what you think? Do you think this pr is a good thing ? and that we should finish it and test it well, to merge it in 🤔 I wonder if it wouldn't be better to add an Don't hesitate if you want to add code or modify the existing one. |
app/angular/src/client/preview/angular/ComponentClassFromStoryComponent.ts
Outdated
Show resolved
Hide resolved
app/angular/src/client/preview/angular/ComponentClassFromStoryComponent.ts
Outdated
Show resolved
Hide resolved
app/angular/src/client/preview/angular/ComponentClassFromStoryComponent.ts
Outdated
Show resolved
Hide resolved
|
||
import { getComponentInputsOutputs } from './NgComponentAnalyzer'; | ||
|
||
describe('getComponentInputsOutputs', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome test suite, it will make us much more confident about everything related to component properties detection 💪🏻
e81985c
to
e64423b
Compare
@gaetanmaisse Anything else I can think of at the moment would be new features made possible/easier from this. So, they would be separate PR's. |
@Marklb @gaetanmaisse sounds like this PR is good to go then? @ThibaudAV Can you get the tests to pass? |
@@ -32,4 +32,6 @@ export interface StoryFnAngularReturnType { | |||
moduleMetadata?: NgModuleMetadata; | |||
template?: string; | |||
styles?: string[]; | |||
/** Uses legacy angular rendering engine that use dynamic component */ | |||
useLegacyRendering?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about making this a framework option. User would write the following in .storybook/main.js
:
module.exports = {
angularOptions: {
legacyRendering: true,
}
}
Another option would be to use story parameters if we want to control this on a per story basis. To configure globally, .storybook/preview.js
:
export const parameters = {
angularLegacyRendering: true,
}
Both of these would be more "storybook-like" than adding a new field to the story function return type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of the framework option, because that projects rendering would be consistent, instead of potentially introducing bugs from the previous renderer not getting cleaned up.
As long as the renderer's clean themselves up when switching, then users with a large project may have a reason to migrate a few stories at a time. So, a parameter may be the better choice to avoid users not being able to start migrating, because of a bug or situation we didn't consider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small suggestion about configuration via framework options or parameters. See comment line @ThibaudAV @gaetanmaisse
7add6e7
to
9a24909
Compare
@shilman I added it as a parameters : I also corrected tests |
9a24909
to
42a43a3
Compare
A `useLegacyRendering` flag allows to use old rendering engine
Configure app/angular package as jest projects Allow to use jest-preset-angular for app/angular tests
The test allows to verify the function by using angular engine
…rties - allow story with component to override local properties - handles Input with `bindingPropertyName`
42a43a3
to
0e9c7a7
Compare
Impressive PR, great job! 💪 |
cc @mandarini |
Issue: #11614
Work in progress, TODO list:
Custom/Feature Module as Context
storyI did not browse all issues. but it fixes, for instance, the color background which does not change on this issue:
#11614
What I did
Two main goals:
1. make operation of angular rendering more clear
(currently, all the functions are tangled in helpers file or ) and does not make the code very clear to understand how it works
2. use a new way to add story component|template in the Angular application
Currently the component | template of the story is added dynamically with
componentFactoryResolver
. This way does not add the component in the same way as if it was used in a template (the component will not have the same life cycle) which forces Storybook to complete the missing life cycle. (see app.componentngOnChanges
,writeValue
,registerOnChange
, missingregisterOnTouched
, ext..)This new approach no longer uses dynamic components.
So in this way Storybook should better simulate the real use of the story in the angular application.
Tests
I've checked all the story examples for angular (don't hesitate to do the same again, I think)
I found only one case that is problematic and that creates breaking changes
For the story
Component with default providers
andComponent with overridden provider
inside Custom/Feature Module as Context theChipComponent
cannot be added because it is not exported by ChipsModule and it is already declared by ChipsModule. So the virtual Storybook module cannot use it. :/Before that, it was not a problem to dynamically add a non-exported component. Now, it's not possible anymore.
It is always possible to add a component already declared in a module because Storybook detects it and will not re-declare it.
Notes
Another point is on NO_ERRORS_SCHEMA, it would be really nice if addon doc + controls only add the Input and Output properties of a component, and so not include private properties anymore.Removing this will allow Storybook to return an error if component inputs & outputs no longer match story inputs & outputs. Currently, there are no errors (if I am not wrong) but I think it would be nice if Storybook returns an error in this case. This will keep the story and associated components consistent.With my current work, it would be very handy 🙈
I think the problem with this issue #11613 is the same
The doc+controls addon returns the value of the "private" property value to in story
props
.On my 2nd commit, I don't know how to do otherwise without editing jest configuration to run a test with
jest-preset-angular
🤔How to test
👋 Don't hesitate to give me feedback, if you think it's not interesting. If the code is unclear or if there are too many comments or missing comments, ty
p.s : sorry for my bad english 🙈