AngularJs指令:如何在ng-repeat元素上动态设置属性

时间:2022-10-22 08:10:46

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'?

我对AngularJS比较陌生。冒险进入指令创建时,我可以解决这个问题:当这些子元素动态添加'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.

事实证明,我的代码永远不会进入此angular.forEach循环,因为listItems为空。我想这是因为ng-repeat正在等待scope.playlists通过$ 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;

在指令定义中,我添加了一个布尔变量,用于检查元素属性中是否存在'add':变量add ='add'在attrs中?真假;

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!

虽然它工作正常,但显然不是DRY。帮帮我!

2 个解决方案

#1


2  

Instead of removing attributes, change your click handler.

而不是删除属性,更改您的单击处理程序。

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

将$ event添加到参数列表中并有条件地使用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.

不添加时,函数返回并获取href。否则,将阻止默认值,并且单击处理程序执行添加操作。

From the Docs:

来自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.

像ngClick和ngFocus这样的指令在该表达式的范围内公开$ event对象。当jQuery存在或类似的jqLit​​e对象时,该对象是jQuery事件对象的实例。

-- AngularJS Developer Guide -- $event

- AngularJS开发人员指南 - $ event

#2


2  

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:

你试图做事的方式可能不是最有角度的(Angularist?Angularyist?)方式。当您在此处尝试使用angular.element()选择子元素时,可以确保子元素已准备好,如下所示:

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.

但是,这不太适合您的情况,正如@charlietfl在下面指出的那样。如果你想避免你已经拥有的解决方案(我认为这比你的第一次尝试更好),你将不得不重新实现你的代码。

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.

我建议使用指令定义对象的require属性定义一个与其父指令通信的附加指令。新指令可以访问父指令的add属性(在父指令的控制器中为this.add),并且可以编程为相应的行为。该解决方案的实施超出了本答案的范围。

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:

该示例使用Angular 1中的新功能:组件。您可以在此处阅读有关可注射模板和组件的更多信息。从本质上讲,组件允许您使用可以访问元素及其属性的函数来定义模板,如下所示:

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
      };
    }]
  });

#1


2  

Instead of removing attributes, change your click handler.

而不是删除属性,更改您的单击处理程序。

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

将$ event添加到参数列表中并有条件地使用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.

不添加时,函数返回并获取href。否则,将阻止默认值,并且单击处理程序执行添加操作。

From the Docs:

来自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.

像ngClick和ngFocus这样的指令在该表达式的范围内公开$ event对象。当jQuery存在或类似的jqLit​​e对象时,该对象是jQuery事件对象的实例。

-- AngularJS Developer Guide -- $event

- AngularJS开发人员指南 - $ event

#2


2  

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:

你试图做事的方式可能不是最有角度的(Angularist?Angularyist?)方式。当您在此处尝试使用angular.element()选择子元素时,可以确保子元素已准备好,如下所示:

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.

但是,这不太适合您的情况,正如@charlietfl在下面指出的那样。如果你想避免你已经拥有的解决方案(我认为这比你的第一次尝试更好),你将不得不重新实现你的代码。

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.

我建议使用指令定义对象的require属性定义一个与其父指令通信的附加指令。新指令可以访问父指令的add属性(在父指令的控制器中为this.add),并且可以编程为相应的行为。该解决方案的实施超出了本答案的范围。

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:

该示例使用Angular 1中的新功能:组件。您可以在此处阅读有关可注射模板和组件的更多信息。从本质上讲,组件允许您使用可以访问元素及其属性的函数来定义模板,如下所示:

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
      };
    }]
  });