Forms structure

This documentation will help you with some best practices to use when building Angular reactive forms components that have a parent/input component structure (such as container/presenter). Note that this structure is simply an implementation of the ControlValueAccessor pattern, but we have chosen to guide you with an example implemented in the showcase application.

Parent/input component and reactive forms

A parent/input component architecture was put in place to ensure the best reusability and sharing of components.

Form creation in the parent component or input component?

  • The form creation (it can be a FormGroup or FormArray or FormControl) should be done in the input component because:
    • It is up to the input component to decide how the data will be displayed/computed. For example, a date can be displayed in an input field (FormControl) in one input component or in a FormGroup containing 3 input fields in another input component (the parent component only needs a date value).
    • We will not use the formGroup / formArray / formControl object as a two-way data binding object between the parent component and the input component.
  • The parent component only needs the value and, in some specific cases, the errors propagated from the input component. If needed, it can set the default value.

From now on we will refer to input component form object as the formGroup or formArray or formControl created in the input component.

Data exchange between parent component and input component

Simple cases

In a simple case, the purpose of the data exchange is to display the inline errors, check the form validity, and emit the form value.

  • The input component containing the form should:
    • Handle the display of form errors.
    • Trigger the form submit.
    • Check the form validity.
    • Use an event emitter to propagate the form value to the parent component.
  • The parent component should intercept the propagated value and execute the submit logic.

Complex cases

Data exchange in a complex case has the same purpose as the simple case, plus the display of a message panel containing the form errors and the ability to submit the form from the input component or page.

  • The input component containing the form should:
    • Implement ControlValueAccessor. It will propagate all the value/status changes done inside the input component form object to the parent component. In this way, it will behave as an input HTML element on which we can apply the ngModel directive, or we can bind a FormControl.
    • Implement the Validator interface if your form validators are only synchronous or the AsyncValidator interface if the form needs asynchronous validators. See Form Validation for more details about validation in Otter.
      • Implementing this interface gives us the possibility to define, in the validate function, the error object model which will be propagated to the parent component. See the form error documentation for details.
  • The parent component will apply a Form Control Directive to the input component form to give the possibility to:
    • Set the default value for the input component form object if needed.
    • Listen to the value changes if needed.
    • Listen to the status changes if needed.
    • Easily get the errors propagated by the input component.

We prefer to use the formControl rather than ngModel because we can easily listen to the valueChanges or statusChanges of the input component form. Another constraint is that it's easier to identify the parent component context for the CMS (See Component Structure for details about the component context).

Component creation

Here, a component refers to a parent component or an input component.

Basic case

In this case, all we need to do is to implement a form, display the inline errors, check the form validity, and do something with the form value.

In the input component:

  • The form is created.
  • The validators are applied (see Form Validation for details about validators and where they are created).
  • The inline errors are handled (see Form Errors for details about the error messages translations).
  • The form validity will be checked.
  • The submission is triggered and the form value is emitted.

The parent component:

  • Captures the form value emitted.
  • Executes the submit logic.

The difference from the default implementation of the forms in Angular is that we have to emit the form value from the parent component to the input component, using an @Output event. Another difference might be related to the custom validators, which we suggest to be created in the parent component because they can be related to the business logic. (Please have a look at the dedicated section on the forms validators.).

Adding complexity

In addition to the simple case, if we need an error message panel, which can be displayed anywhere in the page, or to submit the form outside the component, we must follow the more complex implementation described below.

1. Basic structure

The form created in the input component and the default value of the form control in the parent component should have the same contract. The contract of a form is an interface that defines the names of the form controls and the type of value that should be handled by each control.

Below is an example of a component creation based on a form used to introduce data for a PersonalInfo object.

Define the contract object:

Below is an example of a model used to create a form that introduces data for a PersonalInfo object.

Example :
/** Model used to create Personal Info form */
export interface PersonalInfo {
  /** Name */
  name: string;
  /** Date of birth */
  dateOfBirth: string;
}

Parent component class:

  • Create a form control to set the binding and the default data.
  • This form control will be passed as an input to the input component class through the HTML template.

You can find the implementation of a parent component class in the showcase application.

Input component class:

  • Here we have to create the formGroup/formArray/formControl object.
  • Provide NG_VALUE_ACCESSOR - used to provide a ControlValueAccessor for form controls, to write a value and listen to changes on input elements.
  • Provide NG_VALIDATORS - this is an InjectionToken for registering additional synchronous validators used with forms.

You can find the implementation of an input component class in the showcase application.

Submit and Intercommunication:

We have to handle specific cases for form submission and communication between input component/parent component/page. For the submit action, we have to support two cases:

  • Submit from page (app level) - there is no submit button in the input component and the submit action is triggered at application level.
    • The page triggers the submit action. The parent component receives the signal, executes the submit logic, and emits an event when the submit logic is finished.

      This is useful when you have multiple forms on a page and you want to trigger the submit for all in the same time.

  • Submit from input component - the submit button is displayed.
    • The submit button is clicked and the input component emits an event. The parent component receives the signal, executes the submit logic, and emits an event when the submit logic is finished.

This section is explained in details in the Otter form submit and intercommunication documentation.

2. Include validation

You can create basic or custom validators in your application, depending on the use cases. You can find details on this in the Otter form validation documentation.

results matching ""

    No results matching ""