AngularJS中ng-repeat内的ng-model $scope未定义

19

如果我表达不清楚,先向大家道歉。在 ng-repeat 中有一个带有 ng-model 的文本框,当我试图获取文本框中的值时,它总是显示为 undefined。我只想让它显示对应文本框中输入的内容。

看起来这是一个与 $scope 相关的问题,所以我该如何将 $scope.postText 变成全局变量或者在控制器根级别使其可被访问?

这是 JSFiddle 链接,可以帮助理解情况:http://jsfiddle.net/stevenng/9mx9B/14/

4个回答

45

正如 @Gloopy 已经说明的那样,ng-repeat 为您 posts 数组中的每个项目创建一个新的子作用域。由于 posts 数组的每个项目都是原始值(字符串),ng-repeat 还会在每个子作用域上创建一个 post 属性,并将来自数组的适当值分配给它们中的每一个。 ng-repeat 块内部是 ng-model="postText"。这在每个子作用域上创建了一个 postText 属性。以下是其中两个子作用域的示例:

ng-repeat scopes

当用户在输入框中输入文本时,相应的灰色框将存储文本。(例如,从顶部算起第二个灰色框将存储用户在“技术”文本框中输入的文本。)父级作用域无法看到子级作用域中的postText属性--这是你所遇到的问题。有三种常见的解决方案:
  1. @Gloopy的回答:在父作用域中定义一个函数(子作用域可以访问,因为ng-repeat的子作用域从父作用域继承原型),并将子作用域属性值(即postText的值)传递给父作用域。
  2. 在您的posts数组中使用对象而不是基元。例如,
    $scope.posts = [{type:'tech'},{type:'news'},...];
    然后在您的ng-repeat循环中使用
    <input type="text" ng-model="post.postText">
    因为每个数组项都是一个对象(而不是基元),所以每个子作用域都会获得对数组posts中适当对象的引用,而不是值的副本。因此,post.postText被创建在父$scope属性posts上,因此它可见于父作用域。(因此,在这种情况下,子作用域只需调用savePost() - 不需要将任何值传递给父作用域。)
    换句话说,如果用户在第一个文本框中输入“this is tech related”,则父级的posts数组将自动更新如下:
    $scope.posts = [{type:'tech', postText:'this is tech related'},{type:'news'},...];
    最后注意:只有在用户键入内容后,postText属性才会添加到posts对象中。
  3. 使用ng-model="$parent.someProperty"将表单元素绑定到父作用域上的属性,而不是子作用域上的属性。这种解决方案对于您的情况来说很难实现,并且是一种相当脆弱的解决方案,因为它依赖于HTML结构进行作用域继承...但我提到它是为了完整性。
(@Renan在@Gloopy的答案评论中提出了第四种解决方案。这类似于解决方案1,但有一个变化:this代替将值传递给父级。我不喜欢这种方法,因为它很难确定正在访问或修改哪个$scope。我认为最好的方法是仅让在$scope上定义的函数访问和修改自己的$scope。)
要了解关于Angular中原型作用域继承如何工作的更多信息(以及更多图片),请参见AngularJS中原型/原型继承的细微差别是什么?

我不明白在第二个问题中如何在不传递任何内容的情况下调用savePost()。如果不传递子对象或索引,你如何引用特定的子作用域? - Dean Or
@Dean,说得好。你可以循环遍历“posts”并确定哪些帖子发生了更改(例如,如果您只想要一个“保存”按钮)。或者,如果您想为每个帖子提供“保存帖子”链接,您可以将post.type作为参数传递。 - Mark Rajcok
如果您在图表中绘制有关ParentScope无法看到输入框中键入的文本的部分,那么该图表可能会更好。请绘制一条红线或其他方式以显示在框中输入的内容对父级不可见。 - Kyle Pennell
@MarkRajcok 我有一个类似的情况,我在对象数组(发票)上使用ng-repeat。每个重复的发票都有一个输入字段,用于填充父发票内的另一个子对象数组(发票行)。我还需要在添加发票行后清除文本框。现在我所做的是,在发票中除了发票行数组之外,我还有一个临时的发票输入属性,我使用ng-model ='invoice.invoice-input'进行绑定。然后在将发票行添加到invoice.invoice-lines数组后清除它。这是这种情况的正确方法吗? - Tru
@Tru,你应该创建一个新的Stack Overflow问题/帖子,并展示你当前的设计/代码。这样多个人就可以对其进行评论并提出建议。 - Mark Rajcok

25
在您的点击表达式中,您可以引用postText并在savePost函数中访问它。如果这不是在ng-repeat中,那么您可以成功地访问单个$scope.postText,但是ng-repeat为每个项目创建了一个新的作用域。 这里是更新后的fiddle。
<div ng-repeat="post in posts">
   <strong>{{post}}</strong>
   <input type="text" ng-model="postText">
   <a href="#" ng-click="savePost(postText)">save post</a>
</div>

$scope.savePost = function(post){
   alert('post stuff in textbox: ' + post);
}

谢谢Renan,这帮了我很多。我通过像这样传递2个对象'<a href="#" ng-click="savePost(post, postText)">save post</a>'来使它工作。但是使用一个简单的'this'更好! - phteven
这个可以工作,但是如何通过脚本设置值呢?我正在寻找类似于 $scope.postText = "abv" 的东西。 - storm_buster
如果我理解你的问题,一种方法是使用ng-init来设置postText的初始值,就像这个例子中所示。我更喜欢改变绑定的数据类型,包括帖子文本,就像这个例子中所示。 - Gloopy

2
这可能是一个晚回答,请参考这个fiddle。http://jsfiddle.net/X5gd2/在输入文本框中键入一些文本后,单击链接时,请参阅firebug的控制台。这个想法是为每个在ng-repeat中重复的视图都有一个itemcontroller。
项控制器:
function postItemController($scope){
    $scope.savePost = function(){
        console.log($scope.postText + " which belongs to " + $scope.post +" will be saved")
    }
}

1
将模型分成标题和值。
angular.module('MyApp',[]);

function PostCtrl($scope) {
     $scope.posts = [{heading:'tech',value:''}, {heading:'news',value:''},     {heading:'sports',value:''},{heading:'health',value:''}];

    $scope.savePost = function(post){
        alert( 'post stuff in textbox: ' + post);
    }
}

以下是HTML代码。
<div ng-app="MyApp">
    <div ng-controller="PostCtrl">
        <div ng-repeat="post in posts">
          <strong>{{post.heading}}</strong>
          <input type="text" ng-model="post.value"> 
          <a href="#" ng-click="savePost(post.value)">save post</a>   
        </div>
    </div>
</div>

请查看这个Fiddle:Check out this Fiddle..

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