javascript - 我在使用 Angular 指令复制最简单的 JavaScript 代码时遇到问题

标签 javascript angularjs angularjs-directive

我写了一个非常简单的小部件/片段。悬停时有五颗星(使用 font-awesome),用实心星替换空星。鼠标移出时返回默认值。如果有默认值,则单击星号,然后根据单击的星号更改默认值(例如单击第 4 颗星会将值更改为 4 等等。非常简单的东西。我一生都做不到我使用 Angular js 复制它...

如果我理解正确的话,我知道我需要使用指令和嵌入来做到这一点。我什至在获取变量 no 时遇到了很多麻烦。基于默认值的填充星和空星......

如果有人能指导我,我将不胜感激..这是代码。

HTML 内容

<div class="ratingList" rating-widget rate='{{ rating }}' increment="increment()">
<span>Hate it</span>
<span class="star"><i class="fa fa-star-o fa-lg"></i></span>
<span class="star"><i class="fa fa-star-o fa-lg"></i></span>
<span class="star"><i class="fa fa-star-o fa-lg"></i></span>
<span class="star"><i class="fa fa-star-o fa-lg"></i></span>
<span class="star"><i class="fa fa-star-o fa-lg"></i></span>
<span>love it</span>

非常基本的 Controller

bmApp.controller('MainController', ['$scope', function($scope){
$scope.rating = 3;

$scope.increment = function(){
$scope.rating = $scope.rating + 1;
}

}]);

罪魁祸首指令

bmApp.directive('ratingWidget', function(){
return{
    restrict: 'A',
    replace:true,
    transclude:true,

    template: '<div><button ng-click="increment()">Click</button><div class="rating"></div></div>',

    controller:['$scope', '$element', '$transclude', function($scope, $element, $transclude){

        $transclude(function(clone){
            var stars = clone.filter('.star');
            var filledStar = $('<span class="star"><i class="fa fa-star fa-lg"></i></span>');
            var container = $element.find('.rating');
            angular.forEach(stars, function(val, key){
                var star = $(val);
                if(key<$scope.rate)
                {
                    //console.log(key);
                    container.append(filledStar);
                    //star.replaceWith(filledStar);
                    //angular.element(star.children()[0]).removeClass('fa-star-o').addClass('fa-star')

                }else{
                    //console.log(key);
                    container.append(star);
                }       

            });

        });
    }],
    scope:{
        rate:'@',
        increment:'&'
    }
}

});

我一开始就卡住了,无法根据默认值显示填充的星星...追加结果是 3 颗星...

最佳答案

有几种不同的方法可以处理此类功能。

我已经更新了您的示例,以展示隔离范围和嵌入的使用(对于 increment() 按钮)。

我们还将星号标记捆绑到 ratingWidget 指令中,使其模块化并使其更像是一个独立组件。

您可以看到,由于 ng-repeatng-class 指令,如果我们不想,我们不必直接使用 HTML 元素, Angular 通过数据绑定(bind)处理繁重的工作。

这是一个骗子:http://plnkr.co/edit/hd5DLOpRC3R9EFy316Gl?p=preview

(如果您查看该 Plunker 的历史记录,您将看到我如何使用 jQuery 直接操作元素/类)

HTML:

<div ng-app="bmApp">
    <div ng-controller="MainController">
        <div rating-widget rate="rating" max-rating="maxRating">
                <!--
                This is the content that will be transcluded.

                Transclusion means that this content will linked with
                the parent scope instead of being linked into the
                scope of the `ratingWidget`.

                i.e. the `increment()` function is defined in `MainController` 
                not in the `ratingWidget`.
                -->
            <button ng-click="increment()">Click</button>
        </div>
    </div>
</div>

JavaScript:

var bmApp = angular.module('bmApp', []);

bmApp.controller('MainController', ['$scope',
    function($scope) {
        $scope.rating = 3;
        $scope.maxRating = 6;

        $scope.increment = function() {
          if ($scope.rating < $scope.maxRating){
            $scope.rating += 1;
          }
        }
    }]);

bmApp.directive('ratingWidget', function() {
    return {
        restrict: 'A',
        transclude: true,
        scope: {
            rate: '=',
            maxRating: '='
        },
        link: function(scope, element, attr){
            var classes = {
                empty: 'fa-star-o',
                full: 'fa-star'
            };

            scope.stars = [];
            scope.$watch('maxRating', function(maxRating){
              maxRating = maxRating || 5;
              scope.stars.length = maxRating;
              for (var i = 0, len = scope.stars.length; i < len; i++){
                if (!scope.stars[i]){
                  scope.stars[i] = {
                    cssClass: classes.empty
                  };
                }
              }

              updateRating(scope.rate);
            });

            scope.$watch('rate', function(newRating){
                updateRating(newRating);
            });

            scope.selectRating = function(index){
              // The $index is zero-index but the ratings
              // start at one, so add 1.
              scope.rate = index + 1;
            }

            function updateRating(rating){
                rating = rating || 0;
                for (var i = 0, len = scope.stars.length; i < len; i++){
                    var star = scope.stars[i];
                    if (i < rating){
                      star.cssClass = classes.full;
                    } else {
                      star.cssClass = classes.empty;
                    }
                }
            }
        },
        template:   '<div>' +
                        '<div class="ratingList">' +
                            '<span>Hate it</span>' +
                            '<span class="stars">' +
                              '<span class="star" ng-click="selectRating($index)" ng-repeat="star in stars track by $index"><i class="fa fa-lg" ng-class="star.cssClass"></i></span>' +
                            '</span>' +
                            '<span>love it</span>' +
                        '</div>' +
                        '<div ng-transclude></div' +
                    '</div>'

    }
})
<小时/>

编辑:

@丹唐

是的,如果您的按钮位于指令外部但位于 MainController 内部,那么它将全部按预期工作,并且您不需要嵌入。

但重点是按钮位于指令内部并调用 MainController 上定义的方法。为此,我们需要嵌入内容以便绑定(bind)到父范围。

这是一个展示此示例的插件:http://plnkr.co/edit/x9xZwve9VkwbTGKUGjZJ?p=preview

HTML:

<div ng-controller="MainCtrl">
    <div>I am: {{name}}</div>

    <div widget>
        <!-- 
        Without transclusion this will say 'widget', with transclusion this will say 'controller'.
        Transclusion lets us control the scope to which these expressions are bound.
        -->
        <div>I am: {{name}}</div>
    </div>
</div>

JavaScript:

testApp.controller('MainCtrl', ['$scope', function($scope){
    $scope.name = 'controller';
}]);

testApp.directive('widget', function(){
    return {
        scope: true,
        transclude: true,
        link: function(scope, element, attr){
            scope.name = 'widget'
        },
        template: '<div>' +
            '<div>I am: {{name}}</div>' +
            '<div ng-transclude></div>' +
        '</div>'
    }
});

我想说 Angular 中的 transclude 就像 JavaScript 中的闭包 - 它可以让你控制变量和表达式绑定(bind)的范围。

下面是上面示例的一个粗略的 JavaScript 模拟,以显示这两个概念之间的一些相似之处:

var name = 'controller';
var printCallback = function(){
    console.log('name=' + name);
}

function Widget(printCallback){
    var name = 'widget';

    this.printName = function(){
        console.log('name=' + name);
        printCallback();
    }
}

var widget = new Widget(printCallback);
widget.printName();
// Prints:
// name=widget
// name=controller

关于javascript - 我在使用 Angular 指令复制最简单的 JavaScript 代码时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22238582/

相关文章:

javascript - JQuery AJAX 加载使用 AJAX 的表单

jquery - 将动态创建的元素添加到指令范围

javascript - 如何在 Polymer 中的元素上触发事件?

javascript - 未捕获的类型错误 : Cannot read property 'fancybox' of undefined - Google Chrome only error?

javascript - 1.9.1 与 1.8.3 中的 Angular.js/jQuery html 字符串解析

javascript - 是否可以使用 flexbox 显示模型设置元素的滚动位置?

ruby-on-rails - Rails 应用程序的部分是 SPA (AngularJS) 或多个 SPA

javascript - AngularJS 模板渲染后执行 Javascript

angularjs - 解析链接函数内的指令

javascript - 如何在 React Native 上设置 Slider 的样式