17

I have this code for a textarea, it is working great with the ngModel and updating live, however i would like my-custom-directive to know when the model bound to this textarea is changing.

How can i detect a model change for this textarea in the my-custom-directive?

  <textarea my-custom-directive class="green" [(ngModel)]="customertext"></textarea>
0

5 Answers 5

10

Update

@Directive({
  selector: 'xyz', 
  host: {'(ngModelChange)': 'doSomething($event)'}  
}) 
export class Xyz {
  doSomething(event){... }  
} 

Original

<textarea my-custom-directive class="green" [(ngModel)]="customertext"
    (ngModelChange)="doSomething($event) "></textarea>
[(ngModel)]="customertext"

is the short form of

[ngModel]="customertext" (ngModelChange)="customertext=$event"  
Sign up to request clarification or add additional context in comments.

4 Comments

thankyou, but as stated, i want the directive to detect this change, not to have to add it to the html code of the textarea
this only works if you are typing in the textarea, not if the model changes from another textarea that is bound to the same model.
If "customertext" is an @Input()? Implementing ngModelChange(changes) in your directive and adding your code there to react to changes should do what you want.
Ups " Implementing ngModelChange(changes)" should be ngOnChanges(changes)
10

try add to your directive:

@HostListener('input') onInput() {
        console.log('on input');
}

2 Comments

This is the only answer that actually answers the question/problem. I had the same problem, but have been testing it with a setTimeout. I've tried roughly 5 different approaches (including the other answers in here) with no success, but this is the what Angular Material 2 library uses as well to workaround this pseudo bug. Thank you!! I should point out that I'm on Angular 4.1, though I believe this should work for Angular 2+.
this worked for me in angular 6. @HostListener('input', ['$event']) onInput(event) { console.log(event.target.value); }
6

There are multiple ways to detect changes, it also depends on the ChangeDetectionStrategy defined in the Component.

To be helpful, I'll simplify the answer and focus on the Reactive Extensions (Rx) way of interacting with the ngModel directive, simply because it super powerful and has the best integration with angular 2 change detection engine, performance wise.

First, we need to emphasise that NgModel is bound to the value of the Element and all changes are propagated via an EventEmitter which you subscribe to. The EventEmitter is an Angular2 type that is a Rx Subject which is basically an object that can "transmit" (emit) events/data and also can react to data "received". So in Rx land its an Observable and an Observer.

Also worth mentioning is the "ngOnChanges" method on ngModel that receives handler for handling change events, but we will focus on the Rx approach.

So, we need to gain access to that Observable and subscribe to it, great!

To gain access we use dependancy injection, simply by declaring that we want to get NgModel in the constructor of our directive.

Now we also need to be careful, we need to make sure we do get that NgModel otherwise we will have errors.

We can do it silently, by creating a directive selector that enforces an NgModel element, like: 'selector': '[myCustomDirective][ngModel]', now if ngModel is not part of the element it won't match our directive's selector.

Or, we can make noise and not go out silently, we mark the dependency injection as @Optional and if its null we can throw a clear exception saying whats missing. If we won't mark it as @Optional angular will throw a generic message about a missing dependency, its ok but not good.

Now, an example:

import {Directive, Optional} from 'angular2/core';
import {NgModel}from 'angular2/common';

@Directive({
  selector: '[myCustomDirective][ngModel]'
})
export class MyCustomDirective {
  constructor(@Optional() public model: NgModel) {
    if (!model)
      throw new Error ("myCustomDirective requires ngModel.");

    // this code should be in a designated functions...

    // register to incoming changes to the model
    // we can also transform values..., for example take only distinct values...
    // this.model.update.distinctUntilChanged()...
    let subscription = this.model.update.subscribe((newValue) => {
      // Changes happen here, do something with them... 
      // note that the value of this.model is still the old value
      // The local parameter "newValue" holds the new value.
    });

    // We can also change the value of the model:
    this.model.update.emit('something...');
  }
}

Hope that helped.

5 Comments

does that mean it only matches if ngModel is defined on the textarea? how do you account for both cases.
It needs an ngModel defined as an attribute in the DOM element used for your custom directive.
The question is about changes in the model, when ngModel exists. If you want to listen to changes using an element without ngModel or ngControl you need to register handlers to the Dom events, such as change, keyup etc... RxJs provides helpers for this, such as Observable.fromEvent... ngModel does it for you, but it can be done without it... just more work
If you don't want observables, use <textarea (change)="onChange($event) /> This is same as creating an event handler in code but with the benefit of being worry free about destruction. Note, this is the good old "callback" approach, you won't gain any Rx benefits here.
Hi Shlomi, this subscribe only seems to fire if I am typing inside the textarea, it does not fire if the model is changed from another textarea using the same model
4

There are different approaches to detect changes in Agnular2, and according to the conditions you're using them, they will have upside and downsides. Angular lets you bind all the standard events to elements including change. so in this case as a form element you can simply use:

<textarea my-custom-directive class="green" [(ngModel)]="customertext" (change)="doSomething($event)"></textarea>

The event will be native JavaScript event, so you can get the originalEvent, target, and also to get the change property and its value:

doSomething (e) {
    let whatChanged = e.target.getAttribute('ng-reflect-name');
    let newValue = this[whatChanged];
    console.log(whatChanged, newValue);
}

This works well specially when you want to watch for changes in all/some of elements in a form. using hash-referencing is also pretty straight forward is this approach.

Comments

2

Pass the model(detecting change on) as Input() to directive and use ngOnChange() like below.

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

@Directive({
    selector: '[myCustomDirective]'
})
export class MyCustomDirective implements OnChanges {

    @Input() myCustomDirective: any;

    constructor(private el: ElementRef) {}

    ngOnChanges(changes) {
        if (changes.myCustomDirective) {
            console.log('chnaged');
        }
    }

}

Here is how you can pass the model as input to the directive:

<textarea [myCustomDirective]="customertext" class="green" [(ngModel)]="customertext"></textarea>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.