I am trying to figure out how to save and respond to multiple forms in multiple directives.
To give you a quick overview: Screenshot of current view
I have three tabs containing forms, and the fourth is JsTree (Groups). Each of the three tabs contains a directive, which, in turn, contains a Formly form. The tabs are wrapped with the main directive, which contains the bottom line with the save and cancel buttons in the lower right corner.
The main directive:
export function UserDetailsDirective() { class UserDetailsDirective { constructor( $stateParams, userService, formlyChangeService ) { this.currentUser = this.currentUser || {}; this.originalUser = this.originalUser || {}; this.userForms = { mainData: {}, personalData: {}, basicSettings: {} }; this.savingAllowed = true; formlyChangeService.onFormChange('mainData', () => { console.log('test123'); console.log('this123', this); console.log('this.userForms.mainData.api.isValid()', this.userForms.mainData.api.isValid()); }); if ($stateParams.id > 0) { userService.getUser($stateParams.id).then((userData) => { userData.Birthday = new Date(userData.Birthday); this.currentUser = userData; this.breadcrumbData = [...]; }) } } onSave(controller) { alert('on save'); console.log('controller', controller); } } return { restrict: 'E', templateUrl: 'components/usermanagement/edit/user-details/user-details.directive.html', controller: UserDetailsDirective, controllerAs: 'controller', bindToController: true } }
<breadcrumb [...]></breadcrumb> <ul class="nav nav-tabs"> <li class="active"><a data-toggle="tab" data-target="#mainData">Account data</a></li> <li><a data-toggle="tab" data-target="#personalData">Personal data</a></li> <li><a data-toggle="tab" data-target="#basicSettings">Settings</a></li> <li><a data-toggle="tab" data-target="#userGroupAssignment">Groups</a></li> </ul> <div class="row"> <div class="col-lg-6"> <div class="tab-content"> <div id="mainData" class="tab-pane fade in active"> <main-data user="controller.currentUser"></main-data> </div> <div id="personalData" class="tab-pane fade"> <personal-data user="controller.currentUser"></personal-data> </div> <div id="basicSettings" class="tab-pane fade"> <basic-settings user="controller.currentUser"></basic-settings> </div> <div id="userGroupAssignment" class="tab-pane fade"> <group-assignment user="controller.currentUser"></group-assignment> </div> </div> </div> <div class="col-lg-6"> [...] </div> </div> <user-details-footer on-save="controller.onSave(controller)" saving-allowed="controller.savingAllowed" ></user-details-footer>
Bottom row
export function UserDetailsFooterDirective() { class UserDetailsFooterDirective { constructor( $state, Notification, $translate ) { this.state = $state; this.notification = Notification; this.translate = $translate; this.savingAllowed = this.savingAllowed || false; } saveEvent() { if (typeof this.onSave === 'function') { this.onSave(); } } goToUserList() { this.state.go('userList'); } } return { restrict: 'E', templateUrl: 'components/usermanagement/edit/user-details-footer/user-details-footer.directive.html', controller: UserDetailsFooterDirective, controllerAs: 'controller', bindToController: true, scope: { onSave: '&?', savingAllowed: '=?' } } }
<nav class="navbar navbar-fixed-bottom"> <div class="container-fluid pull-right"> <button class="btn btn-default" ng-click="controller.goToUserList()"><i class="fontIcon fontIconX"></i> Cancel</button> <button class="btn btn-primary" ng-disabled="controller.savingAllowed !== true" ng-click="controller.saveEvent()"><i class="fontIcon fontIconSave"></i> Save</button> </div> </nav>
First tab directive
export function MainDataDirective() { class MainDataDirective { constructor( formlyFormService, mainDataFieldProviders, $state, userSubmitService, $timeout, formlyChangeService, $scope ) { this.state = $state; this.$timeout = $timeout; this.userSubmitService = userSubmitService; this.model = {}; this.originalUser = this.originalUser || {}; this.fields = []; this.form = null; var that = this; this.watch('formMainData', function(x, y, form) { console.log('formMainData', form); that.form = form; form.watch('$invalid', function(foo, bar, value) { console.log('$invalid', arguments); }); }); formlyFormService.getFormConfiguration(mainDataFieldProviders).then((result) => { this.fields = result; formlyChangeService.registerFields(this.fields, 'mainData'); }, (error) => { console.error('getMainDataFields error:', error); }); this.api = { isValid: angular.bind(this, this.isValid), submit: angular.bind(this, this.onSubmit) } } isValid() {
<form name="controller.form" ng-submit="controller.onSubmit()" class="form-horizontal" novalidate> <formly-form form="controller.formMainData" model="controller.model" fields="controller.fields" ></formly-form> </form>
Second attempt: FormlyChangeService => received a change event, but before checking => no success
export function FormlyChangeService() { let callbacks = []; return { triggerFormChangeEvent: triggerFormChangeEvent, registerFields: registerFields, onFormChange: onFormChange }; function triggerFormChangeEvent(value, options) { callbacks.forEach((callback) => { if ( typeof callback === 'function' && callback.formDirective === options.templateOptions.formDirective ) { callback(); } }); } function onFormChange(formDirective, callback) { callback.formDirective = formDirective; callbacks.push(callback); } function registerField(fieldConfig) { fieldConfig.templateOptions.changeEvents.push( triggerFormChangeEvent ); } function registerFields(fieldConfigs, formDirective) { fieldConfigs.forEach((fieldConfig) => { fieldConfig.templateOptions.formDirective = formDirective; registerField(fieldConfig); fieldConfig.watcher = { listener: function() { console.log('listener', arguments); } }; console.log('fieldConfig', fieldConfig); fieldConfig.watch('$valid', function() { console.log('valid field', arguments); }); }); } }
Formal forms are submitted with a custom model, which is provided by the main directive.
I need to save all four tabs at once, because there are several required fields that must be present in order to save the entered record. Now here is the hard part:
I want the save button to be disabled if the model has not changed or an error has occurred in any field in any form. I also want to know what form the error comes from.
What I was thinking about is an event or observer in the Formly configuration file or something like that.
I tried the onChange event in the field configuration, but it fires right before checking the field, so I wonβt get the current error status of this field.
The error status should be transferred to the main directive, from where it should be transmitted before the save button.
Can someone help me get the forms (or even better corresponding fields) to inform the main directive about the presence of an invalid field?
It is actually hard to imagine an example of such a complex task, so if there is any ambiguity, please let me know.
Thank you in advance.
Julian