From 50c9e5eb81ba2a43211197bfd6539d04e450c9bc Mon Sep 17 00:00:00 2001 From: tsander Date: Wed, 18 Jul 2018 19:54:57 -0700 Subject: [PATCH] Create an AbstractControlGroup and AbstractForm which will allow different infrastructure to create different implementation of ControlGroups and Forms with different backing values. Example usage: cl/205017920 Global Presubmit: https://test.corp.google.com/OCL:204254973:BASE:205149190:1531953387644:95fc22b0 PiperOrigin-RevId: 205178408 --- angular_forms/CHANGELOG.md | 6 + angular_forms/lib/angular_forms.dart | 8 +- angular_forms/lib/src/directives.dart | 2 +- .../lib/src/directives/abstract_form.dart | 20 ++-- .../lib/src/directives/control_container.dart | 7 +- .../lib/src/directives/form_interface.dart | 7 +- .../lib/src/directives/ng_control_group.dart | 3 +- angular_forms/lib/src/directives/ng_form.dart | 32 +++-- .../lib/src/directives/ng_form_model.dart | 2 +- angular_forms/lib/src/directives/shared.dart | 4 +- angular_forms/lib/src/model.dart | 109 ++++++++++-------- doc/developing/generated-code.md | 5 - doc/developing/issue-labels.md | 6 - doc/developing/style-guide.md | 10 -- doc/effective/change-detection.md | 6 - doc/effective/di.md | 6 - doc/effective/styling.md | 6 - doc/faq/change-detection.md | 6 - doc/faq/component-loading.md | 5 - doc/faq/di.md | 6 - doc/router/migration.md | 6 - doc/testing/e2e.md | 5 - 22 files changed, 120 insertions(+), 147 deletions(-) diff --git a/angular_forms/CHANGELOG.md b/angular_forms/CHANGELOG.md index c6592b4ab9..0c380be0b6 100644 --- a/angular_forms/CHANGELOG.md +++ b/angular_forms/CHANGELOG.md @@ -1,3 +1,9 @@ +### New Features + +* Add AbstractControlGroup and AbstractNgForm to allow infrastructure to + create their own form systems that can be backed by types such as a proto, + or have different control group logic. + ### Breaking Changes * Use value from AbstractControl for valueChanges event instead of internal diff --git a/angular_forms/lib/angular_forms.dart b/angular_forms/lib/angular_forms.dart index a187057455..d4f7f457bb 100644 --- a/angular_forms/lib/angular_forms.dart +++ b/angular_forms/lib/angular_forms.dart @@ -17,6 +17,7 @@ export 'src/directives.dart' setUpControlGroup, formDirectives, AbstractControlDirective, + AbstractNgForm, ChangeFunction, CheckboxControlValueAccessor, ControlContainer, @@ -48,7 +49,12 @@ export 'src/directives.dart' ValidatorFn; export 'src/form_builder.dart' show FormBuilder; export 'src/model.dart' - show AbstractControl, Control, ControlGroup, ControlArray; + show + AbstractControl, + Control, + AbstractControlGroup, + ControlGroup, + ControlArray; export 'src/validators.dart' show NG_VALIDATORS, Validators; /// Shorthand set of providers used for building Angular forms. diff --git a/angular_forms/lib/src/directives.dart b/angular_forms/lib/src/directives.dart index 8de73fabb0..be7f590c7c 100644 --- a/angular_forms/lib/src/directives.dart +++ b/angular_forms/lib/src/directives.dart @@ -35,7 +35,7 @@ export 'directives/ng_control_group.dart' show NgControlGroup; export 'directives/ng_control_name.dart' show NgControlName; // ignore: deprecated_member_use export 'directives/ng_control_status.dart' show NgControlStatus; -export 'directives/ng_form.dart' show NgForm; +export 'directives/ng_form.dart' show NgForm, AbstractNgForm; export 'directives/ng_form_control.dart' show NgFormControl; export 'directives/ng_form_model.dart' show NgFormModel; export 'directives/ng_model.dart' show NgModel; diff --git a/angular_forms/lib/src/directives/abstract_form.dart b/angular_forms/lib/src/directives/abstract_form.dart index d0e7ce15b2..e36ea9735b 100644 --- a/angular_forms/lib/src/directives/abstract_form.dart +++ b/angular_forms/lib/src/directives/abstract_form.dart @@ -10,23 +10,23 @@ import 'ng_control.dart' show NgControl; import 'ng_control_group.dart' show NgControlGroup; /// A base implementation of [Form]. -abstract class AbstractForm extends ControlContainer implements Form { - final _ngSubmit = new StreamController.broadcast(sync: true); - final _ngBeforeSubmit = - new StreamController.broadcast(sync: true); +abstract class AbstractForm + extends ControlContainer implements Form { + final _ngSubmit = new StreamController.broadcast(sync: true); + final _ngBeforeSubmit = new StreamController.broadcast(sync: true); - ControlGroup get form; + T get form; /// An event fired with the user has triggered a form submission. @Output() - Stream get ngSubmit => _ngSubmit.stream; + Stream get ngSubmit => _ngSubmit.stream; /// An event that is fired before the main form submission event. /// /// This is intended to be used to set form values or perform validation on /// submit instead of when a value changes. @Output() - Stream get ngBeforeSubmit => _ngBeforeSubmit.stream; + Stream get ngBeforeSubmit => _ngBeforeSubmit.stream; @HostListener('submit') void onSubmit(Event event) { @@ -45,7 +45,7 @@ abstract class AbstractForm extends ControlContainer implements Form { Form get formDirective => this; @override - ControlGroup get control => form; + T get control => form; @override List get path => []; @@ -54,8 +54,8 @@ abstract class AbstractForm extends ControlContainer implements Form { Control getControl(NgControl dir) => form?.findPath(dir.path) as Control; @override - ControlGroup getControlGroup(NgControlGroup dir) => - (form?.findPath(dir.path) as ControlGroup); + AbstractControlGroup getControlGroup(NgControlGroup dir) => + form?.findPath(dir.path) as AbstractControlGroup; @override void updateModel(NgControl dir, dynamic value) { diff --git a/angular_forms/lib/src/directives/control_container.dart b/angular_forms/lib/src/directives/control_container.dart index 392c7e1cb7..1e8a908396 100644 --- a/angular_forms/lib/src/directives/control_container.dart +++ b/angular_forms/lib/src/directives/control_container.dart @@ -1,12 +1,13 @@ -import '../model.dart' show ControlGroup; +import '../model.dart' show AbstractControlGroup; import 'abstract_control_directive.dart' show AbstractControlDirective; import 'form_interface.dart' show Form; /// A directive that contains multiple [NgControl]s contained in a -/// [ControlGroup]. +/// [AbstractControlGroup]. /// /// Only used by the forms package. -abstract class ControlContainer extends AbstractControlDirective { +abstract class ControlContainer + extends AbstractControlDirective { /// Get the form to which this container belongs. Form get formDirective; } diff --git a/angular_forms/lib/src/directives/form_interface.dart b/angular_forms/lib/src/directives/form_interface.dart index ed0f51ca52..50799e759f 100644 --- a/angular_forms/lib/src/directives/form_interface.dart +++ b/angular_forms/lib/src/directives/form_interface.dart @@ -1,4 +1,4 @@ -import '../model.dart' show Control, ControlGroup; +import '../model.dart' show Control, AbstractControlGroup; import 'ng_control.dart' show NgControl; import 'ng_control_group.dart' show NgControlGroup; @@ -21,8 +21,9 @@ abstract class Form { /// Remove a group of controls from this form. void removeControlGroup(NgControlGroup dir); - /// Look up the [ControlGroup] associated with a particular [NgControlGroup]. - ControlGroup getControlGroup(NgControlGroup dir); + /// Look up the [AbstractControlGroup] associated with a particular + /// [NgControlGroup]. + AbstractControlGroup getControlGroup(NgControlGroup dir); /// Update the model for a particular control with a new value. void updateModel(NgControl dir, dynamic value); diff --git a/angular_forms/lib/src/directives/ng_control_group.dart b/angular_forms/lib/src/directives/ng_control_group.dart index 204c7fb7ff..461dabfb2d 100644 --- a/angular_forms/lib/src/directives/ng_control_group.dart +++ b/angular_forms/lib/src/directives/ng_control_group.dart @@ -60,7 +60,8 @@ import 'validators.dart' show ValidatorFn; ], exportAs: 'ngForm', ) -class NgControlGroup extends ControlContainer implements OnInit, OnDestroy { +class NgControlGroup extends ControlContainer + implements OnInit, OnDestroy { final ValidatorFn validator; final ControlContainer _parent; diff --git a/angular_forms/lib/src/directives/ng_form.dart b/angular_forms/lib/src/directives/ng_form.dart index 9926ebabe5..9d46fc12f7 100644 --- a/angular_forms/lib/src/directives/ng_form.dart +++ b/angular_forms/lib/src/directives/ng_form.dart @@ -3,7 +3,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:angular/angular.dart'; -import '../model.dart' show AbstractControl, ControlGroup, Control; +import '../model.dart' + show AbstractControl, AbstractControlGroup, ControlGroup, Control; import '../validators.dart' show NG_VALIDATORS; import 'abstract_form.dart' show AbstractForm; import 'control_container.dart' show ControlContainer; @@ -75,13 +76,24 @@ import 'shared.dart' show setUpControl, setUpControlGroup, composeValidators; exportAs: 'ngForm', visibility: Visibility.all, ) -class NgForm extends AbstractForm { - ControlGroup form; - +class NgForm extends AbstractNgForm { NgForm(@Optional() @Self() @Inject(NG_VALIDATORS) List validators) { form = new ControlGroup({}, composeValidators(validators)); } + @override + ControlGroup createGroup(NgControlGroup _) => ControlGroup({}); +} + +/// Abstract class to easily create forms that are template driven. +/// +/// This allows an implementing form to easily specify how to create control +/// groups, and controls. Allowing for infrastructure to create form systems +/// that are backed by different types such as protos. +abstract class AbstractNgForm + extends AbstractForm { + T form; + @Input('ngDisabled') set disabled(bool isDisabled) { toggleDisabled(isDisabled); @@ -89,10 +101,14 @@ class NgForm extends AbstractForm { Map get controls => form.controls; + T createGroup(NgControlGroup dir); + // This is separate to allow clients to override the logic if they wish. + Control createControl(NgControl _) => Control(); + @override void addControl(NgControl dir) { var container = findContainer(dir.path); - var ctrl = new Control(); + var ctrl = createControl(dir); container.addControl(dir.name, ctrl); scheduleMicrotask(() { setUpControl(ctrl, dir); @@ -114,7 +130,7 @@ class NgForm extends AbstractForm { @override void addControlGroup(NgControlGroup dir) { var container = findContainer(dir.path); - var group = new ControlGroup({}); + var group = createGroup(dir); container.addControl(dir.name, group); scheduleMicrotask(() { setUpControlGroup(group, dir); @@ -141,8 +157,8 @@ class NgForm extends AbstractForm { } @protected - ControlGroup findContainer(List path) { + T findContainer(List path) { path.removeLast(); - return path.isEmpty ? form : (form.findPath(path) as ControlGroup); + return path.isEmpty ? form : (form.findPath(path) as T); } } diff --git a/angular_forms/lib/src/directives/ng_form_model.dart b/angular_forms/lib/src/directives/ng_form_model.dart index 4802ea6b8b..979e146eb1 100644 --- a/angular_forms/lib/src/directives/ng_form_model.dart +++ b/angular_forms/lib/src/directives/ng_form_model.dart @@ -87,7 +87,7 @@ import 'validators.dart' show ValidatorFn; exportAs: 'ngForm', visibility: Visibility.all, ) -class NgFormModel extends AbstractForm implements AfterChanges { +class NgFormModel extends AbstractForm implements AfterChanges { final ValidatorFn _validator; bool _formChanged = false; diff --git a/angular_forms/lib/src/directives/shared.dart b/angular_forms/lib/src/directives/shared.dart index f4ed5dc1f3..b90154f066 100644 --- a/angular_forms/lib/src/directives/shared.dart +++ b/angular_forms/lib/src/directives/shared.dart @@ -1,7 +1,7 @@ import 'dart:html'; import 'dart:js_util' as js_util; -import '../model.dart' show Control, ControlGroup; +import '../model.dart' show Control, AbstractControlGroup; import '../validators.dart' show Validators; import 'abstract_control_directive.dart' show AbstractControlDirective; import 'checkbox_value_accessor.dart' show CheckboxControlValueAccessor; @@ -43,7 +43,7 @@ void setUpControl(Control control, NgControl dir) { dir.valueAccessor.registerOnTouched(() => control.markAsTouched()); } -void setUpControlGroup(ControlGroup control, NgControlGroup dir) { +void setUpControlGroup(AbstractControlGroup control, NgControlGroup dir) { if (control == null) _throwError(dir, 'Cannot find control'); control.validator = Validators.compose([control.validator, dir.validator]); } diff --git a/angular_forms/lib/src/model.dart b/angular_forms/lib/src/model.dart index bb9d4ec5b5..da82a25d0d 100644 --- a/angular_forms/lib/src/model.dart +++ b/angular_forms/lib/src/model.dart @@ -7,7 +7,7 @@ import 'directives/validators.dart' show ValidatorFn; AbstractControl _find(AbstractControl control, List path) { if (path == null || path.isEmpty) return null; return path.fold(control, (v, name) { - if (v is ControlGroup) { + if (v is AbstractControlGroup) { return v.controls[name]; } else if (v is ControlArray) { var index = int.parse(name); @@ -474,27 +474,9 @@ class Control extends AbstractControl { /// `ControlGroup` is one of the three fundamental building blocks used to /// define forms in Angular, along with [Control] and [ControlArray]. /// [ControlArray] can also contain other controls, but is of variable length. -class ControlGroup extends AbstractControl> { - final Map controls; - - ControlGroup(this.controls, [ValidatorFn validator]) : super(validator) { - _setParentForControls(this, controls.values); - } - - /// Add a control to this group. - void addControl(String name, AbstractControl control) { - controls[name] = control; - control.setParent(this); - } - - /// Remove a control from this group. - void removeControl(String name) { - controls.remove(name); - } - - /// Check whether there is a control with the given name in the group. - bool contains(String controlName) => - controls.containsKey(controlName) && controls[controlName].enabled; +class ControlGroup extends AbstractControlGroup> { + ControlGroup(Map controls, [ValidatorFn validator]) + : super(controls, validator); @override void updateValue(Map value, @@ -519,43 +501,16 @@ class ControlGroup extends AbstractControl> { _value = _reduceValue(); } - @override - bool _anyControls(bool condition(AbstractControl c)) { - for (var name in controls.keys) { - if (contains(name) && condition(controls[name])) return true; - } - return false; - } - - @override - bool _allControlsHaveStatus(String status) { - if (controls.isEmpty) return this.status == status; - - for (var name in controls.keys) { - if (controls[name].status != status) return false; - } - return true; - } - - @override - void _forEachChild(void callback(AbstractControl c)) { - for (var control in controls.values) { - callback(control); - } - } - Map _reduceValue() { final res = {}; for (var name in controls.keys) { - if (_included(name) || disabled) { + if (included(name) || disabled) { res[name] = controls[name].value; } } return res; } - bool _included(String controlName) => controls[controlName]?.enabled ?? false; - void _checkAllValuesPresent(Map value) { if (value == null) return; @@ -577,6 +532,60 @@ class ControlGroup extends AbstractControl> { } } +/// Generic control group that allows creating your own group that is backed +/// by a value that is not a Map. +abstract class AbstractControlGroup extends AbstractControl { + final Map controls; + + AbstractControlGroup(this.controls, [ValidatorFn validator]) + : super(validator) { + _setParentForControls(this, controls.values); + } + + /// Add a control to this group. + void addControl(String name, AbstractControl control) { + controls[name] = control; + control.setParent(this); + } + + /// Remove a control from this group. + void removeControl(String name) { + controls.remove(name); + } + + /// Check whether there is a control with the given name in the group. + bool contains(String controlName) => + controls.containsKey(controlName) && controls[controlName].enabled; + + @override + bool _anyControls(bool condition(AbstractControl c)) { + for (var name in controls.keys) { + if (contains(name) && condition(controls[name])) return true; + } + return false; + } + + @override + bool _allControlsHaveStatus(String status) { + if (controls.isEmpty) return this.status == status; + + for (var name in controls.keys) { + if (controls[name].status != status) return false; + } + return true; + } + + @override + void _forEachChild(void callback(AbstractControl c)) { + for (var control in controls.values) { + callback(control); + } + } + + @protected + bool included(String controlName) => controls[controlName]?.enabled ?? false; +} + /// Defines a part of a form, of variable length, that can contain other /// controls. /// diff --git a/doc/developing/generated-code.md b/doc/developing/generated-code.md index 355fa0dc38..c771f7e6fb 100644 --- a/doc/developing/generated-code.md +++ b/doc/developing/generated-code.md @@ -1,10 +1,5 @@ # AngularDart's generated code - -[TOC] - - - This is a document focused on the _internals_ of the generated (`.template.dart`) files aimed primarily at _developers_ of AngularDart (versus diff --git a/doc/developing/issue-labels.md b/doc/developing/issue-labels.md index 4940739e92..34c17f0566 100644 --- a/doc/developing/issue-labels.md +++ b/doc/developing/issue-labels.md @@ -1,11 +1,5 @@ # Issue Labels - -go/angulardart/developing/issue-labels - -[TOC] - - diff --git a/doc/developing/style-guide.md b/doc/developing/style-guide.md index b8bc067c29..36c2c51ec2 100644 --- a/doc/developing/style-guide.md +++ b/doc/developing/style-guide.md @@ -1,15 +1,5 @@ # AngularDart Internal Style Guide - -go/angulardart/developing/style_guide - -[TOC] - - - Below is a non-authoritative style guide for building low-overhead/high-performance Dart applications, specifically when compiled to diff --git a/doc/effective/change-detection.md b/doc/effective/change-detection.md index d39624353b..f9aa3a6c21 100644 --- a/doc/effective/change-detection.md +++ b/doc/effective/change-detection.md @@ -1,11 +1,5 @@ # Effective AngularDart: Change Detection - -go/effective-angular-dart/change_detection - -[TOC] - - > **NOTE**: This is a work-in-progress, and not yet final. Some of the links may > be substituted with `(...)`, and some TODOs or missing parts may be in the diff --git a/doc/effective/di.md b/doc/effective/di.md index c35f2f1bfe..d246512115 100644 --- a/doc/effective/di.md +++ b/doc/effective/di.md @@ -1,11 +1,5 @@ # Effective AngularDart: Dependency Injection - -go/effective-angular-dart/di - -[TOC] - - > **NOTE**: This is a work-in-progress, and not yet final. Some of the links may > be substituted with `(...)`, and some TODOs or missing parts may be in the diff --git a/doc/effective/styling.md b/doc/effective/styling.md index 421801e215..dc2052470c 100644 --- a/doc/effective/styling.md +++ b/doc/effective/styling.md @@ -1,11 +1,5 @@ # Effective AngularDart: Styling - -go/effective-angular-dart/styling - -[TOC] - - AngularDart offers a variety of methods for styling your application. This document is intended to help developers understand how and when to use these diff --git a/doc/faq/change-detection.md b/doc/faq/change-detection.md index 169afbb0af..ea9a1a4d28 100644 --- a/doc/faq/change-detection.md +++ b/doc/faq/change-detection.md @@ -1,11 +1,5 @@ # Change Detection FAQ - -go/angulardart/faq/change_detection - -[TOC] - - Change detection is the workhorse that drives Angular applications. As such, it is important to understand its workings to write performant apps. Being the diff --git a/doc/faq/component-loading.md b/doc/faq/component-loading.md index d479c1d50b..4dbc7ee27a 100644 --- a/doc/faq/component-loading.md +++ b/doc/faq/component-loading.md @@ -1,10 +1,5 @@ # Imperative Component Loading - -[TOC] - - - AngularDart performs best when a template and application tree is statically defined. However, some smaller parts of an application might need to, at diff --git a/doc/faq/di.md b/doc/faq/di.md index 7b700dd459..28d4b7b385 100644 --- a/doc/faq/di.md +++ b/doc/faq/di.md @@ -1,11 +1,5 @@ # Dependency Injection FAQ - -go/angulardart/faq/di - -[TOC] - - Dependency Injection in AngularDart has several significant technical and pattern implications that are not trivial to work around or change without a diff --git a/doc/router/migration.md b/doc/router/migration.md index bed6223709..4e3bc56a6d 100644 --- a/doc/router/migration.md +++ b/doc/router/migration.md @@ -1,11 +1,5 @@ # Router Migration Guide - -go/angulardart/router/migration - -[TOC] - - ## Major changes diff --git a/doc/testing/e2e.md b/doc/testing/e2e.md index b1710bb626..71f007897c 100644 --- a/doc/testing/e2e.md +++ b/doc/testing/e2e.md @@ -1,10 +1,5 @@ # End to End Testing - -[TOC] - - - AngularDart does not currently have _direct_ support for integration/end-to-end testing, but instead has some Dart and JavaScript code for communicating with