Angular将作用域传递给ng-include

42

我有一个控制器,我在我的应用程序中多次使用 ng-includeng-repeat。如下:

<div
  ng-repeat="item in items"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>
在控制器/模板中,我希望item的值存在,并且整个内容都是基于这个想法构建的。现在,我需要以稍微不同的方式使用控制器,而不使用ng-repeat,但仍然需要能够传入一个item。我看到了ng-init,认为它可以实现我所需的功能,就像这样:
<div
  ng-init="item = leftItem"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>
<div
  ng-init="item = rightItem"
  ng-include="'item.html'"
  ng-controller="ItemController"
></div>
但是似乎这并没有起作用。有人有想法怎样才能在这种特定情况下传递一个作用域变量吗?
编辑: 在上面的控制器中,会加载左侧项和右侧项等值,类似于以下内容:
.controller('MainController', function($scope, ItemModel) {
    ItemModel.loadItems()
        .then(function(items) {
            $scope.$apply(function() {
                $scope.leftItem = items.left;
                $scope.rightItem = items.right;
            });
        });
});

一个解决方案是创建一个新的指令,就像我在这个答案中所说的那样。 - smartmouse
kbjr,请记住ng-repeat是一个指令,它创建了一个新的子作用域,ng-include也是如此。除此之外,ng-init可以与ng-include一起使用。恐怕您需要展示控制器(ItemController)文件和模板文件(item.html),以便完全理解情况并找出为什么不起作用的原因。 - Victor
8个回答

36

虽然有点晚了,但是有一个小的Angular“技巧”可以在不实现愚蠢指令的情况下实现这一点。

添加一个内置指令,将扩展控制器作用域(例如ng-if),并且在使用ng-include的任何地方都会让您隔离所有包括范围的变量名称。

因此:

<div ng-include="'item.html'"
  ng-if="true"
  onload="item = rightItem">
</div>
<div ng-include="'item.html'"
  ng-if="true"
  onload="item = leftItem">
</div>
你可以使用不同的项目将模板item.html绑定到item变量上多次。 这是一个实现你想要的东西的Plunker示例 问题在于item在控制器作用域中不断更改,该作用域仅保存对item变量的一个引用,在每个onload指令时被擦除。
引入一个扩展当前作用域的指令,可以让您为所有ng-include拥有一个孤立的作用域。 因此,item引用在所有扩展作用域中得到保留并且是唯一的。

6
谢谢,这个完美地运作了。提示:我发现当添加一个在加载后(异步)动态填充的值时,它不起作用,因为在评估包含语句时它还没有准备好,但是将if语句更改为ng-if="myVariable"以便仅在myVariable填充时加载解决了这个问题。 - Iain Collins
使用onload不是很好的做法,因为它会在全局范围内设置一个变量。请参阅我的答案。 - Tanin
1
@Tanin,这就是为什么我注入了一个ng-if指令,以便为每个ng-include创建一个唯一的隔离子作用域,并避免变量名称冲突。 - Piou

33

您可以使用ngInclude提供的onload属性来实现此目的:

<div ng-include="'item.html'"
     ng-controller="ItemController"
     onload="item = rightItem">
</div>

链接到文档

编辑

在父级作用域中尝试像这样做:

$scope.dataHolder = {};

然后当异步数据被接收时,将数据存储在dataHolder上:

$scope.dataHolder.leftItem = items.leftItem;
$scope.dataHolder.rightItem = items.rightItem;

现在,当ng-include加载模板时,它会创建一个继承父级属性的子作用域。因此,$scope.dataHolder将在此子作用域中定义(最初为空对象)。但是,在接收到异步数据时,对空对象的引用应该包含新接收到的数据。


@kbjr 嗯,我刚在我的应用程序中测试了一下,它可以正常工作。我认为你对项目被异步加载的结论可能是解释。例如,如果我传递一个字符串值,它就可以工作。 - Sunil D.
这是我基于文档示例创建的一个plunkr。在“template1”中,我引用了一个名为bar的变量,它从作用域变量foo初始化。 - Sunil D.
@kbjr 实际上,在我的简单示例中甚至不需要使用 onload,因为由 ng-include 创建的作用域继承了父作用域的属性。因此,您可以通过在父作用域中初始化一个对象,然后在异步数据加载时更改您初始化的对象的属性来解决这个问题。我会编辑我的答案... - Sunil D.
我仍然需要在模板中引用这个新值,对吧?所以,我不再使用模板中的 item,而是使用 dataHolder.item。这将破坏另一个需要使用 ng-repeat 的用例。 - kbjr
是的,它并不完全符合你的例子...但我认为你至少可以确信你已经正确地诊断了问题。由ng-include创建的子作用域无法看到对$scope.leftItem/$scope.rightItem的更新。我的建议是确保两个作用域都引用同一个对象的一种方法。另一个想法是使用服务来存储这些值。但这两件事都不符合你最初的范例 :) - Sunil D.
显示剩余3条评论

17

我最终将其重写为一个指令,并在作用域中绑定所需的值

scope: {
    item: '='
}

2
投票赞成,因为ng-init=""和onload=""似乎不起作用,尽管它们应该...很遗憾我们需要每次都创建一个指令来完成这个简单的任务。 - Davy
每次都写特定的指令并不理想。请看我的解决方案。希望能帮到你! - Tanin

13

喜欢@Tanin的答案。它一次性解决了很多问题,并以非常简洁的方式进行了解决。对于像我这样不懂 Coffeescript 的人,下面是 javascript 代码...

注意:出于某些我还不太理解的原因,此代码要求您引用一次模板名称,而不是 ng-include 要求您两次引用模板名称,即使用 <div ng-include-template="template-name.html" ... > 替代 <div ng-include-template="'template-name.html'" ... >

.directive('ngIncludeTemplate', function() {  
  return {  
    templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },  
    restrict: 'A',  
    scope: {  
      'ngIncludeVariables': '&'  
    },  
    link: function(scope, elem, attrs) {  
      var vars = scope.ngIncludeVariables();  
      Object.keys(vars).forEach(function(key) {  
        scope[key] = vars[key];  
      });  
    }  
  }  
})

11

使用 onload 不是一个干净的解决方案,因为它会弄乱全局作用域。如果你有更复杂的东西,它会开始失败。

ng-include 不太可重用,因为它可以访问全局作用域。这有点奇怪。

以上内容是不正确的。ng-if 与 onload 不会弄乱全局作用域。

我们也不想为每种情况编写特定的指令。

编写一个通用指令而不是 ng-include 是一个更干净的解决方案。

理想的用法如下:

<div ng-include-template="'item.html'" ng-include-variables="{ item: 'whatever' }"></div>
<div ng-include-template="'item.html'" ng-include-variables="{ item: variableWorksToo }"></div>

该指令是:

.directive(
  'ngIncludeTemplate'
  () ->
    {
      templateUrl: (elem, attrs) -> attrs.ngIncludeTemplate
      restrict: 'A'
      scope: {
        'ngIncludeVariables': '&'
      }
      link: (scope, elem, attrs) ->
        vars = scope.ngIncludeVariables()
        for key, value of vars
          scope[key] = value
    }
)

您可以看到该指令没有使用全局作用域。相反,它从ng-include-variables中读取对象,并将这些成员添加到自己的本地作用域。

我希望这是您想要的;它干净而通用。


未捕获的语法错误:意外的符号> - Oliver Dixon
1
这是Coffeescript,不是Javascript。 - Tanin
这种方式更加优雅,可以避免全局范围的混乱。 - iplus26

4

我认为ng-init更适合这种情况。

<div ng-include='myFile.html' ng-init="myObject = myCtrl.myObject; myOtherObject=myCtrl.myOtherObject"/>

2
请考虑编辑您的帖子,增加更多关于代码内容和解决问题的原因的说明。即使代码能够运行,如果答案主要只包含代码而缺乏解释,往往并不能帮助提问者理解他们的问题。 - SuperBiasedMan
2
这种方法无法将同一模板多次绑定到不同的变量上。 - smartmouse

4

上面的方法无法处理第二级属性,例如<div ng-include-template=... ng-include-variables="{ id: var.id }">。请注意var.id

更新指令(比较丑陋,但可行):

.directive('ngIncludeTemplate', function() {
  return {
    templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },
    restrict: 'A',
    scope: {
      'ngIncludeVariables': '&'
    },
    link: function(scope, elem, attrs) {
      var cache = scope.ngIncludeVariables();
      Object.keys(cache).forEach(function(key) {
        scope[key] = cache[key];
      });

      scope.$watch(
        function() {
          var val = scope.ngIncludeVariables();
          if (angular.equals(val, cache)) {
            return cache;
          }
          cache = val;
          return val;
        },
        function(newValue, oldValue) {
          if (!angular.equals(newValue, oldValue)) {
            Object.keys(newValue).forEach(function(key) {
              scope[key] = newValue[key];
            });
          }
        }
      );

    }
  };
});

1
这应该真正成为ngInclude本身的一部分! ng-include是Angular中模块化的基本单元,它应该有一种清晰的参数化方式。 - David Bau
这个解决方案解决了第一个解决方案没有考虑到的模型更改问题...做得好! - Billy McCafferty

1
也许对于Mike和Tanin的答案来说是显而易见的更新-如果您使用内联模板,如下所示:
<script type="text/ng-template" id="partial">{{variable}}</script>

<div ng-include-template="'partial'" ng-include-variables="{variable: variable}"></div>

然后,在指令ngIncludeTemplate中,替换掉

。保留HTML,不要解释。

templateUrl: function(elem, attrs) { return attrs.ngIncludeTemplate; },  

带有

template: function(elem, attrs) { return document.getElementById(attrs.ngIncludeTemplate.split("'")[1]).innerHTML },

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