Skip to content Skip to sidebar Skip to footer

Binding NgModel To A Custom Directive

So I have been working on this issue for a week now and i cannot seem to get my head around this whole Directive thing. I have read lots of posts ... Demystifying Directives Direc

Solution 1:

Directives are not the easiest concepts out there and documentation is really not that good and it's scattered around the interwebs.

I struggled with compile, pre-compile and such when I tried to write my first directives but to date I have never needed those functions. It might be due to my lack of understanding but still...

Looking at your examples I see there's some basic things that needs clarification. First of all, I'd restrict your directive to Element since it's replacing the control in HTML. I'd use Attribute e.g. to add functionality to existing control.

There is a (mandatory) naming convention where you use dashed naming in HTML and camel casing inside your JavaScript. So something-cool becomes somethingCool. When you "bind" variables to directive's scope, there's a major difference on how you do it. Using = you bind to variable, using @ to variables evaluated (string) value. So first allows the "two-way binding" but latter of course, not. You can also use & to bind to parent scope's expression/function.

If you use e.g. plain = then directive's scope expects same name in your HTML. If you wish to use different name, then you add variable name after the =. An example

ngModel : '='        // <div ng-model="data"></div>
otherVar: '@someVar' // <div some-var="data></div> or <some-var="data"></some-var>

 

I took liberty to take your first Fiddle of metro-input-transform as starting point and rewrite it in Plunker. I'm trying to explain it here (and hope I understood you right).

Metro input directive

directives.directive('metroInput', function () {
  return {
    restrict: 'E',
    scope: {
      ngModel: '=',
      placeholder: '@watermark'
    },
    link: function (scope) {
      scope.clear = function () {
        scope.ngModel = null; 
      };
    },
    templateUrl: 'metro-template.html'
  };
});

Directive expects ngModel to bind to and watermark to show when ngModel has no value (text input is empty). Inside link I've introduced clear() function that is used within directive to reset ngModel. When value is reset, watermark is show. I have separated the HTML parts into a separate file, metro-template.html.

Metro input HTML template

<input type="text" ng-model="ngModel" placeholder="{{ placeholder }}">
<button type="button" class="btn-clear" ng-click="clear()">x</button>

Here we bind ngModel to input and assign placeholder. Button showing [X] is bound to clear() method.

Now when we have our directive set up, here's the HTML page using it.

HTML page

<body>
  <div ng-controller="Ctrl">
    <section>
      The 'Product name' textbox in the 'Directive' 
      fieldset and the textbox in the 'Controls'<br>
      fieldset should all be in sync. 
    </section>

    <br>

    <fieldset>
      <legend>Directive</legend>
      <label for="productName">Product name</label>
      <br>
      <metro-input name="productName" 
                   ng-model="data.productName"
                   watermark="product name">
      </metro-input>
    </fieldset>

    <br>

    <fieldset>
      <legend>Control</legend>
      <input detect-mouse-over
             type="text" 
             ng-model="data.productName">
    </fieldset>
  </div>
</body>

So in above example usage of metro directive is as follows. This will be replaced with directive's HTML template.

<metro-input name="productName" 
             ng-model="data.productName" 
             watermark="product name">
</metro-input>

The other input has detect-mouse-over directive applied to it, restricted to Attribute just to show usages/differences between A and E. Mouse detection directive makes input change background-color when mouse is moved over/out of it.

<input detect-mouse-over
       type="text" 
       ng-model="data.productName">

.

directives.directive('detectMouseOver', function () { 
  return {
    link: function (scope, element, attrs) {
      element.bind('mouseenter', function () {
        element.css('background-color', '#eeeeee');
      });
      element.bind('mouseleave', function () {
        element.css('background-color', 'white'); 
      });
    }
  };
});

It also has same ng-model to mirror changes between controls.

In your example you also had a productService that provided the value to above input controls. I rewrote it as

Product service

app.service('productService', function () {
  return {
    get: function () {
      return { productName: 'initial value from service' };
    }
  };
});

So get() function just gets the hard coded value but it still demonstrates use of services. Controller, named Ctrl is really simplistic. Important part here is that you remember to inject all services and such into your controller. In this case angular's $scope and our own productService.

Controller

app.controller('Ctrl', function ($scope, productService) {
  $scope.data = productService.get();
});

 

Here a screen capture of above solution.

imgur

Changing value in any of the inputs changes value of both. Input below has "mouseover" so it's greyish, mouseout would turn it white again. Pressing [X] clears the value and makes placeholder visible.

Here's the link to plunker once more http://plnkr.co/edit/GGGxp0


Solution 2:

Ok I'm not exactly sure what other advantages from the Metro UI you're getting, but here's a simple fiddle that doesn't need your directive at all to capture what you had in your first fiddle that works for me. http://jsfiddle.net/f0sph1vp/7/

<input placeholder="{{page.placeholder}}"
       ng-model="page.data.productName"  
       ng-focus="page.data.productName=''">
<button ng-click="page.data.productName=''">x</button>

The second fiddle you posted, http://jsfiddle.net/gary_stenstrom/xcx2y8uk/64/, is pretty weird to me, because it doesn't seem like you want the second input box to be the same model as your first one. It kind of seems like you want the clicking of the x button to assign the value of the first input to the second. Which makes a lot more sense.

<input ng-model="data.first">
<button ng-click="data.second = data.first; data.first=''">X</button
<input ng-model="data.second">

Post a Comment for "Binding NgModel To A Custom Directive"