VSO VSO - 5 days ago 5
Javascript Question

Updating Angular 2 Field Programmatically

I have the following form field which works fine. By that, I mean that when I type, paste, etc into the field, the

fooObj.expDate
gets updated just fine in real time and validation occurs. I have the pre-tag to make this obvious to myself.

<pre>{{fooObj.someDate | json}}</pre>

<div class="form-group inline-form__input">
<label for="someDate">Some Date</label>
<input tabindex="2"
type="tel"
class="form-control"
maxlength="7"
placeholder="MM/YY"
formControlName="someDate"
name="someDate"
[(ngModel)]="fooObj.someDate"
someDate>
</div>


However, I have that
someDate
directive on this field. This directive intercepts paste events. It cancels the paste event, does some fancy formatting of the input and then does this:

setTimeout(() => {
this.target.value = 'lol fancy date';
}, 3000);


target.value
is my
someDate
field. The value gets updated fine inside the input box (I see it change on the screen inside the input). However,
fooObj.someDate
is NOT updated and validation does not occur. E.g. setting target value in a timeout does NOT trigger the same validation/object update as typing a key / pasting / any other javascript event.

Angular docs don't have much useful to say on this:


Angular updates the bindings (and therefore the screen) only if the app does something in response to asynchronous events, such as keystrokes. This example code binds the keyup event to the number 0, the shortest template statement possible. While the statement does nothing useful, it satisfies Angular's requirement so that Angular will update the screen.


So, how do I trigger an update of the field from a directive on that field?

Edit: I tried triggering an event on the element as recommended in the comments by using the code found here on my element: How can I trigger an onchange event manually?

Runs fine, but doesn't force the field to update:

if ("createEvent" in document) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
this.target.dispatchEvent(evt);
}
else
this.target.fireEvent("onchange");


Also, here is where I am getting the idea of synthetic events not triggering "normal" actions as a keyDown or whatever would (I am really hoping I am misreading or they are wrong for this use case, but it didn't work with trying to re-issue a paste event): https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces


NOTE:
Synthetic events do not have default actions. In other words, while the script above will fire a paste event, the data will not actually be pasted into the document.

Answer

I don't know the details of your directive, however I can guess as to your intentions. To start with, we're going to subscribe to the valueChanges observable of our control and eschew the two way binding directly on the control just to avoid excessive writes and checks:

form.html

<input tabindex="2"
             type="tel"
             class="form-control"
             maxlength="7"
             placeholder="MM/YY"
             formControlName="someDate"
             name="someDate"
             someDate />

form.ts

Here is where we subscribe (it can move out of the constructor, depends on when you're making the form).

constructor() {
    this.myForm = new FormGroup({
        someDate: new FormControl(''),
    });

    this.myForm.controls['someDate'].valueChanges.subscribe(
      value => this.fooObj.someDate = value;
      );
  }

some-date.directive.ts

The directive will write the value into the control and the subscription to the valueChanges will then update our model. This works on a paste event and every other event (so you can limit the events targeted, but I wanted to at least make sure pasting worked).

@Directive({
  selector: '[someDate]'
})
export class SomeDateDirective{
  constructor(private el: ElementRef, private control : NgControl) {

  }

  @HostListener('input',['$event']) onEvent($event){
    $event.preventDefault();
    let data = $event.clipboardData.getData('text');
    setTimeout(() => {
      this.control.control.setValue(data.toUpperCase());
    }, 3000);
  }
}

Change to paste instead of input to capture only onpaste events. It's a little weird with preventDefault() as the input effectively disappears for a while.

Here's a plunker: http://plnkr.co/edit/hsisILvtKErBBOXECt8t?p=preview

Comments