将函数传递给指令以在链接中执行的正确方法 [英] Proper way to pass functions to directive for execution in link

查看:74
本文介绍了将函数传递给指令以在链接中执行的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道我们通常通过隔离的范围将函数传递给指令:

 .directive('myComponent', function () {
    return {
        scope:{
            foo: '&'
        }        
    };
})
 

然后在模板中,我们可以像下面这样调用此函数:

 <button class="btn" ng-click="foo({ myVal: value })">Submit</button>
 

myVal是父作用域中函数foo所采用的参数的名称.

现在,如果我打算从link函数而不是模板中使用此函数,则必须使用scope.foo()(value)进行调用,因为scope.foo充当原始函数的包装器.对我来说,这似乎有些乏味.

如果我使用=将函数传递给myComponent指令:

 .directive('myComponent', function () {
    return {
        scope:{
            foo: '='
        }        
    };
})
 

然后,我将可以仅使用链接功能中的scope.foo(value).那么,这是在函数上使用双向绑定的有效用例吗?还是我正在做某种我不应该做的hack?

解决方案

这就是为什么我对答案不满意.

首先,您永远不要使用'='将函数引用传递给指令.

'='创建两个监视,并使用它们来确保指令作用域和父作用域引用都相同(双向绑定).允许指令在父作用域中更改函数的定义是一个非常糟糕的主意,这是在使用这种类型的绑定时发生的情况.另外,应该将手表最小化-尽管它可以工作,但多余的两个$ watch是不必要的.因此,这并不好-拒绝投票的部分原因是暗示确实如此.

第二个-答案误解了'&'做. &不是单向绑定".它之所以会被误用,是因为与"="不同,它不会创建任何$ watches,并且在指令范​​围内更改属性的值不会传播到父级.

根据文档:

&或& attr-提供一种在以下情况下执行表达式的方法 父范围

使用&在指令中,它生成一个函数,该函数返回针对父范围评估的表达式的值.该表达式不必是函数调用.它可以是任何有效的角度表达式.另外,此生成的函数采用一个对象参数,该参数可以覆盖表达式中找到的任何局部变量的值.

要扩展OP的示例,假设父级以以下方式使用此伪指令:

<my-component foo="go()">

在指令(模板或链接函数)中,如果您调用

foo({myVal: 42});

您正在执行的操作是对表达式"go()"求值,该表达式恰好在父作用域上调用了函数"go",未传递任何参数.

或者,

<my-component foo="go(value)">

您正在评估父作用域上的表达式"go(value)",该表达式将基本上调用$ parent.go($ parent.value)"

<my-component foo="go(myVal)">

您正在评估表达式"go(myVal)",但是在评估该表达式之前,myVal将被替换为42,因此评估后的表达式将为"go(42)".

<my-component foo="myVal + value + go()">

在这种情况下,$ scope.foo({myVal:42})将返回以下结果:

42 + $parent.value + $parent.go()

本质上,此模式允许指令注入"指令的使用者可以在foo表达式中选择使用的变量.

您可以这样做:

<my-component foo="go">

并在指令中:

$scope.foo()(42)

$ scope.foo()将计算表达式"go",该表达式将返回对$ parent.go函数的引用.然后将其称为$ parent.go(42).这种模式的不利之处在于,如果表达式不求值为函数,则会出现错误.

最终否决的最终原因是断言ng-event指令使用&.事实并非如此.内置指令均无法使用以下方式创建隔离范围:

scope:{
}

& foo"的实现(为简化起见被简化)归结为:

$scope.foo = function(locals) {
    return $parse(attr.foo)($scope.$parent, locals);
}

ng-click的实现类似,但是(也简化了):

link: function(scope, elem, attr) {
    elem.on('click', function(evt) {
        $parse(attr.ngClick)(scope, {
             $event: evt
        }
    });
}

因此要记住的关键是,当您使用'&'时,您没有传递函数-您传递的是表达式.该指令可以通过调用生成的函数随时获取该表达式的结果.

I know we usually pass functions to directives via an isolated scope:

.directive('myComponent', function () {
    return {
        scope:{
            foo: '&'
        }        
    };
})

And then in the template we can call this function like such:

<button class="btn" ng-click="foo({ myVal: value })">Submit</button>

Where myVal is the name of the parameter that function foo in the parent scope takes.

Now if I intend to use this from the link function instead of template, I will have to call it with: scope.foo()(value), since scope.foo serves as a wrapper of the original function. This seems a bit tedious to me.

If I pass the function to the myComponent directive using =:

.directive('myComponent', function () {
    return {
        scope:{
            foo: '='
        }        
    };
})

Then I will be able to just use scope.foo(value) from my link function. So is this a valid use case to use 2-way binding on functions, or am I doing some sort of hack that I shouldn't be doing?

解决方案

Here is why I downvoted the answer.

First, you should never use '=' to pass function references to directives.

'=' creates two watches and uses them to ensure that both the directive scope and the parent scope references are the same (two-way binding). It is a really bad idea to allow a directive to change the definition of a function in your parent scope, which is what happens when you use this type of binding. Also, watches should be minimized - while it will work, the two extra $watches are unnecessary. So it is not fine - part of the down vote was for suggesting that it was.

Second - the answer misrepresents what '&' does. & is not a "one way binding". It gets that misnomer simply because, unlike '=', it does not create any $watches and changing the value of the property in the directive scope does not propagate to the parent.

According to the docs:

& or &attr - provides a way to execute an expression in the context of the parent scope

When you use & in a directive, it generates a function that returns the value of the expression evaluated against the parent scope. The expression does not have to be a function call. It can be any valid angular expression. In addition, this generated function takes an object argument that can override the value of any local variable found in the expression.

To extend the OP's example, suppose the parent uses this directive in the following way:

<my-component foo="go()">

In the directive (template or link function), if you call

foo({myVal: 42});

What you are doing is evaluating the expression "go()", which happens to call the function "go" on the parent scope, passing no arguments.

Alternatively,

<my-component foo="go(value)">

You are evaluating the expression "go(value)" on the parent scope, which will is basically calling $parent.go($parent.value)"

<my-component foo="go(myVal)">

You are evaluating the expression "go(myVal)", but before the expression is evaluated, myVal will be replaced with 42, so the evaluated expression will be "go(42)".

<my-component foo="myVal + value + go()">

In this case, $scope.foo({myVal: 42}) will return the result of:

42 + $parent.value + $parent.go()

Essentially, this pattern allows the directive to "inject" variables that the consumer of the directive can optionally use in the foo expression.

You could do this:

<my-component foo="go">

and in the directive:

$scope.foo()(42)

$scope.foo() will evaluate the expression "go", which will return a reference to the $parent.go function. It will then call it as $parent.go(42). The downside to this pattern is that you will get an error if the expression does not evaluate to a function.

The final reason for the down vote was the assertion that the ng-event directives use &. This isn't the case. None of the built in directives create isolated scopes with:

scope:{
}

The implementation of '&foo' is (simplified for clarity), boils down to:

$scope.foo = function(locals) {
    return $parse(attr.foo)($scope.$parent, locals);
}

The implementation of ng-click is similar, but (also simplified):

link: function(scope, elem, attr) {
    elem.on('click', function(evt) {
        $parse(attr.ngClick)(scope, {
             $event: evt
        }
    });
}

So the key to remember is that when you use '&', you are not passing a function - you are passing an expression. The directive can get the result of this expression at any time by invoking the generated function.

这篇关于将函数传递给指令以在链接中执行的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆