Handling Input Counts with Directive and Service for State Management
- A directive is created to detect changes in the element and trigger updates.
- We use a service to store the state information of our form.
Important points: Please take note of the following details:
- The Service is currently set at the root level, limiting it to work for only one form. To handle multiple forms, you will need to provide the service at a component level or extend the state logic to support multiple forms using keys or similar methods.
- You may consider replacing the service with more advanced state management systems. The decision to use a service was based on its simplicity.
Note: This solution recalculates the total percentage every time any input is changed for simplicity. For larger forms, optimizing by checking if an input has been counted already may be more efficient.
The Directive
This directive assumes that it will only be used with HTMLInputElement
types.
@Directive({selector: '[countInput]'})
export class CountInputDirective implements AfterViewInit, OnDestroy {
private subscription = new Subscription();
constructor(private host: ElementRef<HTMLInputElement>, private countService: CountingService) {
}
ngAfterViewInit() {
this.countService.addInput(this.host.nativeElement);
this.subscription.add(
fromEvent(this.host.nativeElement, 'keydown')
.pipe(debounceTime(350))
.subscribe(change => {
this.countService.inputStateChanged()
})
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.countService.removeInput(this.host.nativeElement);
}
}
You can adjust the event binding according to your specific requirements if not all inputs support key events.
The Service
@Injectable({providedIn: 'root'})
export class CountingService {
private _inputs: HTMLInputElement[] = [];
private formUpdateEvent: EventEmitter<void> = new EventEmitter();
private _filled: number = 0;
get total() {
return this._inputs.length;
}
get percentageDone() {
return Math.round(this._filled / Math.max(this.total, 1) * 100);
}
get formUpdated() {
return this.formUpdateEvent.asObservable();
}
addInput(element: HTMLInputElement) {
this._inputs.push(element);
}
removeInput(element: HTMLInputElement) {
const index = this._inputs.indexOf(element);
if (index > -1) {
this._inputs.splice(index, 1);
}
}
inputStateChanged() {
this.recalculateState();
this.formUpdateEvent.emit();
}
private recalculateState() {
this._filled = 0;
this._inputs.forEach(element => {
if (Boolean(element.value))
this._filled++;
});
}
}
Implementing the Solution
If you need to perform actions when a change occurs, subscribe to the provided EventEmitter
. If you simply want to display the total percentage, access the getter directly.
@Component()
// inject the service in the component with your form.
constructor(private count: CountingService) {}
<!-- apply the directive to all inputs requiring counting -->
<form action="">
<input type="text" countInput>
<input type="text" countInput>
<input type="text" countInput>
</form>
{{count.percentageDone}}
</form>