Skip to main content

Legacy Angular App - Coding Patterns

NOTE: This advice relates to the legacy Angular frontend, which we are deprecating and is included for reference purposes until that work is complete.

Debugging

In the supervision directory: cd front-end/supervision

Run The Command: yarn test:watch

In Studio Code, do to the Debug panel, ensure that Debug Supervision Jest is selected and press play.

Base Form and Form Parent Classes

Once reducers are dry and all reducers follow a consistent pattern for the forms in question, a the new BaseForm and BaseFormParent classes should be applied rather than repeating code on the form smart and dumb components. This is new so may require development, but an example of this is used on the add-task.component and task-form.component. The classes themselves are located in components/classes folder.

Usage for smart component that contains the form:

import { FormData } from '../../classes/base-form/form-data.class';
import { BaseFormParent } from '../../classes/base-form/base-form-parent.class';
// Call super(); in constructor and extend the class BaseFormParent
// Define data components that will be passed to the dumb form stored in a data variable by a combined subscription, e.g.

this.data = new FormData<any>();

this.teamsState$.pipe(
  combineLatest(this.usersState$, this.taskTypesState$, this.tasksState$),
  takeUntil(this.destroyed$)
).subscribe( ( data: any[] ): void => {
this.data = {
  loaders: [data[0].loadStep, data[1].active.loadStep, data[2].loadStep],
  savers: [data[3].activeTask ? data[3].activeTask.saveStep : null ],
  payload: { teams: data[0].teams, user: data[1].active.user, taskTypes: data[2].taskTypes }
};
});

//Then pass in data component in the HTML instead of each entity, and use this in the dumb component, see below:

Usage for dumb component that is the form:

import { BaseForm } from '../../../classes/base-form/base-form.class';

get taskTypes() {
 return this.data.payload ? this.data.payload.taskTypes : null;
}

get teams() {
 return this.data.payload ? this.data.payload.teams : null;
}

get activeUser() {
 return this.data.payload ? this.data.payload.user : null;
}

// And in constructor, call super() and be sure to also call this.trackFormChanges(); after the form has been initialised.

Disabled form state

The latest pattern for saving a form disables the form during the “please wait” and “saved” states. To achieve this, have the following getters in the form component itself:

get loading() {
 return (this.recordDeathState && this.recordDeathState.loadStep.started && !this.recordDeathState.loadStep.finished);
}

get saving() {
 return (this.recordDeathState && this.recordDeathState.saveStep && this.recordDeathState.saveStep.started);
}

get saved() {
 return (this.recordDeathState && this.recordDeathState.saveStep && this.recordDeathState.saveStep.finished);
}

This gives you a nice set of states for the form data that you can trigger various things from. The form is hidden on loading [hidden]="loading" and disabled on save. You can disable on save with a further getter:

get isDisabled() {
 return this.disabled || this.saving || this.saved;
}

this allows the form to automatically disable on a save action, but also disable if the calling smart component wants to disable the form outside of that process (ie viewing the record death form).

Importantly, the call to form.disable() must be called so that the forms are disabled. This is both best practice and will allow accessibility aids to understand what is going on. That is achived by the following in the ngOnChanges() method:

if (this.isDisabled) this.form.disable();
  else this.form.enable();

by using the isDisabled getter we can unify the form disabled state, and the CSS application:

[ngClass]="{ disabled : isDisabled }"

Now the calling parent doesn’t need to concern itself with the form state and the [disabled] input will only be used when there is an external need to disable the form.

In page notifier for loading and saving states

The in page notifier component is used in both loading and saving patterns for the form. To cover both ends of the process, have two on the page (one for each step - save & load). The following snippets show an easy use case:

Loading

<in-page-notification [stepState]="(recordDeath$ | async).loadStep" [hidden]="(recordDeath$ | async).loadStep.finished">
 <div progress-message>
  <div translate>MESSAGES.PLEASEWAIT</div>
 </div>
 <div error-message>
  <div>
   <strong>{{ deputy ? [ deputy.salutation, deputy.firstname, deputy.otherNames, deputy.surname].join('
    ') : '' }}</strong>
   <em message translate>MESSAGES.LOADFAILED</em>
  </div>
 </div>
</in-page-notification>

The notifier hangs off the load step, only has a processing and failure message configured, and is hidden when the step is finished (not started or errored).

Saving

<in-page-notification [stepState]="(recordDeath$ | async).saveStep" [hidden]="(recordDeath$ | async).saveStep.errored">
 <div progress-message>
  <div translate>MESSAGES.PLEASEWAIT</div>
 </div>
 <div success-message>
  <div>
   <strong>{{ deputy ? [ deputy.salutation, deputy.firstname, deputy.otherNames, deputy.surname].join('
    ') : '' }}</strong>
   <em message translate>MESSAGES.UPDATED</em>
  </div>
 </div>
</in-page-notification>

The notifier hangs off the save step and is hidden on error only so that the validation summary can take over.

Run both of these in the smart component and they should take care of loading and saving messaging as per the current UX patterns.

Radio Button Group

There is a component now that can be used to create a radio button group, listed below are the steps you must do in order for it to work

A new FormHelper class was created to make the supplying of the datasource easier.

A. Import the RadioButtonGroupModule module in to the [your-component].module.ts B. If using the radio button group for simple yes no then use the FormHelper to build a datasource and add it to the [your-component].component.ts

private yesNoDataSource: IInputGroupSourceDataApp[] = null;

constructor() {
    this.yesNoDataSource = FormHelper.createYesNoInputGroupDataSource();
}

C. Add the radio button group element to the html

<radio-button-group [formControl]="form.get('yesNoField')"
    label="Yes No field?"
    [invalid]="invalid('yesNoField')" [sourceData]="yesNoDataSource"
    name="yesNoField" ngDefaultControl></radio-button-group>

D. If you are using values from refData then you can pass the refData object to the FormHelper and create the datasource that way. The rest is identical

Inline Paginator

The inline paginator is provided as a self-contained pagination control that will set inside any container element. At present it is used on the dashboard screen to paginate the tasks list.

Usage

<inline-paginator [currentPage]="currentTasksPage" [pageSize]="pageSize" [totalResults]="totalTasks"
    (pageSelected)="changeTasksPage($event)"></inline-paginator>

The available inputs are:

  • display - how many pages to display on screen (default is 5)
  • pageSize - the size of a single page of results
  • totalResults - the full total of all available results
  • currentPage - the current page to display

The control also emits an event pageSelected providing the index of the selected page when a user interacts with any of the controls.

the available interactions to a user are:

  • click on any page number to move directly to that page
  • click the forward or back chevron to move one page in that direction
  • click on the first or last page numbers (for result sets that are larger than the display property).

Rendering rules

The control renders pages with the following rules:

Display a number of page indexes, upto the display property value. If the number of available pages is less, this will be the maximum.

1 2 3 4 5

Display a forward > and back < chevron for navigation. Controls will disable when the start or end of the available pages is reached.

< 1 2 3 4 5 >

When the number of pages exceeds the display limit, the final page index is displayed after the main set of pages, separated by three ellipses ....

< 1 2 3 4 ... 10 >

As the user moves through the pages and the first page is no longer present in the central list of pages, it is prepended to the start of the list, separated by three ellipses ....

< 1 ... 3 4 5 6 7 ... 10 >

The only exception to the above rule is where the first page is no longer in the list, but the second page is. In this case the ellipses will not be shown.

< 1 2 3 4 5 6 7 ... 10 >

The same rule applies when approaching the last page in the list also.

< 1 ... 5 6 7 8 9 10 >

This page was last reviewed on 24 March 2023. It needs to be reviewed again on 24 September 2023 by the page owner #opg-sirius-develop .
This page was set to be reviewed before 24 September 2023 by the page owner #opg-sirius-develop. This might mean the content is out of date.