Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way to wrap a source element from a Directive?

I'm writing a Directive (using AngularJS 1.2.0-rc2, if that matters) that adds a "clear" button (think of the "x" button that the browser adds for <input type="search"> elements) to an <input>.

As the "clear" button is injected from the Directive and is positioned absolutely, I'd like to contain both the existing <input> and the newly added "clear" button in a wrapper element that has position: relative to ensure proper positioning of the "clear" button.

The declared HTML looks like this:

<input type="text" id="myField" data-ng-model="someModel" data-search>

The Directive I have so far:

angular.module('myApp', []).directive('search', function() {
  var
    clear = angular.element('<a href role="button" class="btn x" style="display:none">&times;</a>'),
    wrapper = angular.element('<div style="position: relative"></div>');

  return {
    restrict: 'A',
    link: function( scope, element, attrs ) {
      element.wrap(wrapper.append(clear));

      // more code that's not relevant to my question
    }
  };
});

The HTML I end up with is

<div style="position: relative">
  <a href role="button" class="btn x" style="display: none">&times;</a>
  <input type="text" id="myField" data-ng-model="someModel" data-search>
</div>

which is what I'm after, but I'd like to ideally template the two new elements and abstract the DOM manipulation out of my link function.

I feel like there's probably a better way to go about this using either or both replace/template and transclude options, but I'm not sure how to retain and use the source element (with all of its attributes and data bindings) with either of those options.

Also note that even though my sample has an ng-model defined on the source element, that should be considered optional. I'd also like to keep the Directive restricted to attributes (restrict: 'A').

like image 494
André Dion Avatar asked Oct 11 '13 14:10

André Dion


Video Answer


2 Answers

Transclusion is probably what you're looking for. Here is some updated code.

Template:

<div style="position: relative">
  <a href role="button" class="btn x" style="display: none">&times;</a>
  <div ng-transclude></div>
</div>

Directive:

angular.module('myApp', []).directive('search', function() {
  return {
    restrict: 'A',
    replace: true,
    transclude: true,
    // template: '', // string template of the HTML above, or better yet
    templateUrl: '', // relative URL of an HTML file containing the above template
  };
});

HTML:

<div data-search>
  <input type="text" id="myField" data-ng-model="someModel">
</div>

You should only use one of either template or templateUrl in the directive, I would lean towards templateURL because I hate string templates.

You can still include a link function if you need more logic.

The replace property will replace the existing DOM element you apply the element to entirely, while leaving this property out will preserve the element and put your template inside of it. Up to you how you would want this configured.

Thinking about it some more, if you did this:

transclude: 'element'

You could just do this in your HTML:

<input type="text" id="myField" data-ng-model="someModel" data-search>
like image 113
Adam Avatar answered Oct 13 '22 01:10

Adam


In my opinion, a better way to design a reusable component is isolate scope and directive controller. In your case, I don't see your logic, so I will make it simple using just isolate scope.

Your template file:

<div style="position: relative">
  <a href role="button" class="btn x" style="display: none">&times;</a>
  <input type="text" id="myField" data-ng-model="model">
</div>

JS:

angular.module('myApp', []).directive('search', function() {

  return {
    restrict: 'E',
    scope:{
      model:"=model"
    },
    link: function( scope, element, attrs ) {

      // more code that's not relevant to my question
    },
    templateUrl:url-to-your-template
  };
});

Use it:

<search model="someModel"/>

Or just:

<search />

If you need to use it as an attribute, try:

JS:

angular.module('myApp', []).directive('search', function() {

  return {
    restrict: 'A',
    scope:{
      model:"=model"
    },
    replace:true,
    link: function( scope, element, attrs ) {

      // more code that's not relevant to my question
    },
    templateUrl:url-to-your-template
  };
});

Use it:

<div search model="someModel"/>

or just:

<div search />
like image 36
Khanh TO Avatar answered Oct 13 '22 01:10

Khanh TO