有条件地向元素添加Angular指令属性

19

有没有一种简单直接的方法来做以下事情 -

<div class="my-class" my-custom-directive="{{evaluate expression}}"></div>

如此一来,只有当表达式的值为真时,Angular才会添加该指令。

编辑:

该指令必须是一个属性,所以请不要使用像restrict: 'E'ng-if解决方案,
也不要使用restrict: 'C'ng-classng-attr - 它们不能用于自定义指令。

3个回答

5
通过创建一个高优先级和 terminal: true 的指令,可以实现此操作。然后,您可以调整元素属性(添加或删除它们),然后重新编译元素以让指令运行。
这里有一个示例: http://plnkr.co/edit/DemVGr?p=info 更改 "directive-if" 属性中的表达式以保留/删除 "logger" 指令。
如果属性的表达式求值为 false,则该属性将被移除。
<div directive-if="{'logger': 'myValue == 1'}"
     logger="testValue">
    <p>"logger" directive exists? <strong>{{logger}}</strong></p>
</div>

这是指令的实现。
稍加调整,您可以将其改为添加指令,而不是删除它们,如果您更喜欢这样做。
/**
 * The "directiveIf" directive allows other directives
 * to be dynamically removed from this element.
 *
 * Any number of directives can be controlled with the object
 * passed in the "directive-if" attribute on this element:
 *
 *    {'attributeName': expression[, 'attribute': expression]}
 * 
 * If `expression` evaluates to `false` then `attributeName`
 * will be removed from this element.
 *
 * Usage:
 *
 *         <any directive-if="{'myDirective': expression}"
 *                    my-directive>
 *         </any>
 *
 */
directive('directiveIf', ['$compile', function($compile) {
    return {

        // Set a high priority so we run before other directives.
        priority: 100,
        // Set terminal to true to stop other directives from running.
        terminal: true,

        compile: function() {
            
            // Error handling - avoid accidental infinite compile calls
            var compileGuard = 0;
            
            return {
                pre: function(scope, element, attr) {

                    // Error handling.
                    // 
                    // Make sure we don't go into an infinite 
                    // compile loop if something goes wrong.
                    compileGuard++;
                    if (compileGuard >= 10) {
                        console.log('directiveIf: infinite compile loop!');
                        return;
                    }
                    // End of error handling.

                    // Get the set of directives to apply.
                    var directives = scope.$eval(attr.directiveIf);
                    angular.forEach(directives, function(expr, directive) {
                        // Evaluate each directive expression and remove the directive
                        // attribute if the expression evaluates to `false`.
                        var result = scope.$eval(expr);
                        if (result === false) {
                            // Set the attribute to `null` to remove the attribute.
                            // 
                            // See: https://docs.angularjs.org/api/ng/type/$compile.directive.Attributes#$set
                            attr.$set(directive, null)
                        }
                    });

                    /*
                    Recompile the element so the remaining directives can be invoked.
                    
                    Pass our directive name as the fourth "ignoreDirective" argument 
                    to avoid infinite compile loops.
                    */
                    var result = $compile(element, undefined, undefined, 'directiveIf')(scope);


                    // Error handling.
                    // 
                    // Reset the compileGuard after compilation
                    // (otherwise we can't use this directive multiple times).
                    // 
                    // It should be safe to reset here because we will
                    // only reach this code *after* the `$compile()`
                    // call above has returned.
                    compileGuard = 0;

                }
            };

        }
    };
}]);

回顾一下,如果您使用表达式值而不是字符串来配置指令,例如 directive-if="{'logger': myValue == 1}"(请注意,myValue == 1 是一个表达式而不是字符串),那么您就不需要在循环内部再次使用第二个 scope.$eval(expr) - Sly_cardinal

2

@Sly_cardinal是正确的,我使用了他的代码,但是需要做一些调整:

(function () {

angular.module('MyModule').directive('directiveIf', function ($compile) {

    // Error handling.
    var compileGuard = 0;
    // End of error handling.

    return {

        // Set a high priority so we run before other directives.
        priority: 100,
        // Set terminal to true to stop other directives from running.
        terminal: true,

        compile: function() {
            return {
                pre: function(scope, element, attr) {

                    // Error handling.
                    // Make sure we don't go into an infinite
                    // compile loop if something goes wrong.
                    compileGuard++;
                    if (compileGuard >= 10) {
                        console.log('directiveIf: infinite compile loop!');
                        return;
                    }


                    // Get the set of directives to apply.
                    var directives = scope.$eval(attr.directiveIf);

                    for (var key in directives) {
                        if (directives.hasOwnProperty(key)) {

                            // if the direcitve expression is truthy
                            if (directives[key]) {
                                attr.$set(key, true);
                            } else {
                                attr.$set(key, null);
                            }
                        }
                    }

                    // Remove our own directive before compiling
                    // to avoid infinite compile loops.
                    attr.$set('directiveIf', null);

                    // Recompile the element so the remaining directives
                    // can be invoked.
                    var result = $compile(element)(scope);


                    // Error handling.
                    //
                    // Reset the compileGuard after compilation
                    // (otherwise we can't use this directive multiple times).
                    //
                    // It should be safe to reset here because we will
                    // only reach this code *after* the `$compile()`
                    // call above has returned.
                    compileGuard = 0;

                }
            };

        }
    };
});

})();

谢谢。虽然这段代码可能有效(我没有检查),但我不会轻易称其为“简单”或“直接”。它看起来非常冒险和容易出错(只需看一下大量的“错误处理”注释),而且我不是足够熟练的Angular专家,无法确定它对我的应用程序会产生什么后果。 - Daniel

0
另一种方法是创建两个版本的代码 - 一个需要指令,另一个不需要。并使用ng-if / ng-show显示其中之一。重复的代码可以移动到模板中,并可以进行包含。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接