Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add custom Directive to existing Input that already has angular directives [ng-model/ng-required]

I would like to use a standard input control that is decorated with ng-model and ng-required and then add my own custom attribute directive that provides uib-typeahead functionality to the control.

I used this link to get my directive partly working.

Add directives from directive in AngularJS

PLUNKR - The Version 2 of the directive does not work correctly with ng-model

My Directive does add typeahead functionality and that works quite well, but it is not binding the model on to the control after item is selected.

I have two version of my directive.

Version 1: is an element style directive and I have been using it successfully for a while, but it fell short when I wan't to have a bit more control over the input element, especially when I wanted to use ng-required='true' and other ng-message directives.

Version 2: is an attribute style directive, I went with this because I felt it was better to just add the typeahead functionality that I wanted to any standard HTML that can optionally use ng-required='true', ng-model etc...

While this directive is mostly working, it does not interact correctly with ng-model and I'm not sure how to get it working

angular.module(APP)

.directive('wkLocationSuggest', ['$compile', function ($compile) {
  return {
    restrict: 'A',
    require: 'ngModel',
    replace: false,
    //terminal: true,
    //priority: 0,
    scope: {
      wkApiModel: '=' // Provide access to the internal data that is returned via the API lookup
    },
    controller: 'LocationSuggestController',
    link: function (scope, element, attrs, ngModelCtrl) {
      if (!ngModelCtrl) {
        return;
      }

      element.attr('typeahead', 'location as row.location for row in typeAhead($viewValue)');
      element.attr('typeahead-wait-ms', '750');
      element.attr('typeahead-on-select', 'onSelectInternal($item, $model, $label)');
      element.attr('typeahead-min-length', '2');
      element.attr('typeahead-focus-first', 'true');

      element.removeAttr("wk-location-suggest");        //remove the location-suggest to avoid indefinite loop
      element.removeAttr("data-wk-location-suggest");   //also remove the same attribute with data- prefix if it exists

      // None of this is working
      //// invoked when model changes from the outside
      //ngModelCtrl.$render = function () {
      //  //scope.innerModel = ngModelCtrl.$modelValue;
      //};

      ////// invoked when model changes from the inside
      //scope.onChange = function (value) {
      //  ngModelCtrl.$setViewValue(scope.innerModel);
      //};

      scope.onSelectInternal = function ($item, $model, $label) {

        // This fires, but it effects the ng-model on the first input, 
        // but not the input that this directive is attached too
        ngModelCtrl.$setViewValue($item.location);

      };

      $compile(element)(scope);

    }
  };
}]);

These two images demonstrate part of the problem, may be better to test for yourself using PLUNKR above

Version 1 & 2 of the directive in action

Version 2 not working correctly

like image 806
David Cruwys Avatar asked May 26 '16 07:05

David Cruwys


1 Answers

I initially tried to dynamically add validators to your wk-location-suggest-new directive by implementing blur on the input element in combination with ngModel's $setValidity method; but don't know what exactly was preventing the event from firing.

Therefore, I turned to the other directive wk-location-suggest-old and tweaked it a bit to fit in both desired behaviors.

There, I noticed that you were missing a couple of things:

  • First of all, in order for a form element to glue with the form itself (wkProfileCompany in your case), and to work with ng-model, the element (in the directive template) needs a name.
  • Secondly, ng-required (or required) would work with the form only if it is added as an attribute to the element in the directive template, not the directive which compiles to the template containing the element.

Directive Definition

As you may notice, I've passed two properties from the outer scope to the directive's inner scope, namely:

  • the name of the input element,
  • and an isRequired flag as to specify whether the input is required or not.

.

.directive('wkLocationSuggestOld', [function () {
  return {
    restrict: 'E',
    require: '?ngModel',
    scope: {
      name: '@',      // <==
      isRequired: '=' // <==
    },
    template: '<input name="{{name}}" type="text" class="{{innerClass}}" ng-model="innerModel"'
       + ' ng-change="onChange()" uib-typeahead="location as row.location for row in typeAhead($viewValue)" '
       + ' typeahead-wait-ms="750" typeahead-on-select="onSelectInternal($item, $model, $label)" '
       + ' typeahead-min-length="2" typeahead-focus-first="true" '
       + ' ng-required="isRequired">',  // <== added ng-required here
    controller: 'LocationSuggestController',
    link: function (scope, element, attrs, ngModel) {
      if (!ngModel) {
          return;
      }          
      ...
}])

HTML

Finally, you can use the tweaked directive in your HTML as such:

<wk-location-suggest-old class="form-control" type="text" name="location2" ng-model="location2" is-required="true"></wk-location-suggest-old>

Plunker


Update

One of the possible reasons for ng-model not correctly binding in the wk-location-suggest-new directive to a provided value (i.e. location3) is that you are replacing the whole DOM element with a new custom DOM element which is compiled with the isolated scope of the directive itself.

Since the directive wk-location-suggest-new has an isolate scope, the scope is totally unaware of location3, because location3 (and all the other location values) are defined in the scope of MainCtrl and NOT the scope of the directive itself; therefore, you'll end up binding the input's value to an undefined property.

link: function (scope, element, attrs, ngModelCtrl) {
   if (!ngModelCtrl) {
     return;
   }
 ...
$compile(element)(scope); // <== here
like image 182
Ahmad Baktash Hayeri Avatar answered Oct 15 '22 10:10

Ahmad Baktash Hayeri