AngularJS - 指令,ng-repeat,ng-click和Jquery

时间:2013-07-17 15:46:10

标签: javascript jquery angularjs

尝试使用两个非常简单的自定义指令来使用3d party jQuery库:

无法让ng-click工作,也不确定如何从链接函数中的重复元素中获取数据。

当您点击幻灯片时,它的名称和隐藏数据应该附加在它下面的列表中。

jsfiddle

angular.module('sm', [])
.directive('selector', function () {
return {
    restrict: "E",
    template: '<div class="swiper-wrapper">' +
        '<div class="swiper-slide" ng-repeat="slide in slides">' +
        '<h1 ng-click="selected(slide)">{{ slide.name }}</h1>' +
        '</div></div>',
    replace: true,
    controller: ['$scope', '$timeout', function ($scope, $timeout) {

        $scope.slides = [{
            name: 'one',
            hidden: 'kittens'
        }, {
            name: 'two',
            hidden: 'puppies'
        }, {
            name: 'three',
            hidden: 'bacon'
        }];
        $timeout(function () { // important!
            $.swiper.init();
        });

        // ng-click never fired due to the jQuery slider plugin
        $scope.selected = function (data) {
            console.log('ng-click called $scope.selected');
            $scope.$broadcast('slideSelected', data);
        };
    }],
    link: function linkFn(scope, lElement, attrs) {

        lElement.on('click', function (el) {
            console.log('lElement on click called');
            // how do I get access to the clicked element's data?
            scope.$broadcast('slideSelected', el);
            $
        })
    }
}
})
      .directive('selected', function () {
        return {
            restrict: "E",
            template: '<ul>' +
                '<li ng-repeat="selection in selected">{{ selection }}</li>' +
                '</ul>',
            replace: true,
            controller: ['$scope', function ($scope) {
                var selected = ['Add to me', 'please'];
                $scope.selected = selected;
                $scope.$on('slideSelected', function (data) {
                    $scope.$apply(function () {
                        selected.push(selected);
                    })
                });
            }],
        }
    })
        .controller('MyCtrl', function ($scope) {});

    $.swiper = {
        init: function () {
            var mySwiper = $('.swiper-container').swiper({
                mode: 'horizontal',
                loop: true
            });
        }
    };

2 个答案:

答案 0 :(得分:4)

这里有一些注意事项:

1。如果您没有以这样的方式创建指令,即子指令应该能够require并且可以访问其控制器,那么您可以考虑使用链接函数而不是控制器。 $timeout依赖关系可以移动到指令工厂函数。

2。你的指令正在分享范围;由于没有告诉指令创建一个新的或隔离范围,它们各自的scope.selected属性(一个函数和另一个函数)相互覆盖。

隔离范围可以解决此问题,但是由于不再连接范围,因此您无法执行scope.$broadcast。你的选择是

  1. 在父作用域上广播事件:scope.$parent.$broadcast
  2. $rootScope(所有范围的最终父母)
  3. 上广播活动
  4. 使用共享服务而不是事件广播(这可能是我要做的)
  5. 3。如果你看一下the documentation for Scope#$on,你会发现监听器函数的第一个参数是被触发的事件; 第二个参数将是您发送到$broadcast函数的自定义数据。

    4。在1.1.x版本的Angular中,如果没有添加ng-repeat子句来告诉Angular应该使用哪些数据来确定数据是否真的重复,则不能在track by属性中包含相同的数据。我们在这里使用$index

    <li ng-repeat="selection in selected track by $index">{{ selection }}</li>
    

    解决这些问题会让我们看到这段代码:http://jsfiddle.net/BinaryMuse/hCdJA/;问题是jQuery插件仍在使用ng-click。在Angular中使用第三方jQuery插件时,这类问题并不少见,答案通常是编写一个指令来包装插件的功能。


    经过一番努力,我有一套包含Swiper功能的指令(至少我们关心的一点; Swiper在API方面有相当宽的表面积,所以我没有涵盖所有)一种相当可重用的方式。我很难让setDatagetData正常工作(我怀疑这是插件中的一个错误)所以最后通过常规的data()来电和外部的黑客攻击用于存储回调的对象。

    在我们进入代码之前,您可以在此处看到一个有效的演示:http://jsfiddle.net/BinaryMuse/UruNG/

    这是最终的HTML:

    <div ng-app="sm">
      <div ng-controller="MyCtrl">
        <swiper>
          <slide ng-repeat="slide in slides" ng-click="select(slide)">
            <h1>{{slide.name}}</h1>
          </slide>
        </swiper>
        <ul>
          <li ng-repeat="item in items track by $index">{{item | json}}</li>
        </ul>
      </div>
    </div>
    

    我已将swiperslide元素拆分,以使其可重用且可组合; slide指令使用require属性来获取父swiper指令定义的控制器,以访问它公开的函数。

    这是让它工作的JavaScript:

    angular.module('sm', [])
    .directive('swiper', function($timeout) {
      return {
        restrict: 'EA',
        template: "<div class='swiper-container'>" +
          "<div class='swiper-wrapper'></div>" +
          "<div style='display: none' ng-transclude></div>" +
          "</div>",
        replace: true,
        transclude: true,
        // We use a controller here so the slide directive
        // can require it and call `addSlide`.
        controller: function($element) {
          var newSlides = [];
          var mySwiper = null;
          var slideCount = 0;
          var callbacks = {};
    
          // Attached directly to the controller so other directives
          // have access to it.
          this.addSlide = function(html, callback) {
            if (mySwiper) {
              var newSlide = mySwiper.createSlide(html.html());
              // Hackily save off the callback based on
              // a unique ID since getData() for
              // swiper.clickedSlide doesn't appear to work
              // when using setData() on newSlide.
              newSlide.data('slideNumber', ++slideCount);
              mySwiper.appendSlide(newSlide);
              callbacks[slideCount] = callback;
              mySwiper.swipeTo(0, 0, false);
            } else {
              // mySwiper hasn't been initialized yet; save
              // the slide off in an array so we can add it later.
              newSlides.push({html: html, callback: callback});
            }
          };
    
          $timeout(function() {
            mySwiper = $element.swiper({
              mode: 'horizontal',
              loop: true,
              onSlideClick: function(swiper) {
                // Look up the callback we saved off and call it.
                var clicked = swiper.clickedSlide;
                var slideNumber = clicked.data('slideNumber');
                var callback = callbacks[slideNumber];
                if (callback) callback();
              }
            });
    
            // Now that mySwiper has been initialized, iterate
            // over any calls to `addSlide` that happened
            // before we were ready and add them to the swiper.
            for (var i = 0; i < newSlides.length; i++) {
              var slide = newSlides[i];
              this.addSlide(slide.html, slide.callback);
            }
          }.bind(this));
        }
      }
    })
    .directive('slide', function() {
      return {
        restrict: 'EA',
        // Look for a parent `swiper` element and get its controller 
        require: '^swiper',
        template: "<div class='swiper-slide' ng-transclude></div>",
        replace: true,
        transclude: true,
        link: function(scope, elem, attrs, swiper) {
          swiper.addSlide(elem, function() {
            scope.$apply(attrs.ngClick);
          });
        }
      }
    })
    .controller('MyCtrl', function ($scope) {
      $scope.slides = [{
        name: 'one',
        hidden: 'kittens'
      }, {
        name: 'two',
        hidden: 'puppies'
      }, {
        name: 'three',
        hidden: 'bacon'
      }];
    
      $scope.items = ["Add to me", "please"];
    
      $scope.select = function(slide) {
        $scope.items.push(slide);
      };
    });
    

    您可以看到我们已经设法将所有特定于Swiper的功能保留在指令中,而我们循环的数据(slides)和要回拨的数据(select)附加到控制器范围,它们更有意义(因为它们是特定于应用程序的数据)。

    同样,可以在此处找到工作演示:http://jsfiddle.net/BinaryMuse/UruNG/

答案 1 :(得分:1)

我注意到了几件事:

1)根本不需要链接功能。当您使用模板时,angular负责编译模板内的指令。此外,您的链接函数绑定到选择器元素,而不是单独绑定每个li,因此您无法确定单击哪个数据对象的原因。

2)您的指令正在使用继承的范围,但都将不同的东西分配给同一属性名称。在selector指令中,您将$scope.selected指定为函数。在selected指令中,您将$scope.selected指定为值数组。这些干扰是因为它们使用相同的范围。我能够通过将第一个更改为$scope.select = function(data)...来修复它。

3)您编写了事件处理程序以查找数据作为第一个参数。该事件是第一个参数,之后的任何参数都绑定到广播事件时传递的参数,所以它更像是$scope.$on('slideSelected', function(event, data)...

4)您的事件处理程序不需要应用范围,因为这将自动发生,而只是更新模型。

更新的小提琴是here