typescript 54 lines · 8 steps

A custom range validator directive in Angular

An Angular attribute directive that validates a numeric range and mirrors its state into ARIA attributes.

Explained by highlit
1import { Directive, Input, HostBinding, HostListener } from '@angular/core';
2import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
3 
4@Directive({
5 selector: '[appNumericRange]',
6 standalone: true,
7 providers: [
8 { provide: NG_VALIDATORS, useExisting: NumericRangeDirective, multi: true },
9 ],
10})
11export class NumericRangeDirective implements Validator {
12 @Input({ required: true, alias: 'appNumericRange' }) range!: [number, number];
13 
14 private outOfRange = false;
15 
16 @HostBinding('attr.aria-invalid')
17 get ariaInvalid(): 'true' | null {
18 return this.outOfRange ? 'true' : null;
19 }
20 
21 @HostBinding('attr.aria-valuemin')
22 get ariaValueMin(): number {
23 return this.range[0];
24 }
25 
26 @HostBinding('attr.aria-valuemax')
27 get ariaValueMax(): number {
28 return this.range[1];
29 }
30 
31 @HostListener('input', ['$event.target.value'])
32 onInput(raw: string): void {
33 const value = Number(raw);
34 this.outOfRange = raw !== '' && (Number.isNaN(value) || value < this.range[0] || value > this.range[1]);
35 }
36 
37 validate(control: AbstractControl): ValidationErrors | null {
38 const [min, max] = this.range;
39 const value = Number(control.value);
40 
41 if (control.value === '' || control.value == null) {
42 this.outOfRange = false;
43 return null;
44 }
45 
46 if (Number.isNaN(value) || value < min || value > max) {
47 this.outOfRange = true;
48 return { numericRange: { min, max, actual: control.value } };
49 }
50 
51 this.outOfRange = false;
52 return null;
53 }
54}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Registering a directive under NG_VALIDATORS with multi:true plugs custom logic into Angular's form validation pipeline.
  2. 2HostBinding getters let a directive keep DOM attributes in sync with internal state declaratively.
  3. 3Reflecting validation state into ARIA attributes makes custom form controls accessible to assistive technology.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

A custom range validator directive in Angular — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code