How to update ngModel from directive?
Asked Answered
K

1

6

I've created a directive to limit the length of input field type=number.

// Input

<input min="1" appLimitTo [limit]="5" type="number" name="property" [(ngModel)]="property">

// Directive

import {Directive, HostListener, Input} from '@angular/core';

@Directive({
  selector: '[appLimitTo]',
})
export class LimitToDirective {

    @Input() limit: number;
    @Input() ngModel: any;

    @HostListener('input', ['$event'])
    onInput(e) {
        if (e.target.value.length >= +this.limit) {
            e.target.value = (e.target.value).toString().slice(0, this.limit - 1);
            e.preventDefault();
        }
    }
}

It works fine if we enter the value through the keyboard. But the problem arises when if I copy & paste 12345678913465789 this number, this line e.target.value = (e.target.value).toString().slice(0, this.limit - 1); shorten it to the limit, but the ngModel still contains 12345678913465789 value. How to update this ngModel value?

Please help.

PS - What should I add in my directive to meet the requirement?

Kosher answered 9/4, 2018 at 13:15 Comment(1)
Angular directives @outputHobbs
F
12

You can inject NgControl into your own directive. You can then listen to the control valueChanges event.

limit-to.directive.ts

import {Directive, HostListener, Input, OnInit, OnDestroy} from '@angular/core';
import {NgControl} from '@angular/forms';
import {map} from 'rxjs/operators';
import {Subscription} from 'rxjs/Subscription';

@Directive({
  selector: '[appLimitTo]',
})
export class LimitToDirective implements OnInit, OnDestroy {
    @Input('appLimitTo') limit: number;

    private subscription: Subscription;

    constructor(private ngControl: NgControl) {}

    ngOnInit() {
      const ctrl = this.ngControl.control;

      this.subscription = ctrl.valueChanges
        .pipe(map(v => (v || '').toString().slice(0, this.limit)))
        .subscribe(v => ctrl.setValue(v, { emitEvent: false }));
    }

    ngOnDestroy() {
      this.subscription.unsubscribe();
    }
}

usage:

<input ngModel appLimitTo="3" type="number" />

Live demo

Furniture answered 9/4, 2018 at 13:25 Comment(8)
You've probably initialized the ngModel to null. Check out my edit. The map function now looks like this: map(v => (v || '').slice(0, this.limit))Furniture
No have checked... It's not like that... I've tried to check the v using if but still, the same error arises... Is there an alternative way to do so.... using ElementRef and Events?Kosher
Please tell me how to fix it?Kosher
note that you should unsubscribe on destroy to avoid mem leaksRite
I'm aware of that. I wanted to keep the example as simple as possible as not to confuse the OP. It was probably a bad idea, because I should not have assumed that OP knows about unsubscribing. I'll edit it now.Furniture
FYI. OP is using type="number so this solution will throw the error OP mentions. This stackblitz works because it isn't type number. It can easily be fixed by changing (v || '').slice to (v || '').toString().slice.Ansate
Fixed it. Thank you for the comment :)Furniture
Everything is working fine with directive except 1 thing. It is not allowing the user to enter 0. How can I fix this?Kosher

© 2022 - 2024 — McMap. All rights reserved.