Angular-Formly: interacting with multiple forms in multiple directives

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:

/** * Displays the ui for editing a specific user */ export function UserDetailsDirective() { class UserDetailsDirective { /*@ngInject*/ constructor( $stateParams, userService, formlyChangeService ) { this.currentUser = this.currentUser || {}; this.originalUser = this.originalUser || {}; this.userForms = { mainData: {}, personalData: {}, basicSettings: {} }; this.savingAllowed = true; /* Second try: Registering a callback at the change service, which will be executed on any field change in the passed form (mainData) */ 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"> [...] <!-- Right column --> </div> </div> <!-- Footer --> <user-details-footer on-save="controller.onSave(controller)" saving-allowed="controller.savingAllowed" ></user-details-footer> 

Bottom row

 /** * Displays the user details footer */ export function UserDetailsFooterDirective() { class UserDetailsFooterDirective { /*@ngInject*/ constructor( $state, Notification, $translate ) { this.state = $state; this.notification = Notification; this.translate = $translate; this.savingAllowed = this.savingAllowed || false; } /** * Event that is triggered on save button click * * Propagates to the parent controller via attribute binding */ saveEvent() { if (typeof this.onSave === 'function') { this.onSave(); } } /** * Navigates to the user list */ 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

 /** * Displays the contents of the tab "Account data" */ export function MainDataDirective() { class MainDataDirective { /*@ngInject*/ 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; /* Third try: Watching the form instance => partial success */ this.watch('formMainData', function(x, y, form) { console.log('formMainData', form); that.form = form; form.watch('$invalid', function(foo, bar, value) { /* This will react on field changes but it seems really dirty to me */ console.log('$invalid', arguments); }); }); formlyFormService.getFormConfiguration(mainDataFieldProviders).then((result) => { /* Here the formly fields are set */ this.fields = result; /* Second try: A service which provides a callback that will be executed on field invalidation => no success */ 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) } } /* First try to get the validity of the fields => no success */ isValid() { //return this.$timeout(() => { let isValid = true; this.fields.some((field) => { if ( field.validation.errorExistsAndShouldBeVisible === true || field.validation.serverMessages.length > 0 ) { isValid = false; return true; } }); //return isValid; //}, 10); return isValid; } /** * Method triggered by the formSubmit event */ onSubmit() { this.userSubmitService.submitUser(this.fields, this.model); } } return { restrict: 'E', templateUrl: 'components/usermanagement/edit/main-data/main-data.directive.html', controller: MainDataDirective, controllerAs: 'controller', bindToController: true, scope: { originalUser: '=user', api: '=?' }, link: (scope) => { scope.$watch('controller.originalUser', (newValue) => { if (newValue.hasOwnProperty('ID')) { scope.controller.model = angular.copy(newValue); } }); } } } 
 <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 /*@ngInject*/ 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

+5
source share
1 answer

I think you should have a service or factory, that all your directive depends on what contains the data for all your forms.

Thus, you can set the clock in your directive, which will call any method on your shared service to check / cancel forms on other tabs.

I hope this helps

0
source

Source: https://habr.com/ru/post/1244124/


All Articles