3

I'm using bootstrap-autocomplete, but the bound model is not updated when selecting a value.

I looked into the bootstrap-autocomplete source code, and it is using JQuery.val() to assign a selected value, and it looks like if JQuery.val() is used, the model is not updated.

-- template
<input id="test" class="form-control" type="text" autocomplete="off" [(ngModel)]="myValue">
Angular Model: {{myValue}}
<br />
Jquery Val: {{getJqueryValue()}}


-- component
import { Component, AfterViewInit } from '@angular/core';
import 'bootstrap-autocomplete'
declare const $: any;
@Component({
  selector: 'app-app-list',
  templateUrl: './app-list.component.html',
  styleUrls: ['./app-list.component.css']
})
export class AppListComponent implements AfterViewInit {
  public myValue: string;
  ngAfterViewInit(): void {
    $('#test').autoComplete({
      resolver: 'custom',
      events: {
        search: function (qry, callback) {
          callback(["google", "yahoo", "amazon"])
        }
      }
    });
  }
  getJqueryValue() {
    return $('#test').val();
  }
}

enter image description here

How can I make Angular to understand that the value is changed?

FYI, I cannot use Angular material or other angular compatible autocomplete.

https://stackblitz.com/edit/angular-playground-6dpemy?file=app%2Fapp.component.ts

jong shin
  • 682
  • 1
  • 5
  • 19
  • you could use the `(blur)="setAngularModel(getJqueryValue())"` on your `` tag. Also, if you are using angular, you shouldn't be using jquery – rhavelka Sep 15 '20 at 21:01
  • @rhavelka, I know it is not good to use Jquery in Angular in general , but there are some cases you cannot avoid it, and I have my reasons why I'm using Jquery plugins in my project. I just tried your solution, but it does not work, because when blur is called, the value is yet assigned. So, when I type "yah" and click "yahoo", $("#test").val() returns "yah" not "yahoo". – jong shin Sep 15 '20 at 21:21
  • Not sure if it helps. add `private changeRef: ChangeDetectorRef` to your constructor. Then try to use `changeRef.detectChanges()` after a selection. – xDrago Sep 15 '20 at 21:36
  • I tried using changeRef.detectChanges() @xDrago, But it does not update the model. – jong shin Sep 15 '20 at 21:40
  • Try trigering the event by yourself with for example: `$('.yourInput').trigger('input')` and `$('.yourInput').trigger('change')` . Also [check this out](https://stackoverflow.com/questions/17109850/update-angular-model-after-setting-input-value-with-jquery). I think it is basicly the same question with similar solution. – xDrago Sep 15 '20 at 21:49
  • How about if you listen for the `change` event, and when that occurs you override the `myValue` property? `$('#test').on('change', this.myValue = this.getJqueryValue());`. – David Fontes Sep 15 '20 at 22:18
  • Thank you @xDrago, but the problem with that solution is that I'm not assigning value when clicking the list, and I do not know when val() is called, so I don't know when to trigger the event. I could modify the bootstrap-autocomplete source code and trigger the event after calling val() and assinging the value, but I'd like to avoid that. – jong shin Sep 15 '20 at 22:18
  • Unfortunately @DavidFontes, clicking a autocomplete list item does not trigger a change event.. I just tried your solution, but that "this.myValue = this.getJqueryValue()" is never called. – jong shin Sep 15 '20 at 22:25
  • 2
    How about if you change to the event `autocomplete.select` and change the handler to `$('#test').on('autocomplete.select', (ev, item) => this.myValue = item);` would that work? Here is a [fiddle](https://jsfiddle.net/pkw06cq2/3/) without Angular though. – David Fontes Sep 15 '20 at 22:54
  • @DavidFontes that can capture the select event and get the selected value. Great! However, there is a catch. Since I have multiple autocomplete inputs bound to multiple models, I do not know which model to assign the value to. Of course, I can give it an ID value to each autocomplete and compare which ID is associated with which model, but there's got to be a better way. Do you happen to know how to notify Angular to update the binding? FYI, changedetectorref.detectchanges() does not work. – jong shin Sep 16 '20 at 00:26
  • @DavidFontes, or do you happen to know how to capture 'autocomplete.select' event in an Angular way? – jong shin Sep 16 '20 at 00:34
  • I'm not sure I understand your first question, but you don't need to compare the IDs directly, just assign different handlers for each input. Check out this [fiddle](https://jsfiddle.net/7ew3925j/2/). Regarding your second and third question, since change detection is not working (which I don't know why), you could try trigger a change event manually so Angular can catch it and understand that it needs to update the value. Check out this [SO](https://stackoverflow.com/questions/42372110/how-to-trigger-change-in-a-angular-form-by-a-custom-control-without-an-input) for more information. – David Fontes Sep 16 '20 at 09:32
  • Also, if you could share a [MVCE](https://stackoverflow.com/help/minimal-reproducible-example), it would be easier for us to test and find a solution to your problem. You can use [StackBlitz](https://stackblitz.com/) for it. – David Fontes Sep 16 '20 at 09:34
  • Thank you @DavidFontes, I just created StackBlitz. https://stackblitz.com/edit/angular-playground-6dpemy?file=app%2Fapp.component.ts I just tried your link, but it still does not trigger Angular to update the model. – jong shin Sep 16 '20 at 14:16
  • @jongshin: This is what David Fontes suggested too? My posted answer and his comment are same. It works in the demo? – naveen Sep 16 '20 at 14:32
  • @jongshin I looked at your stackblitz and could find a way by changing 2 things. First, change the event from `change` to `input`. Second, you are dispatching the event from your component but you need to dispatch it from the native input HTML element instead. – David Fontes Sep 16 '20 at 15:04
  • 1
    I have added an answer with the solution I mentioned. Nonetheless, either @naveen answers or mine will solve the issue. Good luck :) – David Fontes Sep 16 '20 at 15:19

2 Answers2

3

Using your StackBlitz as a reference I was able to build this solution:

Template

<!--   | Add a template reference so you can grab this element from your component. -->
<!--   v Name it what ever you like. -->
<input #test id="test" class="form-control" type="text" autocomplete="off" [(ngModel)]="myValue">

Component

export class YourComponent implements AfterViewInit {
    public myValue: string;
    @ViewChild('test') test: ElementRef; // <- The name in quotes must match on the template

     ngAfterViewInit(): void {
         $('#test').autoComplete({
             resolver: 'custom',
             events: {
                 search: function (qry, callback) {
                     callback(["google", "yahoo", "amazon"])
                 }
             }
          });
          $('#test').on('autocomplete.select', (ev, item) => {              
              this.test.nativeElement.dispatchEvent(
                  new CustomEvent('input', { bubbles: true })
              );
          });
     }
}

Demo https://stackblitz.com/edit/angular-playground-r3s5b3?file=app/app.component.ts

David Fontes
  • 1,427
  • 2
  • 11
  • 16
1

This will do

$('#test').on('autocomplete.select', (ev, item) => {
  this.myValue = item;
});

Link: https://stackblitz.com/edit/angular-jq-autocomplete-z6bc1r

naveen
  • 53,448
  • 46
  • 161
  • 251