在angularjs中创建一个容器指令

时间:2014-01-22 18:23:43

标签: javascript angularjs angularjs-directive

所以我正在尝试创建一个指令来布局列中的项集合。 在plunker我有一个非常简化的版本,只使用一个ul,但这并不重要。我想要将指令调用为。

<my-column-layout collection="names">
    <tab name="{{ item }}"></tab>
</my-column-layout>

我想使用内部html(此处的选项卡)作为集合中每个项目的模板。我试着去 在my-column-layout模板中有一个ng-repeat,比如

template : '<ul><li ng-repeat="item in collection" ng-transclude></li></ul>

虽然有效,但它没有访问包含控制器的范围,所以我无法在选项卡上有任何点击事件,并让它调用控制器中的一个函数。 所以我认为我正在朝着正确的方向前进,但是不确定。此外,当我尝试在名称集合中添加其他名称时,这不会出现在我的指令集合中。我的范围。$ watch('collection'...)永远不会被调用。

http://plnkr.co/edit/4vyZDAhBcbULEd3uIznh?p=preview

希望有人可以提供帮助

3 个答案:

答案 0 :(得分:1)

我做的事情与我认为相似。如果我错过了这一点,请告诉我。我有一个基于远程数据执行转换ng-repeat的指令。这是它的工作原理。

<强>更新

这是页面标记中的模板问题。但是,如果您希望ng-repeat模板存在于同一页面标记上,则可以执行以下操作:

<script type="text/ng-template" id="navbar.html">
    <li ng-repeat="item in items" ng-class="{active: item.selected}">
        <a href="/{{item.link}}">{{item.title}}</a>
    </li>
</script>

不完全相同的东西,但它得到的效果相同 - 与指令在同一页面上的模板 - 只是没有与它嵌套。

更新结束

我在父级和子级范围内有相同的数组:即$scope.items。因为它是引用类型,所以通过原型继承,两个作用域都引用同一个对象。在不更新属性的位置,我像这样$scope.items = $scope.items || [];初始化它 - 即如果属性尚未初始化,则初始化它,否则保留它。

directive('navbar', ['$location', '$http',  function ($location, $http) {
    return {
        restrict: 'E',
        transclude: true,
        scope: { heading: '@'},
        controller: 'NavbarCtrl',
        templateUrl: 'navbar.html',
        replace: true,
        link: function ($scope, $element, $attrs, navbarCtrl) {

            $scope.items = [];
            $scope.heading = $scope.heading || $attrs.heading;

            $http.get(itemsUrl).success(function(data) {
                $scope.items = ... async get of data ... ;
                navbarCtrl.selectByUrl($location.absUrl());
            });

            $scope.$watch('$location.absUrl()', function (locationPath) {
                navbarCtrl.selectByUrl(locationPath)
            });
        }
    }
}])

该指令的$ watch调用一个控制器函数,该函数可以通过其闭包访问控制器$ scope。

function NavbarCtrl($scope, $timeout, $http, $location, $attrs) {
    $scope.items = $scope.items || [];

    this.select = $scope.select = function (item) {
        angular.forEach($scope.items, function (item) {
            item.selected = false;
        });
        item.selected = true;
    };

    this.selectByUrl = function (url) {
        angular.forEach($scope.items, function (item) {
            if ('http://' + item.link === url) {
                $scope.select(item);
            }
        });
    };
}

然后,在我转发的模板中,我有:

<li ng-repeat="item in items" ng-class="{active: item.selected}">
    <a href="/{{item.link}}">{{item.title}}</a>
</li>

在页面标记中,我使用它:

<div ng-controller="NavbarCtrl">
    <navbar heading="Navbar Heading"/>
</div>

答案 1 :(得分:1)

自定义中继器......

构建自定义转发器是一项复杂的任务。主要是因为性能问题,但也因为它应该与其他指令一起使用。

如果你真的需要构建一个,你必须先深入研究ngRepeat源代码,了解一些注意事项,然后根据自己的需要进行变更。

ngRepeat现在使用 $ watchCollection 自1.2 )取代了深度$ watch 值。


解决方案

所以我的建议是不要构建自定义转发器,请使用ngRepeat

我仍然不知道你想要实现什么,但这就是构造。

这是一个掠夺者:http://plnkr.co/edit/pziqRzz0i1mU6eG5lAmd?p=preview

创建另一个自定义指令,而不是ngTransclude

app.directive('myTransclude',function(){
  return {
    require: "^myColumnLayout",
    link: function(scope,elm,attr,ctrl,$transclude){
      $transclude(function(clone){
        elm.empty();
        elm.append(clone);
        ctrl.do("Hi")
      })
    }
  }
});

使用ng-repeating my-transclude 和控制器创建您的指令:

app.directive('myColumnLayout', function() {
  return {
    restrict: 'EA',
    transclude: true,
    controller: function(){
      this.do = function(x) {
        console.log(x)
      }
    },
    template: '<ul><li ng-repeat="item in collection track by $index" my-transclude></li></ul>',
    scope: {
      collection: '='
    }
  }
});

为什么?

  • 现在您可以完全控制my-transclude
  • 中的转换阶段
  • 您可以在控制器上定义需要分享到已转换内容的任何内容。
  • 重复一些事情,你不会沾沾自喜。

答案 2 :(得分:0)

我不清楚你究竟做了什么,但我在你的指令中修正了如下错误。

  1. 在重新创建之前清除elem孩子。
  2. 注意$ watch函数的第三个参数。观看收藏是必要的。

    app.directive('myColumnLayout', function () {
        return {
            restrict: 'EA',
            transclude: true,
            scope: {
                collection: '='
            },
            link: function (scope, elem, attrs, container, transclude) {
                scope.$watch('collection', function (newVal, oldVal) {
                    elem.empty();
                    for (var i = 0; i < newVal.length; i++) {
                        var li = angular.element('<li></li>');
                        var scp = scope.$parent.$new();
                        scp.item = newVal[i];
                        transclude(scp, function (clone) {
                            elem.append(clone);
                        });
                    }
                }, true);
            }
        }
    });