最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular 2 doesn't update input value modified by third-party libraries - Stack Overflow

programmeradmin1浏览0评论

I have a simple form with its input field associated to a directive:

<form id="scrollable-dropdown-menu">
  <input class="input-field" name="value" [someDirective] type="text"  [(ngModel)]="value" #query="ngModel" />
  <button (click)="onPost(query.value)" type="submit">Search</button>
</form>

The directive changes the input field through the use of a third party library. In my particular case, this is an autoplete/typeahead Jquery plugin. This plugin provides options to the user and after selecting an option, it changes the value of the input field.

However, Angular doesn't update its property query.value and therefore, passes the old value to the onPost method.

The directive looks something like this:

@Directive({
    selector: '[someDirective]',
})
export class MyDirective  {
     constructor(private elRef: ElementRef, private renderer: Renderer) {
         // this changes the value of the field if the user selects an option
         $('.input-field').plugin(); 
     }

}

I was suggested to use UpdateValue, but I can't see how to use it inside a directive. That led me to look at @ViewChild, but it doesn't seem to work in directives, although I could be mistaken.

I also tried to force an update by injecting ChangeDetectorRef, but I didn't see any difference. This is what I did:

my.directive.ts

import {ChangeDetectorRef} from '@angular/core';
@Directive({
    selector: '[someDirective]',
})
export class MyDirective  {
    constructor(private elRef: ElementRef, private renderer: Renderer, private detectorRef: ChangeDetectorRef) {
       $('.input-field').plugin(); 

       $('.input-field').my-plugin(':selected', ()=>{ 
          // do something... 
          this.detectorRef.detectChanges();
       })
    }



}

AfterSelection is triggered when the user selects an option from the autoplete plugin. For the plugin, it looks a bit different because it binds to some event, but I think this illustrates the idea.

I would appreciate any suggestions.

UPDATE:

This is a plnkr to show the main issue using the typeahead.js library. If you write a letter in the input field, it will show you some options. Select one of them and the value of the input field will change accordingly. However, when you submit the form (clicking on the input field or pressing enter), an alert will show that the value that was passed to the onPost function was the old value of the field and not the autopleted value you selected.

I have a simple form with its input field associated to a directive:

<form id="scrollable-dropdown-menu">
  <input class="input-field" name="value" [someDirective] type="text"  [(ngModel)]="value" #query="ngModel" />
  <button (click)="onPost(query.value)" type="submit">Search</button>
</form>

The directive changes the input field through the use of a third party library. In my particular case, this is an autoplete/typeahead Jquery plugin. This plugin provides options to the user and after selecting an option, it changes the value of the input field.

However, Angular doesn't update its property query.value and therefore, passes the old value to the onPost method.

The directive looks something like this:

@Directive({
    selector: '[someDirective]',
})
export class MyDirective  {
     constructor(private elRef: ElementRef, private renderer: Renderer) {
         // this changes the value of the field if the user selects an option
         $('.input-field').plugin(); 
     }

}

I was suggested to use UpdateValue, but I can't see how to use it inside a directive. That led me to look at @ViewChild, but it doesn't seem to work in directives, although I could be mistaken.

I also tried to force an update by injecting ChangeDetectorRef, but I didn't see any difference. This is what I did:

my.directive.ts

import {ChangeDetectorRef} from '@angular/core';
@Directive({
    selector: '[someDirective]',
})
export class MyDirective  {
    constructor(private elRef: ElementRef, private renderer: Renderer, private detectorRef: ChangeDetectorRef) {
       $('.input-field').plugin(); 

       $('.input-field').my-plugin(':selected', ()=>{ 
          // do something... 
          this.detectorRef.detectChanges();
       })
    }



}

AfterSelection is triggered when the user selects an option from the autoplete plugin. For the plugin, it looks a bit different because it binds to some event, but I think this illustrates the idea.

I would appreciate any suggestions.

UPDATE:

This is a plnkr to show the main issue using the typeahead.js library. If you write a letter in the input field, it will show you some options. Select one of them and the value of the input field will change accordingly. However, when you submit the form (clicking on the input field or pressing enter), an alert will show that the value that was passed to the onPost function was the old value of the field and not the autopleted value you selected.

Share Improve this question edited Jul 14, 2016 at 16:18 r_31415 asked Jul 10, 2016 at 6:35 r_31415r_31415 8,98018 gold badges80 silver badges123 bronze badges 12
  • How do you use ChangeDetectRef? show it here. – micronyks Commented Jul 10, 2016 at 7:02
  • I will update my question. – r_31415 Commented Jul 10, 2016 at 7:05
  • In someDirective where do you put $('.input-field').plugin(); after updating your question? – micronyks Commented Jul 10, 2016 at 7:19
  • Oh, sorry. Now it looks like I have two different directives. No, both snippets are the same directive. The part that includes detectChanges is after the $('.input-field').plugin() because the plugin needs to be instantiated and then it can detect events (e.g. AfterSelection in my example). I will correct it to avoid confusion. – r_31415 Commented Jul 10, 2016 at 7:23
  • I think now both snippets reflect better what I have. Notice that both calls (plugin and AfterSelection are inside the constructor). I'm not sure if that's a problem. – r_31415 Commented Jul 10, 2016 at 7:32
 |  Show 7 more ments

5 Answers 5

Reset to default 1

Provisional solution: Demo. Passing the form to the custom directive and using updateValue to manually change the value:

my.ponent.ts

<form *ngIf="active" #frm="ngForm">
    <input name="value" [myDirective]="frm" type="text" [(ngModel)]="value" #query="ngModel" />
    <button (click)="onPost(query.value)" type="submit">Search</button>
</form>

my.directive.ts

@Directive({
    selector: '[myDirective]',
})
export class MyDirective  {

    @Input('myDirectivev') query;
... 
$(this.elRef.nativeElement).my-plugin(':selected', (ev, suggestion) => {
        this.query.controls['value'].updateValue(suggestion);
      });

This works, but let me know if there is a standard approach to solve this kind of issue.

I was told about another solution on Gitter. Unfortunately, I can't remember who told me about it and Gitter's search functionality doesn't help me. Since this person hasn't posted his solution, I will share it for the benefit of people who might be interested.

The idea is basically to pass the whole field as an argument to the onPost(input) method and access the value property of the input field (input.value).

@Component({
  selector: 'my-app',
  template: `
  <form id="scrollable-dropdown-menu" class="search-form">
      <input name="value" class="form-control typeahead" typeahead type="text" data-provide="typeahead" [(ngModel)]="thing" #query />
      <button (click)="onPost(query)" class="btn btn-default search-button" type="submit">Search</button>
  </form>
  `,
  directives: [TypeaheadDirective],
  providers: []
})
export class AppComponent { 
  onPost(input) {
    alert(`Your current value is ${input.value}`);
  }
}

Notice the [(ngModel)]="thing" and #query in the input field. I'm not entirely sure why this updates its value correctly and the native attempt doesn't, but this is far simpler than other alternatives. Here you can find a demo.

Instead of

this.detectorRef.detectChanges();

use

$('.input-field').trigger('input');

ControlValueAccessor used by ngModel listens to input events for normal input fields.

Günter Zöchbauer is right. You should dispatch 'input' event to update angular's model. But JQuery trigger didn't help. Only native event fixed this

@Directive({
    selector: '[myDirective]',
})
export class MyDirective  {

...

  $(this.elRef.nativeElement).my-plugin(':selected', (ev, suggestion) => {
    const inputEvent: any = document.createEvent('CustomEvent');
    inputEvent.initEvent('input', true, true);
    el.dispatchEvent(inputEvent);
  });
}

I think Zone is not aware of third-party variable changes, hence third-party variable changes are not reflected in Angular


You could try to place your jQuery code inside an angular zone, by implementing something like this:

  • import Zone: import {NgZone} from 'angular2/core' or import {NgZone} from '@angular/core' , depending on the Angular 2 version you are using;
  • add zone: NgZone to the constructor arguments of MyDirective;
  • replace $('.input-field').plugin(); by

    zone.run( () => {$('.input-field').plugin(); });
    

Here is a post about Change detection and Zone

Hope this will help you...

发布评论

评论列表(0)

  1. 暂无评论