Angular DOM Manipulation Directive

I got unexpected behavior with DOM manipulation in a directive. I have two icons and I would like icons, phone and email and would like to show their contents when it is frozen. Which should be simple enough, but I'm having trouble moving the DOM around to access child elements.

Here is my directive:

app.directive('contact', function () {
return {
    restrict: 'E',
    scope: {
      email:'@',
      phone: '@',
      extension: '@'
    },
    link: function (scope, element, attrs) {
        var el = element;
        var kids = $(el).children();
        var emailChild = $(kids[0]);
        var email = emailChild.context.children[0];

        element.children().bind('mouseenter', function() {
                console.log(this);
                console.log(emailChild.context.children[0]);
                console.log(email);
            });
    },


    template: '<div class="contact_methods">' +
              '<div ng-if="email" class="email" href="#">' +
              '<div class="contact_info_email more_info" style="display:none;"><a ng-href="mailto:{{email}}">{{email}}</a></div>' +
              '</div>&nbsp;&nbsp;' +
              '<div ng-if="phone" class="phone" href="#"> ' +
              '<div class="contact_info_phone more_info">{{phone}} ext.{{extension}}</div>' +
              '</div>' +
              '</div>'


}

});

      

Here's the unexpected behavior:

console.log(this);
console.log(emailChild.context.children[0]);
console.log(email);

      

They are evaluated as follows:

 <div class=​"contact_methods">​…​</div>​
 <div ng-if=​"email" class=​"email ng-scope" href=​"#">​…​</div>​
 undefined

      

email outputs are undefined; however, this content is the line above it? Also I cannot expand it like element.children (). Children ()? Guidance stops working.

+3


source to share


1 answer


You don't have to do DOM access, you can just use ng-events instead. In your case ng-mouseenter

ng-mouseleave

, combined with ng-show

.

 <div ng-if="email" class="email" href="#" 
      ng-mouseenter="toggleEmail(true)" ng-mouseleave="toggleEmail(false)">Email 
      <div class="contact_info_email more_info" ng-show="displayEmail">
         <a ng-href="mailto:{{email}}">{{email}}</a></div>

      

and in the bind function:

link: function (scope, element, attrs) {
  scope.toggleEmail = function(shouldShow){
    scope.displayEmail = shouldShow;
  }
},

      

Sample demo



angular.module('app', []).controller('ctrl', function($scope) {
  $scope.email = "aaa@aa.com";
  $scope.phone = "111-222-3333";
}).directive('contact', function() {
  return {
    restrict: 'E',
    scope: {
      email: '@',
      phone: '@',
      extension: '@'
    },
    link: function(scope, element, attrs) {
      scope.toggleEmail = function(shouldShow) {
        scope.displayEmail = shouldShow;
      }
    },


    template: '<div class="contact_methods"> \
              <div ng-if="email" class="email" href="#" ng-mouseenter="toggleEmail(true)" ng-mouseleave="toggleEmail(false)">Email \
             <div class="contact_info_email more_info" ng-show="displayEmail"><a ng-href="mailto:{{email}}">{{email}}</a></div> \
             </div>&nbsp;&nbsp; \
             <div ng-if="phone" class="phone" href="#">Phone \
              <div class="contact_info_phone more_info">{{phone}} ext.{{extension}}</div> \
              </div> \
              </div>'


  }

});;
      

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <contact email="{{email}}" phone="{{phone}}"></contact>
</div>
      

Run codeHide result


I would even use a controller for this directive and set up methods and variables there. Also, you can place your template externally instead of the inline template as it is larger and difficult to maintain in a javascript file.

The problem with your approach is that you have the wrong selector here. because the actual root will be the element <contact

, so the children are not what you expect. Also remember that you no longer need to wrap element

with $(element)

as it is already a jquery wrapper element (assuming you have jquery included before angular). So you could do the same (although I would change my mind about doing it this way):

 //Let the directive render DOM first
 $timeout(function() {
    element.find('.email, .phone').bind('mouseenter mouseleave', function() {
      $(this).children().toggle()
    });
  });

      



angular.module('app', []).controller('ctrl', function($scope) {
  $scope.email = "aaa@aa.com";
  $scope.phone = "111-222-3333";
}).directive('contact', function($timeout) {
  return {
    restrict: 'E',
    scope: {
      email: '@',
      phone: '@',
      extension: '@'
    },
    link: function(scope, element, attrs) {
      $timeout(function() {
        element.find('.email, .phone').bind('mouseenter mouseleave', function() {
          $(this).children().toggle()
        });
      });
    },
    template: '<div class="contact_methods">' +
      '<div ng-if="email" class="email" href="#"> Email' +
      '<div class="contact_info_email more_info" style="display:none;"><a ng-href="mailto:{{email}}">{{email}}</a></div>' +
      '</div>&nbsp;&nbsp;' +
      '<div ng-if="phone" class="phone" href="#"> Phone' +
      '<div class="contact_info_phone more_info" style="display:none;">{{phone}} ext.{{extension}}</div>' +
      '</div>' +
      '</div>'
  }

});;
      

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <contact email="{{email}}" phone="{{phone}}"></contact>
</div>
      

Run codeHide result


+5


source







All Articles