1
votes

so I have an Angular UI Typeahead that is working, however I have a use case that I can't get it to cooperate with.

My issue is getting it to display a certain value based not on user-input, which works, but based on the state.

I'm using Angular UI Router, so my code looks like this:

if($stateParams.assetID != undefined)
  {
    $http.get('/getData/idRepo', {
      params: {
        id: $stateParams.assetID
      }
    }).then(function(response){
        $scope.getMaterial(response.data[0].Title);
        //this is where I try and trigger the getMaterial 
        //function to show the Typeahead value based on a url parameter
        //
    });
  }

$scope.getMaterial = function(val) {
    return $http.get($scope.material[0].Query, {
      params: {
        title: val
      }
    }).then(function(response){
        return response.data;
    });
};

My Typeahead HTML is this:

<input type="text" ng-model="selected.asyncSelected"
       placeholder="Material Name..." 
       uib-typeahead="med as med.name for med in getMaterial($viewValue)" 
       typeahead-editable="false"
       typeahead-template-url="meditationssearch.html" 
       typeahead-select-on-exact="true" 
       typeahead-select-on-exact="true" 
       typeahead-loading="loadingLocations" 
       typeahead-no-results="noResults"
       class="form-control">

As you can see, the typeahead reads from getMaterial() which I try to trigger in my if($stateParams.assetID != undefined) conditional.

Expected workflow:

  1. Navigate to this view with a $stateparam for the assetID in the url.
  2. Have the conditional see that assetID param is not undefined, fire the GET request and then have it return the results from the running getMaterial() (the result of the first GET request gets passed through getMaterial's() own GET request - as if the result of the first GET request were being typed in).
  3. Update the model/typeahead to display getMaterial() value in the view.

I tried $scope.$apply() but the digest is already in progress. I'm hoping there is another quick fix that I'm just not aware of to trigger the typeahead.

I also tried something like this:

jQuery("input[uib-typeahead]").val(response.data[0].Title).trigger('input');

...but that isn't a good solution because some of the assets I am type-aheading for have the exact same name, which is why I'm trying to use the id as my url parameter and have the typeahead source function pick up the name as if it was typed in and then return to full object (name/id) to the model.

Please help me achieve my desired workflow.

Edit: Typeahead Source Code:

I believe I want to activate something in the select function:

 scope.select = function(activeIdx, evt) {
      //called from within the $digest() cycle
      var locals = {};
      var model, item;

      selected = true;
      locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
      model = parserResult.modelMapper(originalScope, locals);
      $setModelValue(originalScope, model);
      modelCtrl.$setValidity('editable', true);
      modelCtrl.$setValidity('parse', true);

      onSelectCallback(originalScope, {
        $item: item,
        $model: model,
        $label: parserResult.viewMapper(originalScope, locals),
        $event: evt
      });

      resetMatches();

      //return focus to the input element if a match was selected via a mouse click event
      // use timeout to avoid $rootScope:inprog error
      if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
        $timeout(function() { element[0].focus(); }, 0, false);
      }
    };

Maybe resetMatches()? How do I call that from my own controller?

3
The example code doesn't make sense. What is the URL that the getMaterial function use for its fetch? The code example uses $scope.material[0].Query but does not show how $scope.material is initialized. - georgeawg
@georgeawg it's just a variable URL that changes based on other conditions... by the time I want to run this code $scope.material is already dealt with.. - Summer Developer
$scope.material array conflicts with the iterator that the uib-typeahead directive uses. - georgeawg
@georgeawg Understood, I edited my code and question to change the uib-typeahead iterator. - Summer Developer
@georgeawg Still have the same issue, however. - Summer Developer

3 Answers

0
votes

Instead of having getMaterial($viewValue) in the html (Which will fire every digest cycle...) you can wire up an ng-change instead and that way only fire when the data has actually changed. In that case you have a model $scope.materials now that you can update whenever you want to get the changes.

javascript:

$scope.modelChanged = function(){
    $http.get($scope.material[0].Query, {
        params: {
            title: $scope.selected.asyncSelected
        }
    }).then(function (response) {
        $scope.materials = response.data;
    });
}

if ($stateParams.assetID != undefined) {
    $http.get('/getData/idRepo', {
        params: {
            id: $stateParams.assetID
        }
    }).then(function (response) {
        $scope.selected.asyncSelected = response.data[0].Title;

        $scope.modelChanged(); //not sure if you have to run this it might fire automatically
    });
}

HTML:

<input type="text" ng-model="selected.asyncSelected"
       placeholder="Material Name..." 
       uib-typeahead="material as material.name for material in materials" 
       typeahead-editable="false"
       typeahead-template-url="meditationssearch.html" 
       typeahead-select-on-exact="true" 
       typeahead-select-on-exact="true" 
       typeahead-loading="loadingLocations" 
       typeahead-no-results="noResults"
       ng-change="modelChanged()"
       class="form-control">
0
votes

Use a custom directive:

app.directive("forceInput", function() {
  return {
    require: "ngModel",
    link: postLink
  }
  function postLink(scope, elem, attrs, ngModel) {
    attrs.$observe('forceInput', function(value) {
      if (!value) return;
      elem[0].value = value;
      ngModel.$setViewValue(value);
    })
  }
})

Usage:

<input type="text" ng-model="selected.asyncSelected"
       force-input="{{forceValue}}"
       placeholder="Material Name..." 
       uib-typeahead="med as med.name for med in getMaterial($viewValue)" 
       typeahead-editable="false"
       typeahead-template-url="meditationssearch.html" 
       typeahead-select-on-exact="true" 
       typeahead-select-on-exact="true" 
       typeahead-loading="loadingLocations" 
       typeahead-no-results="noResults"
       class="form-control">

JS

if($stateParams.assetID != undefined)
  {
    $http.get('/getData/idRepo', {
      params: {
        id: $stateParams.assetID
      }
    }).then(function(response){
        ̶$̶s̶c̶o̶p̶e̶.̶g̶e̶t̶M̶a̶t̶e̶r̶i̶a̶l̶(̶r̶e̶s̶p̶o̶n̶s̶e̶.̶d̶a̶t̶a̶[̶0̶]̶.̶T̶i̶t̶l̶e̶)̶;̶
        $scope.forceValue = response.data[0].Title;
        //this is where I try and trigger the getMaterial 
        //function to show the Typeahead value based on a url parameter
        //
    });
  }

The DEMO

angular.module('app', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);
angular.module('app').controller('ctrl', function($scope, $http) {

  var _selected;

  $scope.selected = undefined;
  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California',
                   'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia',
                   'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas',
                   'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 
                   'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 
                   'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico',
                   'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma',
                   'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 
                   'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia',
                   'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
});

angular.module('app').directive("forceInput", function() {
  return {
    require: "ngModel",
    link: postLink
  }
  function postLink(scope, elem, attrs, ngModel) {
    attrs.$observe('forceInput', function(value) {
      if (!value) return;
      elem[0].value = value;
      ngModel.$setViewValue(value);
    })
  }
})
    <script src="//unpkg.com/angular/angular.js"></script>
    <script src="//unpkg.com/angular-animate/angular-animate.js"></script>
    <script src="//unpkg.com/angular-sanitize/angular-sanitize.js"></script>
    <script src="//unpkg.com/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js"></script>
    <link href="//unpkg.com/[email protected]/dist/css/bootstrap.css" rel="stylesheet">

<body ng-app="app">

  <div class='container-fluid typeahead-demo' ng-controller="ctrl">

    <pre>Model: {{selected | json}}</pre>
    <input type="text" ng-model="selected"
           force-input="a"
           uib-typeahead="state for state in states | filter:$viewValue | limitTo:8"
           class="form-control">

 </div>
</body>
-2
votes

Another way to do it is to hook into the Typeahead itself, this way if it has the same name it will pick the right object for the model.

See here:

https://plnkr.co/edit/ABM72yyV4dtp5PsH4bIm?p=preview

angular.module('ui.bootstrap.demo').controller('TypeaheadCtrl', function($scope, $http,uibTypeaheadParser) {

$scope.fire = function(val){
      var locals = {};
      var model;
      var parserResult = uibTypeaheadParser.parse(angular.element("input[uib-typeahead]")[0].getAttribute('uib-typeahead'));
      locals[parserResult.itemName] = val;
    model = parserResult.modelMapper($scope, locals);
    console.log(model);
    $scope.customSelected = model; //$scope.customSelected is the ng-model in this example
  }

  });

Where val is an object, rather than a string.

<button ng-click="fire({
  'name': 'Minnesota',
  'flag': 'b/b9/Flag_of_Minnesota.svg/46px-Flag_of_Minnesota.svg.png'
})" class="button btn-primary">CLICK ME TO SET TYPEAHEAD</button>