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] Addon Controls resets the component's local variables initialised during Angular life cycle events #11613
Comments
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks! |
We encounter the exact same issue in our project. It is a show blocker for us as a lot of our components initialize internal variables in ngInit. |
So the issue here is that the component re-initializes when an arg is changed? It's interesting that seems to be the opposite behaviour to what we were seeing in #11614. I don't really know enough about angular's internals or how |
For me the problem comes from the doc addon which returns the local properties of the component with their initial values. you can see it with a log here in your example : const LabelStory = (args: Label) => {
console.log(args);
return {
component: Label,
props: args,
}; if you are wondering why there is a change between then at the next property change (using the controls, for example) the component is not reloaded. But only these properties are updated. The doc addon at compile time seems to complete the "propertiesClass": [
{
"name": "localText",
"defaultValue": "'Original localText'",
"type": "string",
"optional": false,
"description": "",
"line": 36
}
], then it always sends it when you can't modify it and see it in the UI (whereas for the Input it is possible) I don't know what the right behaviour is 🤷♂️ but I'm not sure I have all the history and all the possible behaviors. aannd, I still haven't found out where this feature is in the code. 🙈 @shilman @Marklb @gaetanmaisse what do you think about it ? |
I don't know Angular well enough to comment on this. If properties are always internal to the component and not settable from outside, we could easily remove their controls from the table. LMK what you guys think! |
@shilman They aren't always internal, but I don't think their I think the optimal fix would be to not set the property unless the value actually changed, like if controls were the only thing that could change them then it would be nice to only hear back from the addon (with a way to know which arg changed, instead of every arg) if the control actually received user interaction(or whatever interaction causes it to change). Without a potentially large refactor to how args work, I think the best solution may be to diff the props against their previously emitted value, before setting them on the component, because that would probably be accurate most of the time.
It may cause issues that I am not thinking of, but I don't think default values discovered automatically by docs should even be set in args for inputs(it should still be available in If we change to the way #13215 renders, then this issue's specific case wouldn't be an issue, but the core problem would not be fixed. Though, it would be a breaking change for users that don't use I will try to give a brief explanation for non Angular users, in case it helps from Storybook's code. *The following example is a valid situation that would break by forcing props to only set input/output properties. There are plenty of other ways to do the following that are clearly better code, but I am just pointing it out as a valid scenario that can be done with Storybook's current Angular framework. // content.component.ts
// This is a poorly written component, but assume it is a component that
// renders a scrollbar and status message based on how far the page has
// been scrolled.
@Component({
selector: 'app-content',
template: `
{{ content }}
{{ status }}
<progress-bar [percentage]="percent"></progress-bar>
`
})
class ContentComponent {
@Input() content: string
status: string
set percent(p: number) {
this.status = `You are ${p}% done.`
this._percent = p
}
get percent() { return this._percent }
_percent: number = 0
sub = Subscription.EMPTY
ngOnInit() {
this.sub = fromEvent(document, 'scroll')
.pipe(toPercentageOfContent)
.subscribe(p => this.percent = p)
}
ngOnDestroy() { this.sub.unsubscribe() }
}
// content.component.stories.ts
// I don't want `status` or `percent` to be inputs, because the are managed
// internally.
//
// Assuming this is legacy code that the user has been told to document with
// Storybook, but changes to the component's code are not allowed. I would
// just let props set percent on the component's class instance.
export const Example1 = (args) => ({
props: args,
component: ContentComponent
})
Example1.args = {
percent: 30
}
// If we were to restrict props from setting properties that are not an
// input/output then one possible workaround could be to fake a scroll event
// after the props are set.
// The following may not work exactly like that, but I can't think of something
// compact enough for a small example at the moment.
@Injectable()
class FakeScrollPercentDirective {
constructor(@Inject(STORY) private data: Observable<StoryFnAngularReturnType>) { }
ngOnInit() { this.data.subscribe(d => triggerFakeScrollEvent(d.props?.percent)) }
ngOnDestroy() { /* clean up data subscription */ }
}
export const Example2 = (args) => ({
moduleMetadata: { providers: [ FakeScrollPercentDirective ] },
props: args,
component: ContentComponent
})
Example1.args = {
percent: 30
} |
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks! |
@Stale just because an issue has no comments doesnt mean it has been fixed or can be closed |
Issue is still present with following dependencies: |
yes same in 6.2 maybe this will help you : if the problem is the same 🤔 |
Yes, exactly the same - all properties and even methods are |
Yee-haw!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.3.0-alpha.18 containing PR #14769 that references this issue. Upgrade today to the @next NPM tag to try it out!
Closing this issue. Please re-open if you think there's still more to do. |
I believe the problem is here: @ThibaudAV Should this be changed to set the props on After making this change to my local, my component is appears to properly initialize the Inputs on first load. |
@nzacca That would fix it in certain situations, but break it on others. After fixing most of the tests I have added in my local branch, I noticed the problem is more complicated than I expected. I am currently trying to fix the problems with setting props by adding tests for every scenario I can think of that I may do in a story. After fixing a few more tests that are essential to be working, in my opinion, I will create a PR to see if my changes should be added. Some can't be simply fixed and I am proposing some potentially breaking or unwanted changes for them, but majority of stories should not be affected by my changes. I think my changes would mostly fix this issue, though. |
@Marklb Thank you for that. Could you provide an example where it breaks? I'd be interested in investigating that scenario in my codebase. |
@nzacca The most basic scenario I can think of, at the moment, would be broken is the following: import { Story, Meta } from '@storybook/angular';
import { Component, Input, SimpleChanges, OnChanges } from '@angular/core';
@Component({ selector: 'ex-comp', template: `{{ bar }}` })
class FooComponent implements OnChanges {
@Input() bar = 'initial';
ngOnChanges(changes: SimpleChanges): void {
console.log('changes', changes);
}
}
export default {
title: 'ExampleA',
component: FooComponent,
} as Meta;
export const Basic: Story = (args) => ({
props: args,
});
Basic.args = {
bar: 'Other',
}; In that snippet the Story "Basic" currently would set "bar" to "Other" and ngOnChanges would be called with I can probably come up with more if I go through all my tests. If I get a chance tonight, I will at least have a branch with my tests available. |
@Marklb This is a behavior that happens when you use compodoc? but if you don't have it it works as expected? or not? |
arf we just encountered the problem on one of our project. it seems to be a problem of the |
I had actually made a mistake when I was quickly trying the suggestion, so my description of what would happen was wrong. The example still causes a problem, which is actually more like what I was expecting, so I was able to go into more detail.
Providing a Providing a template can help sometimes, but it also has different problems. The big ones, for my components, are that it updates properties on the component way to eagerly and if I add the binding in the template myself then the input gets set twice. I used to consider providing a template as a way to fallback to a basic story fully controlled by me, when Storybook happens to not support what I am trying to do or has a bug with props. Now I can't because Storybook is updating properties directly on the component instance. Also, if I want to use a pipe on one of my inputs then I need to define the input binding myself in the template, but Storybook would be updating my input without the pipe and I would be updating it with the pipe causing the input to get updated twice with different values each props change. So, one of my proposed changes is to add a Parameter to disable Storybook touching the component instance when a template is provided. The tests I am adding are being done the same for stories with or without a template, to try and avoid the inconsistency between them. |
Ta-da!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.5.0-alpha.11 containing PR #17156 that references this issue. Upgrade today to the
Closing this issue. Please re-open if you think there's still more to do. |
Good golly!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.4.16 containing PR #17156 that references this issue. Upgrade today to the
|
This is still happening in 6.5.5 even after upgrading to prerelase... If I have private properties within a component with @input setter/getter functions for another property, changing the private property using the controls in storybook directly works. If I change the public @input property, the component initially changes its value and then resets to its initial state.
This results in the The only way I've gotten around this (seems hacky) is by overriding the private property in the template
|
Still happening on 6.5.7 too. Workaround from @famu1hundred is working, but rewriting all things we do in ngOnInit() from our component seems a bit counter productive Component:
Storybook:
Without the override of labelText, nothing is displayed inside the component |
Same issue on 6.5.10 |
Describe the bug
When a field in the Controls is updated it resets the value of the components instance variable which is altered in the Angular lifecycle events
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The changes in the @input variables should not impact or alter the component's instance variable values.
Screenshots
Code snippets
1-Label.stories.ts.txt
label.component.ts.txt
System:
System:
OS: Windows 10 10.0.18362
CPU: (4) x64 Intel(R) Core(TM) i5 CPU M 460 @ 2.53GHz
Binaries:
Node: 12.16.1 - C:\Program Files\nodejs\node.EXE
npm: 6.13.4 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: 44.18362.449.0
npmPackages:
@storybook/addon-actions: ^6.0.0-rc.11 => 6.0.0-rc.11
@storybook/addon-controls: ^6.0.0-rc.11 => 6.0.0-rc.11
@storybook/addon-docs: ^6.0.0-rc.11 => 6.0.0-rc.11
@storybook/addon-links: ^6.0.0-rc.11 => 6.0.0-rc.11
@storybook/angular: ^6.0.0-rc.11 => 6.0.0-rc.11
The text was updated successfully, but these errors were encountered: