Search code examples
javascriptangularjsautocompletemd-autocomplete

access md-autocomplete input value before model debouce evaluation in AngularJs


I have a scenario where I want to use ng-model-options to debounce an input value but I have a specific use case where I need to gain access to the raw input value before the debounce cycle.

Special case, when user hits the enter key, or presses the search button I want to access the value of the input immediately, even if the debounce period has not expired. Turns out in Production users who type in a search query will often hit enter pretty quick, who knew right?

So because the model will not yet have the value I need I have tried to access the html input value directly jQuery:

var searchBox = document.getElementById('searchBox');
var el = angular.element(searchBox);
var val = el.val();

And also in raw javascript:

var searchBox = document.getElementById('searchBox');
var val = searchBo.value;

But these results give me an empty value.

To complicate things, I am using md-autocomplete that has it's own built in functionality that I'd rather not replicate or try to modify too much and I am unsure how to directly target the input field of the directive to get the .val()

Background:

I am trying to implement a search page in AngularJs that has 'best bet' suggestions as well as displaying full text search results.

The autocomplete will call the service for suggestions as the user types, but these suggestions will be exact matches on specific fields in the data model. The full test search will occur

I am using Azure Search API to serve the Query and Suggestions, the implementation of this is out of scope for the question, but a quick plug, it produces super fast responses over large indexes, faster than any SQL I can try to write...

I have used debounce model options to delay the suggest query to the service and this is working nicely, but because of the use of debounce, I cannot access the value of the input via bindings until after the debounce period has expired.

<!-- The Search Box -->
<form ng-submit="$event.preventDefault()">
    <div layout="row">
        <md-autocomplete id="searchBox"
                            ng-disabled="false"
                            md-no-cache="true"
                            md-search-text="vm.searchText"
                            ng-model-options="{ debounce: 300 }"
                            md-selected-item-change="vm.selectedItemChange(item)"
                            md-items="item in vm.querySuggester(vm.searchText)"
                            md-item-text="item.Text"
                            md-min-length="1"
                            placeholder="Search articles..." layout-fill>
            <md-item-template>
                <span md-highlight-text="vm.searchText" md-highlight-flags="i">{{item.Text}}</span>
            </md-item-template>
        </md-autocomplete>
        <button type="submit" ng-click="vm.querySearch(vm.searchText)">
            Search
        </button>
    </div>
</form>

...

<!-- Search Results -->
<md-list class="browser-list">
    <md-list-item ng-repeat="selected in vm.SearchResponse.Results"
                    ng-click="vm.selectItem(selected)">

        <catalogue-Summary ng-if="selected.Document.Catalogue" data="selected.Document" highlights="selected.Highlights" score="selected.Score" entity-name="'dataEntries'" show-in-list="true" class="full-width"></catalogue-Summary>
        <article-Summary ng-if="!selected.Document.Catalogue" data="selected.Document" highlights="selected.Highlights" score="selected.Score" entity-name="'contentEntries'" show-in-list="true" class="full-width"></article-Summary>
        <md-divider ng-if="!$last"></md-divider>
    </md-list-item>
</md-list>

So I want to debounce the value that md-autocomplete uses to delay the suggestions request, while allowing the full text search query that the search button uses to access the raw value immediately


Solution

  • In researching a good way to describe my issue, and add references to my question I found 2 solutions

    1. The md-autocomplete documentation lists md-delay attribute that is explicitly designed for this type of scenario, where you want the model to bind immediately but you want to delay the calling of the search function

      md-delay
      [number]: Specifies the amount of time (in milliseconds) to wait before looking for results

      so we can now remove the model-options debounce statement and use md-delay instead:

      <md-autocomplete id="searchBox"
                          ng-disabled="false"
                          md-no-cache="true"
                          md-search-text="vm.searchText"
                          md-delay="300"
                          md-selected-item-change="vm.selectedItemChange(item)"
                          md-items="item in vm.querySuggester(vm.searchText)"
                          md-item-text="item.Text"
                          md-min-length="1"
                          placeholder="Search articles..." layout-fill>
          <md-item-template>
              <span md-highlight-text="vm.searchText" md-highlight-flags="i">{{item.Text}}</span>
          </md-item-template>
      </md-autocomplete> 
      
    2. You can also directly reference the input value of the above input field by accessing the 2nd descendant of the html element, that is because the above snippet will render out to the following structure at runtime: (I've stripped most attributes from the raw output to explain this point)

      <md-autocomplete>
          <md-autocomplete-wrap>
              <!-- ngIf: !floatingLabel -->
              <input name="" ng-model="$mdAutocompleteCtrl.scope.searchText"></input>
              <!-- end ngIf: !floatingLabel -->
              <!-- ngIf: $mdAutocompleteCtrl.scope.searchText && !$mdAutocompleteCtrl.isDisabled -->
              <!-- ngIf: $mdAutocompleteCtrl.loadingIsVisible() -->
          </md-autocomplete-wrap>
          <aria-status class="md-visually-hidden" role="status" aria-live="assertive"></aria-status>
      </md-autocomplete>
      

      We want to directly access the Input field So the following script will give you access to the value, even if the debounce has meant that the model will not have the value yet:

      var searchInput = document.getElementById('searchBox').firstElementChild.firstElementChild;
      var value = searchInput.value;
      

      Without knowing the full structure of how md-autocomplete renders, but knowing that ultimately we want the inner input control the following would have been a more useful first attempt:

      var searchInput = document.getElementById('searchBox').getElementsByTagName('input')[0];