-
Notifications
You must be signed in to change notification settings - Fork 110
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
Add localState ngrxDirective option #166
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #166 +/- ##
======================================
Coverage 100% 100%
======================================
Files 125 127 +2
Lines 2349 2385 +36
Branches 425 429 +4
======================================
+ Hits 2349 2385 +36
Continue to review full report at Codecov.
|
444ac71
to
4700889
Compare
Thanks a lot for this thorough PR. Overall it looks great. Here's a couple of comments:
I like this design because it keeps the whole local form state concept in the directive without bleeding into the rest of ngrx-forms. At the same time I dislike its verbosity, because you need to pass the parameter to every single form control (which also makes it easy to accidentally forget adding it on a control). If there was an
Yeah, its fine to always emit locally.
I feel that the example is maybe a bit too advanced to make the feature easy to understand. There should be at least one basic example that showcases purely how to handle only local form state without any interaction with effects etc, i.e. showing that this feature allows using ngrx-forms without ngrx. There could then be a second more advanced example for how to connect a local form with actions from the outside.
Yeah, a good example is always important. Even though the text documentation may not contain any extra details, it is still useful, since there are different people out there that learn through different things. Some may prefer looking at an example, others may prefer reading through documentation. For this feature we could probably just add a new markdown file Overall, great work. Let's see if we can nail down the API design. While it's great that the PR is already so complete, in the future I would recommend to not start writing any documentation/examples before the design is finalized to save yourself some unnecessary refactoring/rewriting. |
src/control/e2e.spec/select.spec.ts
Outdated
@@ -172,3 +172,70 @@ describe(NumberSelectComponent.name, () => { | |||
element.dispatchEvent(new Event('change')); | |||
}); | |||
}); | |||
|
|||
@Component({ |
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.
the reason for e2e.spec/select.spec.ts
is that there were some tricky interaction issues with angular itself. This is unrelated to the local form state. It's still a good idea to have an end to end test though, but I'd put it in a separate local-state.spec.ts
file and maybe use just an input
instead of the more complex select
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.
Was not able to trigger a simple change event manually on an input element and have ngrx forms register it properly. Extracted the example into a separate spec file though.
Thanks for the review, i will incorporate all the remarks once we settle on the API, as you said. One thing I am not certain about: For now we have the
In my opinion another set of directives ( If you agree, I would proceed with this approach. However, in this case the question remains how to go about the implementation. As far as I can tell, there are two options:
|
Hmm, I am honestly divided. In the grand scheme of things this is probably just a minor detail though, so any option is fine. We could also just call it
Yeah, let's go with this design.
I was thinking about this as well. I would go for the inheritance approach. You can simply make the |
Updated comment:Scratch everything I wrote before, I can make the new directive's selector be Old comment for reference:I encountered some unexpected roadblocks while trying to implement the directive
@Directive({
selector: 'input[type=radio][ngrxFormControlState]',
// ...
})
export class NgrxRadioViewAdapter implements FormViewAdapter, OnInit, AfterViewInit {
@Input() set ngrxFormControlState(value: FormControlState<any>) { /*...*/ }
}
Here the implementation for reference: @Directive({
// tslint:disable-next-line:directive-selector
selector: '[ngrxLocalFormControlState]',
})
export class NgrxLocalFormControlDirective<TStateValue extends FormControlValueTypes, TViewValue = TStateValue>
extends NgrxFormControlDirective<TStateValue, TViewValue> {
@Input() set ngrxLocalFormControlState(state: FormControlState<TStateValue>) {
this.ngrxFormControlState = state;
}
@Output() ngrxFormsAction = new EventEmitter<Actions<TStateValue>>();
constructor(
el: ElementRef,
@Optional() @Inject(DOCUMENT) dom: Document | null,
@Self() @Optional() @Inject(NGRX_FORM_VIEW_ADAPTER) viewAdapters: FormViewAdapter[],
@Self() @Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
) {
super(el, dom, null as any as ActionsSubject, viewAdapters, valueAccessors);
}
protected dispatchAction(action: Actions<TStateValue>) {
this.ngrxFormsAction.emit(action);
};
} If you know of a good way of introducing another directive, while still being backward-compatible let me know. A cool solution would be to apply the new directive on all elements that have the output binding too, something like Otherwise, maybe we should go ahead with the original approach. Looking forward to hear your thoughts. |
Changes from previous version:
Codecov is failing because I did not test the branches where the |
Yeah, that's a pretty cool approach. For reference in the future, I believe you can also have multiple selectors for a component, e.g |
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.
Great work, this is looking really good. As you can see my comments are mostly minor things (for many of the formatting comments I should really tweak the linting rules).
Let's also bring test coverage to 100% and then this is good to merge.
example-app/src/app/local-state-advanced/local-state-advanced.component.html
Outdated
Show resolved
Hide resolved
example-app/src/app/local-state-advanced/local-state-advanced.component.html
Outdated
Show resolved
Hide resolved
example-app/src/app/local-state-advanced/local-state-advanced.component.ts
Show resolved
Hide resolved
@Self() @Optional() @Inject(NGRX_FORM_VIEW_ADAPTER) viewAdapters: FormViewAdapter[], | ||
@Self() @Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[], | ||
) { | ||
super(el, dom, null as any as ActionsSubject, viewAdapters, valueAccessors); |
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.
you can remove the casts when the base class constructor accepts null
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.
See #166 (comment)
@@ -11,13 +11,19 @@ interface CustomEvent extends Event { } | |||
|
|||
@Directive({ | |||
// tslint:disable-next-line:directive-selector | |||
selector: 'form[ngrxFormState]', | |||
selector: 'form:not([ngrxFormsAction])[ngrxFormState]', |
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'd prefer form[ngrxFormState]:not([ngrxFormsAction])
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.
Tried that initially, but the selector won't work this way.
src/group/directive.ts
Outdated
}) | ||
export class NgrxFormDirective<TValue extends { [key: string]: any }> implements OnInit { | ||
// tslint:disable-next-line:no-input-rename | ||
@Input('ngrxFormState') state: FormGroupState<TValue>; | ||
|
||
constructor(private actionsSubject: ActionsSubject) { } | ||
constructor(@Optional() private actionsSubject: ActionsSubject) { } |
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.
type as ActionsSubject | null
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.
See #166 (comment)
@Output() ngrxFormsAction = new EventEmitter<Actions<TValue>>(); | ||
|
||
constructor() { | ||
super(null as any as ActionsSubject); |
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.
can remove cast when base constructor accepts null
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.
See #166 (comment)
c9ad51e
to
454e20a
Compare
* Add NgrxLocalFormControlDirective and NgrxLocalFormDirective that emit actions on output EventEmitter instead of global ActionsSubject * Add unit and e2e tests * Add introductory and advanced usage example * Add documentation section about local state Closes MrWolfZ#165
454e20a
to
727dcc6
Compare
Alright, applied all proposed changes, or left responses where it's not possible to do so. |
Yup, we are good. Thank you very much for your work. |
And it's published as version 6.1.0. |
Thanks for all your input, I am glad we managed to find a nice API and provide it with examples & documentation! I see you have done some linting afterwards, thanks! |
@MrWolfZ
As discussed in issue #165 , this PR adds:
ngrxSuppressGlobalActionEmit
input boolean to ngrx's directives, which controls whether to dispatch form actions to the globalActionsSubject
or not,false
by default of course to not change current semantics. I thought this was better wording thancreateLocalFormState
, but I can adapt this of course.ngrxFormAction
output event emitter that always emits any form action. It does not hurt if the action is always emitted, regardless ofngrxSuppressGlobalActionEmit
, right?I hope everything checks out, otherwise let me know.
(e: Improved upon the example and some wording, pushed again)