or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

credit-card-validation.mdemail-validation.mdform-control-utilities.mdindex.mdpassword-validation.mdtemplate-driven-forms.mduniversal-validation.md

form-control-utilities.mddocs/

0

# Form Control Utilities

1

2

Utility classes and functions for form control manipulation, validation state management, and specialized validation scenarios.

3

4

## Capabilities

5

6

### AbstractControlUtil

7

8

Utility class providing helper methods for Angular AbstractControl manipulation and validation state management.

9

10

#### Control Presence Check

11

12

Determines if a control has a meaningful value (not null, undefined, or empty string).

13

14

```typescript { .api }

15

/**

16

* Checks if control has a meaningful value

17

* @param control - AbstractControl to check

18

* @returns true if control value is null, undefined, or empty string

19

*/

20

static isNotPresent(control: AbstractControl): boolean;

21

```

22

23

**Usage Example:**

24

25

```typescript

26

import { AbstractControlUtil } from "ngx-validators";

27

import { FormControl } from "@angular/forms";

28

29

const control1 = new FormControl(null);

30

const control2 = new FormControl('');

31

const control3 = new FormControl('value');

32

const control4 = new FormControl(0);

33

34

console.log(AbstractControlUtil.isNotPresent(control1)); // true

35

console.log(AbstractControlUtil.isNotPresent(control2)); // true

36

console.log(AbstractControlUtil.isNotPresent(control3)); // false

37

console.log(AbstractControlUtil.isNotPresent(control4)); // false

38

```

39

40

**Implementation Logic:**

41

- Returns `true` if value is `undefined` or `null`

42

- Returns `true` if value is empty string `""`

43

- Returns `false` for all other values (including `0`, `false`, empty arrays/objects)

44

45

#### Error Management

46

47

##### Add Error

48

49

Programmatically adds a validation error to a control.

50

51

```typescript { .api }

52

/**

53

* Adds a validation error to the control

54

* @param control - AbstractControl to add error to (can be null)

55

* @param errorId - String identifier for the error

56

* @param value - Error value/details

57

*/

58

static addError(control: AbstractControl | null, errorId: string, value: any): void;

59

```

60

61

**Usage Example:**

62

63

```typescript

64

import { AbstractControlUtil } from "ngx-validators";

65

import { FormControl } from "@angular/forms";

66

67

const control = new FormControl('invalid-value');

68

69

// Add custom validation error

70

AbstractControlUtil.addError(control, 'customRule', {

71

message: 'Value does not meet custom criteria',

72

expected: 'valid-format',

73

actual: 'invalid-value'

74

});

75

76

console.log(control.errors);

77

// Output: { customRule: { message: '...', expected: '...', actual: '...' } }

78

79

// Add simple boolean error

80

AbstractControlUtil.addError(control, 'required', true);

81

82

console.log(control.errors);

83

// Output: { customRule: {...}, required: true }

84

```

85

86

**Behavior:**

87

- If control is `null`, the method does nothing

88

- If control has no existing errors, creates new errors object with the specified error

89

- If control has existing errors but not the specified `errorId`, adds the new error

90

- If the error already exists, does nothing (doesn't overwrite)

91

92

##### Remove Error

93

94

Programmatically removes a specific validation error from a control.

95

96

```typescript { .api }

97

/**

98

* Removes a specific validation error from the control

99

* @param control - AbstractControl to remove error from (can be null)

100

* @param errorId - String identifier of the error to remove

101

*/

102

static removeError(control: AbstractControl | null, errorId: string): void;

103

```

104

105

**Usage Example:**

106

107

```typescript

108

const control = new FormControl('', {

109

validators: [/* some validators */]

110

});

111

112

// Assume control has multiple errors

113

console.log(control.errors);

114

// Output: { required: true, minlength: { requiredLength: 5, actualLength: 0 } }

115

116

// Remove specific error

117

AbstractControlUtil.removeError(control, 'required');

118

119

console.log(control.errors);

120

// Output: { minlength: { requiredLength: 5, actualLength: 0 } }

121

122

// Remove the last error

123

AbstractControlUtil.removeError(control, 'minlength');

124

125

console.log(control.errors);

126

// Output: null (errors object is cleared when no errors remain)

127

```

128

129

**Behavior:**

130

- If control is `null`, the method does nothing

131

- If control has no errors or doesn't have the specified error, does nothing

132

- If removing the error would leave other errors, removes only the specified error

133

- If removing the error would leave no errors, sets control errors to `null`

134

135

### EqualToValidator

136

137

Specialized validator for ensuring two form controls have equal values, commonly used for password confirmation scenarios.

138

139

#### Equal To Validation

140

141

Creates a validator function that compares two controls within the same form group.

142

143

```typescript { .api }

144

/**

145

* Creates validator that ensures two controls have equal values

146

* @param c1Name - Name of the first control to compare

147

* @param c2Name - Name of the second control to compare

148

* @returns ValidatorFn that validates equality at form group level

149

*/

150

static equalTo(c1Name: string, c2Name: string): ValidatorFn;

151

```

152

153

**Usage Example:**

154

155

```typescript

156

import { EqualToValidator } from "ngx-validators";

157

import { FormBuilder, FormGroup } from "@angular/forms";

158

159

export class RegistrationComponent {

160

registrationForm: FormGroup;

161

162

constructor(private fb: FormBuilder) {

163

this.registrationForm = this.fb.group({

164

password: [''],

165

confirmPassword: [''],

166

email: [''],

167

confirmEmail: ['']

168

}, {

169

validators: [

170

EqualToValidator.equalTo('password', 'confirmPassword'),

171

EqualToValidator.equalTo('email', 'confirmEmail')

172

]

173

});

174

}

175

}

176

```

177

178

**Advanced Usage with Custom Control Names:**

179

180

```typescript

181

const userForm = this.fb.group({

182

newPassword: [''],

183

passwordVerification: [''],

184

primaryEmail: [''],

185

emailConfirmation: ['']

186

}, {

187

validators: [

188

EqualToValidator.equalTo('newPassword', 'passwordVerification'),

189

EqualToValidator.equalTo('primaryEmail', 'emailConfirmation')

190

]

191

});

192

```

193

194

**Error Handling:**

195

196

The validator manages errors at two levels:

197

198

1. **Form Group Level**: Returns validation error object if controls don't match

199

2. **Control Level**: Directly adds/removes `notEqualTo` error on the second control

200

201

```typescript

202

// Form group level error (returned by the validator)

203

if (form.errors?.equalTo) {

204

console.log('Form has equality validation errors');

205

}

206

207

// Control level error (added directly to the second control)

208

const confirmControl = form.get('confirmPassword');

209

if (confirmControl?.errors?.notEqualTo) {

210

console.log('Confirmation password does not match');

211

}

212

```

213

214

**Template Usage:**

215

216

```html

217

<form [formGroup]="registrationForm">

218

<input formControlName="password" type="password" placeholder="Password">

219

220

<input formControlName="confirmPassword" type="password" placeholder="Confirm Password">

221

222

<!-- Show error from the control level -->

223

<div *ngIf="registrationForm.get('confirmPassword')?.errors?.notEqualTo">

224

Passwords do not match.

225

</div>

226

227

<!-- Show error from the form level -->

228

<div *ngIf="registrationForm.errors?.equalTo">

229

Please ensure all fields match their confirmations.

230

</div>

231

</form>

232

```

233

234

## Integration with Other Validators

235

236

### Using Utilities in Custom Validators

237

238

```typescript

239

import { AbstractControlUtil } from "ngx-validators";

240

import { AbstractControl, ValidatorFn, ValidationErrors } from "@angular/forms";

241

242

export function customValidator(): ValidatorFn {

243

return (control: AbstractControl): ValidationErrors | null => {

244

// Use utility to check if control has meaningful value

245

if (AbstractControlUtil.isNotPresent(control)) {

246

return null; // Don't validate empty controls

247

}

248

249

const value = control.value;

250

251

// Custom validation logic

252

if (value !== 'expected-value') {

253

// Use utility to add error to related control

254

const relatedControl = control.parent?.get('relatedField');

255

AbstractControlUtil.addError(relatedControl, 'relatedFieldError', {

256

message: 'Related field must be updated when this field changes'

257

});

258

259

return { customValidation: { expected: 'expected-value', actual: value } };

260

} else {

261

// Use utility to remove error from related control

262

const relatedControl = control.parent?.get('relatedField');

263

AbstractControlUtil.removeError(relatedControl, 'relatedFieldError');

264

265

return null;

266

}

267

};

268

}

269

```

270

271

### Combining with Password Validators

272

273

```typescript

274

import { PasswordValidators, AbstractControlUtil, EqualToValidator } from "ngx-validators";

275

276

const passwordForm = this.fb.group({

277

password: ['', [

278

PasswordValidators.digitCharacterRule(1),

279

PasswordValidators.lowercaseCharacterRule(1),

280

PasswordValidators.uppercaseCharacterRule(1)

281

]],

282

confirmPassword: ['']

283

}, {

284

validators: [EqualToValidator.equalTo('password', 'confirmPassword')]

285

});

286

287

// Manual error management example

288

const handlePasswordChange = (newPassword: string) => {

289

const confirmControl = passwordForm.get('confirmPassword');

290

291

if (AbstractControlUtil.isNotPresent(confirmControl)) {

292

return;

293

}

294

295

// Clear previous custom errors

296

AbstractControlUtil.removeError(confirmControl, 'passwordStrengthMismatch');

297

298

// Add custom validation if needed

299

if (newPassword.length > 0 && confirmControl.value !== newPassword) {

300

AbstractControlUtil.addError(confirmControl, 'passwordStrengthMismatch', {

301

message: 'Confirmation password must match the new password'

302

});

303

}

304

};

305

```

306

307

### EqualToDirective

308

309

Template-driven forms directive that validates two form controls have equal values, with reactive subscription management and automatic validation updates.

310

311

```typescript { .api }

312

@Directive({

313

selector: "[equalTo][ngModel], [equalTo][formControlName], [equalTo][formControl]"

314

})

315

export class EqualToDirective implements Validator, OnDestroy, OnChanges {

316

@Input() equalTo: string | AbstractControl;

317

318

validate(c: AbstractControl): ValidationErrors | null;

319

ngOnDestroy(): void;

320

ngOnChanges(changes: SimpleChanges): void;

321

registerOnValidatorChange(fn: () => void): void;

322

}

323

```

324

325

**Usage Examples:**

326

327

```html

328

<!-- Basic usage with ngModel -->

329

<form>

330

<input

331

type="password"

332

name="password"

333

[(ngModel)]="user.password"

334

#password="ngModel">

335

336

<input

337

type="password"

338

name="confirmPassword"

339

[(ngModel)]="user.confirmPassword"

340

#confirmPassword="ngModel"

341

[equalTo]="'password'">

342

343

<div *ngIf="confirmPassword.errors?.notEqualTo">

344

Passwords do not match.

345

</div>

346

</form>

347

348

<!-- Usage with reactive forms in template -->

349

<form [formGroup]="myForm">

350

<input formControlName="email" type="email">

351

352

<input

353

formControlName="confirmEmail"

354

type="email"

355

[equalTo]="'email'">

356

357

<div *ngIf="myForm.get('confirmEmail')?.errors?.notEqualTo">

358

Email addresses do not match.

359

</div>

360

</form>

361

```

362

363

**Advanced Usage with AbstractControl Reference:**

364

365

```typescript

366

export class MyComponent {

367

@ViewChild('passwordControl') passwordControl!: NgModel;

368

369

// Can pass control reference directly

370

getPasswordControl(): AbstractControl {

371

return this.passwordControl.control;

372

}

373

}

374

```

375

376

```html

377

<input

378

type="password"

379

name="password"

380

[(ngModel)]="user.password"

381

#passwordControl="ngModel">

382

383

<input

384

type="password"

385

name="confirmPassword"

386

[(ngModel)]="user.confirmPassword"

387

#confirmPassword="ngModel"

388

[equalTo]="getPasswordControl()">

389

```

390

391

**Key Features:**

392

393

1. **Reactive Subscription Management**: Automatically subscribes to target control's value changes

394

2. **Memory Leak Prevention**: Implements `OnDestroy` to unsubscribe from observables

395

3. **Circular Update Prevention**: Uses `delay(1)` operator to prevent validation loops

396

4. **Flexible Input**: Accepts either control name (string) or AbstractControl reference

397

5. **Error Management**: Returns `{ notEqualTo: true }` when values don't match

398

399

**Error Object:**

400

401

```typescript

402

{

403

notEqualTo: true

404

}

405

```

406

407

**Implementation Notes:**

408

409

- The directive creates a subscription to the target control's `valueChanges` observable

410

- When the target control value changes, it triggers validation on the current control

411

- The `delay(1)` operator prevents infinite validation loops

412

- The directive properly cleans up subscriptions to prevent memory leaks

413

- Works with both template-driven forms (`ngModel`) and reactive forms (`formControlName`, `formControl`)

414

415

## Error State Management Best Practices

416

417

### Conditional Error Display

418

419

```typescript

420

export class FormUtilities {

421

/**

422

* Checks if a control should display errors

423

* @param control - FormControl to check

424

* @returns true if control has errors and has been touched or is dirty

425

*/

426

static shouldShowErrors(control: AbstractControl): boolean {

427

return !!(control && control.errors && (control.touched || control.dirty));

428

}

429

430

/**

431

* Gets user-friendly error message for a control

432

* @param control - FormControl with potential errors

433

* @returns formatted error message or empty string

434

*/

435

static getErrorMessage(control: AbstractControl): string {

436

if (!control || !control.errors) return '';

437

438

if (control.errors['required']) return 'This field is required';

439

if (control.errors['notEqualTo']) return 'Values do not match';

440

if (control.errors['minlength']) {

441

const error = control.errors['minlength'];

442

return `Minimum length is ${error.requiredLength} characters`;

443

}

444

445

return 'Please check this field';

446

}

447

}

448

```

449

450

**Template Usage:**

451

452

```html

453

<div class="form-field">

454

<input formControlName="confirmPassword" type="password">

455

456

<div class="error-message"

457

*ngIf="shouldShowErrors(form.get('confirmPassword'))">

458

{{ getErrorMessage(form.get('confirmPassword')) }}

459

</div>

460

</div>

461

```