简体   繁体   中英

AngularJs Directive: How to dynamically set attributes on ng-repeat element

I am relatively new to AngularJS. While venturing into directive creation, I can across this problem: How to dynamically add / remove attributes on the children of the directive's element when these children are dynamically added with 'ng-repeat'?

First, I thought of this solution:

template

...
a.list-group-item(ng-repeat='playlist in playlists', ng-click='addToPlaylist(playlist, track)', ng-href='playlist/{{ playlist._id }})
...

*directive

link: function(scope, elm, attrs) {   
  var listItems = angular.element(element[0].getElementsByClassName('list-group-item')
  angular.forEach(listItems, function(item, index) {
    'add' in attrs ? item.removeAttr('href') : item.removeAttr('ng-click');
    listItems[index] = item;
  }
... 

Result

It turns out, my code never enters this angular.forEach loop because listItems is empty. I suppose it's because the ng-repeat is waiting for the scope.playlists to populate with the data from a async call to a server via $resource.

temporary fix

in the directive definition, I added a boolean variable that checks for the presence of 'add' in the element's attributes: var adding = 'add' in attrs ? true : false; var adding = 'add' in attrs ? true : false;

And then in the template,

a.list-group-item(ng-if='adding', ng-repeat='playlist in playlists', ng-click='addToPlaylist(playlist, track)')
a.list-group-item(ng-if='!adding', ng-repeat='playlist in playlists', ng-href='playlist/{{playlist._id }}')

While it works fine, it is obviously not DRY at all. HELP!

The way that you are trying to do things may not be the most Angularish (Angularist? Angularyist?) way. When using angular.element() to select child elements as you are trying to do here, you can make sure the child elements are ready as follows:

link: function(scope, elm, attrs) {
  elm.ready(function() {
    var listItems = angular.element(element[0].getElementsByClassName('list-group-item')
    angular.forEach(listItems, function(item, index) {
      'add' in attrs ? item.removeAttr('href') : item.removeAttr('ng-click');
      listItems[index] = item;
    }
  });
}

However, this is unlikely to work in your situation, as @charlietfl points out below . If you want to avoid the solution you already have (which I think is better than your first attempt), you will have to reimplement your code altogether.

I would suggest defining an additional directive that communicates with its parent directive using the require property of the directive definition object. The new directive would have access to an add property of the parent ( this.add in the parent directive's controller ) and could be programmed to behave accordingly. The implementation of that solution is beyond the scope of this answer.

Update:

I decided to give the implementation something of a shot. The example is highly simplified, but it does what you are trying to do: alter the template of a directive based on the attributed passed to it. See the example here .

The example uses a new feature in Angular 1: components . You can read more about injectable templates and components here . Essentially, components allow you to define templates using a function with access to your element and its attributes, like so:

app.component('playlistComponent', {

    // We can define out template as a function that returns a string:
    template: function($element, $attrs) {
      var action = 'add' in $attrs
        ? 'ng-click="$ctrl.addToPlaylist(playlist, track)"'
        : 'ng-href="playlist/{{playlist._id}}"';

      return '<a class="list-group-item" ng-repeat="playlist in playlists" ' +
        action + '></a>';
    },

    // Components always use controllers rather than scopes
    controller: ['playlistService', function(playlists) {
      this.playlists = playlists;

      this.addToPlaylist = function(playlist, track) {
        // Some logic
      };
    }]
  });

Instead of removing attributes, change your click handler.

Add $event to the list of arguments and conditionally use preventDefault() .

<a ng-click='addToPlaylist($event,playlist)' ng-href='playlist'>CLICK ME</a>

In your controller:

$scope.addToPlaylist = function(event,playlist) {
     if (!$scope.adding) return;
     //otherwise
     event.preventDefault();
     //do add operation
};

When not adding, the function returns and the href is fetched. Otherwise the default is prevented and the click handler does the add operation.

From the Docs:

$event

Directives like ngClick and ngFocus expose a $event object within the scope of that expression. The object is an instance of a jQuery Event Object when jQuery is present or a similar jqLite object.

-- AngularJS Developer Guide -- $event

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM