在AngularJS中,带ng-repeat范围的指令隔离范围

时间:2022-12-03 14:23:45

I have a directive with an isolate-scope (so that I can reuse the directive in other places), and when I use this directive with an ng-repeat, it fails to work.

我有一个带有隔离范围的指令(这样我可以在其他地方重用该指令),当我使用这个指令与一个ng-repeat一起时,它就不能工作了。

I have read all the documentation and Stack Overflow answers on this topic and understand the issues. I believe I have avoided all the usual gotchas.

我已经阅读了关于这个主题的所有文档和堆栈溢出答案,并理解了这些问题。我相信我已经避免了所有常见的陷阱。

So I understand that my code fails because of the scope created by the ng-repeat directive. My own directive creates an isolate-scope and does a two-way data-binding to an object in the parent scope. My directive will assign a new object-value to this bound variable and this works perfectly when my directive is used without ng-repeat (the parent variable is updated correctly). However, with ng-repeat, the assignment creates a new variable in the ng-repeat scope and the parent variable does not see the change. All this is as expected based on what I have read.

因此我理解我的代码失败是因为ng-repeat指令创建的范围。我自己的指令创建一个隔离范围,并对父范围中的对象执行双向数据绑定。我的指令将为这个绑定变量分配一个新的对象值,当我的指令不使用ng-repeat(父变量被正确地更新)时,这个操作将会非常有效。但是,使用ng-repeat时,赋值在ng-repeat作用域中创建一个新变量,并且父变量没有看到更改。所有这些都是基于我所读到的。

I have also read that when there are multiple directives on a given element, only one scope is created. And that a priority can be set in each directive to define the order in which the directives are applied; the directives are sorted by priority and then their compile functions are called (search for the word priority at http://docs.angularjs.org/guide/directive).

我还了解到,当给定元素上有多个指令时,只创建一个作用域。并在每个指令中设置优先级,以定义执行指令的顺序;这些指令按优先级排序,然后调用它们的编译函数(在http://docs.angularjs.org/guide/directive中搜索单词优先级)。

So I was hoping I could use priority to make sure that my directive runs first and ends up creating an isolate-scope, and when ng-repeat runs, it re-uses the isolate-scope instead of creating a scope that prototypically inherits from the parent scope. The ng-repeat documentation states that that directive runs at priority level 1000. It is not clear whether 1 is a higher priority level or a lower priority level. When I used priority level 1 in my directive, it did not make a difference, so I tried 2000. But that makes things worse: my two-way bindings become undefined and my directive does not display anything.

因此,我希望我可以优先使用,以确保我的指令首先运行,并最终创建一个隔离范围,当ng-repeat运行时,它将重用隔离范围,而不是创建原型从父范围继承的范围。ng-repeat文档说明该指令在优先级1000上运行。目前还不清楚1是高优先级还是低优先级。当我在指令中使用优先级1时,它没有影响,所以我尝试了2000。但这让事情变得更糟:我的双向绑定没有定义,我的指令也没有显示任何内容。

I have created a fiddle to show my issue. I have commented out the priority setting in my directive. I have a list of name objects and a directive called name-row that shows the first and last name fields in the name object. When a displayed name is clicked, I want it to set a selected variable in the main scope. The array of names, the selected variable are passed to the name-row directive using two-way data-binding.

我创造了一个小提琴来展示我的问题。我已经注释掉了指令中的优先级设置。我有一个名为name对象的列表和一个名为name row的指令,该指令显示name对象中的名和名字段。单击显示的名称时,我希望它在主作用域中设置一个选定的变量。使用双向数据绑定将所选变量的名称数组传递给name-row指令。

I know how to get this to work by calling functions in the main scope. I also know that if selected is inside another object, and I bind to the outer object, things would work. But I am not interested in those solutions at the moment.

我知道如何通过调用主作用域中的函数来实现这一点。我还知道,如果被选中的对象在另一个对象中,我将绑定到外部对象,那么这些东西就会工作。但我现在对这些解决方案不感兴趣。

Instead, the questions I have are:

相反,我的问题是:

  • How do I prevent ng-repeat from creating a scope that prototypically inherits from the parent scope, and instead have it use my directive's isolate-scope?
  • 如何防止ng-repeat创建一个原型继承自父范围的范围,而让它使用我的指令的隔离范围?
  • Why is priority level 2000 in my directive not working?
  • 为什么我的指令中2000优先级不工作?
  • Using Batarang, is it possible to know what type of scope is in use?
  • 使用Batarang,是否可能知道正在使用的范围类型?

2 个解决方案

#1


200  

Okay, through a lot of the comments above, I have discovered the confusion. First, a couple of points of clarification:

好的,通过上面的很多评论,我发现了困惑。首先,有几点需要澄清:

  • ngRepeat does not affect your chosen isolate scope
  • ngRepeat不会影响您所选择的隔离范围
  • the parameters passed into ngRepeat for use on your directive's attributes do use a prototypically-inherited scope
  • 传递给ngRepeat的参数用于指令的属性使用的是原型继承的范围。
  • the reason your directive doesn't work has nothing to do with the isolate scope
  • 您的指令不能工作的原因与隔离范围无关

Here's an example of the same code but with the directive removed:

这里有一个相同代码的例子,但是去掉了指令:

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Here is a JSFiddle demonstrating that it won't work. You get the exact same results as in your directive.

这里有一个JSFiddle证明它不能工作。你得到的结果和你的指令完全一样。

Why doesn't it work? Because scopes in AngularJS use prototypical inheritance. The value selected on your parent scope is a primitive. In JavaScript, this means that it will be overwritten when a child sets the same value. There is a golden rule in AngularJS scopes: model values should always have a . in them. That is, they should never be primitives. See this SO answer for more information.

为什么它不工作?因为AngularJS中的范围使用原型继承。父作用域上选择的值是一个原语。在JavaScript中,这意味着当一个孩子设置相同的值时,它会被覆盖。在AngularJS作用域中有一条黄金规则:模型值应该始终具有a。在他们。也就是说,它们不应该是原始的。更多信息请参见此答案。


Here is a picture of what the scopes initially look like.

这是示波器最初的样子。

在AngularJS中,带ng-repeat范围的指令隔离范围

After clicking the first item, the scopes now look like this:

单击第一项后,作用域现在看起来如下:

在AngularJS中,带ng-repeat范围的指令隔离范围

Notice that a new selected property was created on the ngRepeat scope. The controller scope 003 was not altered.

注意,在ngRepeat范围上创建了一个新选择的属性。控制器范围003没有改变。

You can probably guess what happens when we click on the second item:

你可能会猜到当我们点击第二项时会发生什么:

在AngularJS中,带ng-repeat范围的指令隔离范围


So your issue is actually not caused by ngRepeat at all - it's caused by breaking a golden rule in AngularJS. The way to fix it is to simply use an object property:

所以你的问题其实根本不是由ngRepeat引起的——它是由违反AngularJS的黄金准则引起的。修复它的方法是简单地使用对象属性:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Here is a second JSFiddle showing this works too.

下面是第二个JSFiddle,它显示了这个功能。

Here is what the scopes look like initially:

这是作用域最初的样子:

在AngularJS中,带ng-repeat范围的指令隔离范围

After clicking the first item:

点击第一项后:

在AngularJS中,带ng-repeat范围的指令隔离范围

Here, the controller scope is being affected, as desired.

在这里,控制器范围正在按需要受到影响。

Also, to prove that this will still work with your directive with an isolate scope (because, again, this has nothing to do with your problem), here is a JSFiddle for that too, the view must reflect the object. You'll note that the only necessary change was to use an object instead of a primitive.

此外,为了证明这仍然可以与您的指令一起使用隔离范围(因为,同样,这与您的问题无关),这里还有一个JSFiddle,视图必须反映对象。您会注意到,唯一必要的更改是使用对象而不是原语。

Scopes initially:

最初的范围:

在AngularJS中,带ng-repeat范围的指令隔离范围

Scopes after clicking on the first item:

单击第一项后的作用域:

在AngularJS中,带ng-repeat范围的指令隔离范围

To conclude: once again, your issue isn't with the isolate scope and it isn't with how ngRepeat works. Your problem is that you're breaking a rule that is known to lead to this very problem. Models in AngularJS should always have a ..

总结:同样,您的问题不在于隔离范围,也不在于ngRepeat如何工作。你的问题是你打破了一个众所周知的导致这个问题的规则。AngularJS中的模型应该始终有一个。

#2


5  

Without directly trying to avoid answering your questions, instead take a look at the following fiddle:

不要直接避免回答你的问题,而是看看下面的小提琴:

http://jsfiddle.net/dVPLM/

http://jsfiddle.net/dVPLM/

Key point is that instead of trying to fight and change the conventional behaviour of Angular, you could structure your directive to work with ng-repeat as opposed to trying to override it.

关键的一点是,不要试图去对抗和改变传统的角度行为,你可以构造你的指令来与ng-repeat一起工作,而不是试图推翻它。

In your template:

在你的模板:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

In your directive:

在你的指令:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

In response to your questions:

回答你的问题:

  • ng-repeat will create a scope, you really shouldn't be trying to change this.
  • ng-repeat将创建一个范围,您真的不应该试图改变它。
  • Priority in directives isn't just execution order - see: AngularJS : How does the HTML compiler arrange the order for compiling?
  • 指令中的优先级不仅仅是执行顺序——参见:AngularJS: HTML编译器如何安排编译顺序?
  • In Batarang, if you check the performance tab, you can see the expressions bound for each scope, and check if this matches your expectations.
  • 在Batarang中,如果您检查performance选项卡,您可以看到每个范围的表达式绑定,并检查这是否符合您的期望。

#1


200  

Okay, through a lot of the comments above, I have discovered the confusion. First, a couple of points of clarification:

好的,通过上面的很多评论,我发现了困惑。首先,有几点需要澄清:

  • ngRepeat does not affect your chosen isolate scope
  • ngRepeat不会影响您所选择的隔离范围
  • the parameters passed into ngRepeat for use on your directive's attributes do use a prototypically-inherited scope
  • 传递给ngRepeat的参数用于指令的属性使用的是原型继承的范围。
  • the reason your directive doesn't work has nothing to do with the isolate scope
  • 您的指令不能工作的原因与隔离范围无关

Here's an example of the same code but with the directive removed:

这里有一个相同代码的例子,但是去掉了指令:

<li ng-repeat="name in names"
    ng-class="{ active: $index == selected }"
    ng-click="selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Here is a JSFiddle demonstrating that it won't work. You get the exact same results as in your directive.

这里有一个JSFiddle证明它不能工作。你得到的结果和你的指令完全一样。

Why doesn't it work? Because scopes in AngularJS use prototypical inheritance. The value selected on your parent scope is a primitive. In JavaScript, this means that it will be overwritten when a child sets the same value. There is a golden rule in AngularJS scopes: model values should always have a . in them. That is, they should never be primitives. See this SO answer for more information.

为什么它不工作?因为AngularJS中的范围使用原型继承。父作用域上选择的值是一个原语。在JavaScript中,这意味着当一个孩子设置相同的值时,它会被覆盖。在AngularJS作用域中有一条黄金规则:模型值应该始终具有a。在他们。也就是说,它们不应该是原始的。更多信息请参见此答案。


Here is a picture of what the scopes initially look like.

这是示波器最初的样子。

在AngularJS中,带ng-repeat范围的指令隔离范围

After clicking the first item, the scopes now look like this:

单击第一项后,作用域现在看起来如下:

在AngularJS中,带ng-repeat范围的指令隔离范围

Notice that a new selected property was created on the ngRepeat scope. The controller scope 003 was not altered.

注意,在ngRepeat范围上创建了一个新选择的属性。控制器范围003没有改变。

You can probably guess what happens when we click on the second item:

你可能会猜到当我们点击第二项时会发生什么:

在AngularJS中,带ng-repeat范围的指令隔离范围


So your issue is actually not caused by ngRepeat at all - it's caused by breaking a golden rule in AngularJS. The way to fix it is to simply use an object property:

所以你的问题其实根本不是由ngRepeat引起的——它是由违反AngularJS的黄金准则引起的。修复它的方法是简单地使用对象属性:

$scope.state = { selected: undefined };
<li ng-repeat="name in names"
    ng-class="{ active: $index == state.selected }"
    ng-click="state.selected = $index">
    {{$index}}: {{name.first}} {{name.last}}
</li>

Here is a second JSFiddle showing this works too.

下面是第二个JSFiddle,它显示了这个功能。

Here is what the scopes look like initially:

这是作用域最初的样子:

在AngularJS中,带ng-repeat范围的指令隔离范围

After clicking the first item:

点击第一项后:

在AngularJS中,带ng-repeat范围的指令隔离范围

Here, the controller scope is being affected, as desired.

在这里,控制器范围正在按需要受到影响。

Also, to prove that this will still work with your directive with an isolate scope (because, again, this has nothing to do with your problem), here is a JSFiddle for that too, the view must reflect the object. You'll note that the only necessary change was to use an object instead of a primitive.

此外,为了证明这仍然可以与您的指令一起使用隔离范围(因为,同样,这与您的问题无关),这里还有一个JSFiddle,视图必须反映对象。您会注意到,唯一必要的更改是使用对象而不是原语。

Scopes initially:

最初的范围:

在AngularJS中,带ng-repeat范围的指令隔离范围

Scopes after clicking on the first item:

单击第一项后的作用域:

在AngularJS中,带ng-repeat范围的指令隔离范围

To conclude: once again, your issue isn't with the isolate scope and it isn't with how ngRepeat works. Your problem is that you're breaking a rule that is known to lead to this very problem. Models in AngularJS should always have a ..

总结:同样,您的问题不在于隔离范围,也不在于ngRepeat如何工作。你的问题是你打破了一个众所周知的导致这个问题的规则。AngularJS中的模型应该始终有一个。

#2


5  

Without directly trying to avoid answering your questions, instead take a look at the following fiddle:

不要直接避免回答你的问题,而是看看下面的小提琴:

http://jsfiddle.net/dVPLM/

http://jsfiddle.net/dVPLM/

Key point is that instead of trying to fight and change the conventional behaviour of Angular, you could structure your directive to work with ng-repeat as opposed to trying to override it.

关键的一点是,不要试图去对抗和改变传统的角度行为,你可以构造你的指令来与ng-repeat一起工作,而不是试图推翻它。

In your template:

在你的模板:

    <name-row 
        in-names-list="names"
        io-selected="selected">
    </name-row>

In your directive:

在你的指令:

    template:
'        <ul>' +      
'            <li ng-repeat="name in inNamesList" ng-class="activeClass($index)" >' +
'                <a ng-click="setSelected($index)">' +
'                    {{$index}} - {{name.first}} {{name.last}}' +
'                </a>' +
'            </li>' +
'        </ul>'

In response to your questions:

回答你的问题:

  • ng-repeat will create a scope, you really shouldn't be trying to change this.
  • ng-repeat将创建一个范围,您真的不应该试图改变它。
  • Priority in directives isn't just execution order - see: AngularJS : How does the HTML compiler arrange the order for compiling?
  • 指令中的优先级不仅仅是执行顺序——参见:AngularJS: HTML编译器如何安排编译顺序?
  • In Batarang, if you check the performance tab, you can see the expressions bound for each scope, and check if this matches your expectations.
  • 在Batarang中,如果您检查performance选项卡,您可以看到每个范围的表达式绑定,并检查这是否符合您的期望。