AngularJS:$scope.$watch 没有更新从自定义指令上的 $resource 获取的值

Posted

技术标签:

【中文标题】AngularJS:$scope.$watch 没有更新从自定义指令上的 $resource 获取的值【英文标题】:AngularJS : $scope.$watch is not updating value fetched from $resource on custom directive 【发布时间】:2012-06-23 13:15:14 【问题描述】:

我遇到了一个让我发疯的自定义指令问题。 我正在尝试创建以下自定义(属性)指令:

angular.module('componentes', [])
    .directive("seatMap", function ()
        return 
            restrict: 'A',
            link: function(scope, element, attrs, controller)

                function updateSeatInfo(scope, element)
                    var txt = "";
                    for (var i in scope.seats)
                        txt = txt + scope.seats[i].id + " ";
                    $(element).text("seat ids: "+txt);
                

                /* 
                    // This is working, but it's kind of dirty...
                    $timeout(function()updateSeatInfo(scope,element);, 1000);
                */

                scope.$watch('seats', function(newval, oldval)
                    console.log(newval, oldval);
                    updateSeatInfo(scope,element);
                );
            
        
    );

这个“属性类型”指令(称为 seatMap)试图显示一个座位 ID 列表(例如,对于剧院),我将通过 $resource 服务(参见下面的代码)从服务器获取到一个 div (元素)。

我将它与这个简单的部分 html 一起使用:

<div>
    <!-- This is actually working -->
    <ul>
        <li ng-repeat="seat in seats">seat.id</li>
    </ul>

    <!-- This is not... -->
    <div style="border: 1px dotted black" seat-map></div>
</div>

这是加载作用域的控制器:

function SeatsCtrl($scope, Seats) 
    $scope.sessionId = "12345";
    $scope.zoneId = "A";
    $scope.seats = Seats.query(sessionId: $scope.sessionId, zoneId: $scope.zoneId);
    $scope.max_seats = 4;

“席位”是一个简单的服务,使用 $resources 从服务器获取 JSON

angular.module('myApp.services', ['ngResource'])
    .factory('Seats', function($resource)
        return $resource('json/seats-:sessionId-:zoneId.json', , );
    )
;

app.js(asientos_libres.html 是我一直在使用的部分):

angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'componentes']).
  config(['$routeProvider', function($routeProvider) 
    $routeProvider.when('/view1', templateUrl: 'partials/asientos_libres.html', controller: SeatsCtrl);
    $routeProvider.otherwise(redirectTo: '/view1');
  ]);

问题是,即使我在指令的链接函数中设置了“scope.$watch”,以便范围可以检查“seats”属性是否已更改以更新 id 列表,但事实并非如此目前 $scope.seats 正在控制器中工作(当我们调用“查询”时)。

正如您在代码中看到的那样,我尝试使用 $timeout 来延迟“updateSeatInfo”的启动,但恐怕这不是迄今为止最聪明的解决方案...

我也尝试不发出 JSON 请求,而是在 $scope.seats 中使用硬编码字典,它可以工作,所以这似乎是一个同步问题。

注意:updateSeatInfo 只是一个测试函数,我将使用的实际函数有点复杂。

你知道如何应对吗?

非常感谢您!

编辑 1: 添加了 app.js,我在其中使用路由器调用 SeatsCtrl,感谢 Supr 的建议。但是,我仍然遇到同样的问题。

编辑 2:已解决!(?) 好的!看来我找到了一个解决方案,它可能不是最好的,但它工作正常! :) 据我在http://docs.angularjs.org/api/ng.$timeout 看到的,我们可以使用 $timeout(setTimeout 的包装器)没有延迟!这很棒,因为我们不会在 $timeout 内人为地延迟代码的执行,而是让指令在异步请求完成之前不运行它。

希望它也适用于长时间等待的请求...

如果有人知道更好的解决方法,请告诉!

【问题讨论】:

【参考方案1】:

问题是 watch 默认比较引用而不是对象。将 ,true 添加到末尾以使其比较值。

scope.$watch('seats', function(newval, oldval)
                console.log(newval, oldval);
                updateSeatInfo(scope,element);
            , true);

【讨论】:

请注意:像这样的深度比较有性能成本。当深入观察对象集合而不是单个对象时,这主要是一个问题,但值得牢记。【参考方案2】:

我也有这个问题。这是因为我的变量最初没有在范围内定义“未定义”。 Watch 似乎不适用于未定义的变量。毕竟看起来很明显。

当我的变量将由控制器有效设置时,我首先尝试使用 watch 来触发。示例:

myApp.controller('Tree', function($scope, Tree) 

Tree.get(,
function(data)  // SUCCESS
    console.log("call api Tree.get succeed");
    $scope.treeData = data;
,

function(data)  // FAILURE
    console.log("call api Tree.get failed");
    $scope.treeData = ;
);
);

我通过在调用服务之前用一个空对象初始化我的变量来解决它:

myApp.controller('Tree', function($scope, Tree) 

$scope.treeData = ;      // HERE

Tree.get(,
function(data)  // SUCCESS
    console.log("call api Tree.get succeed");
    $scope.treeData = data;
,

function(data)  // FAILURE
    console.log("call api Tree.get failed");
    $scope.treeData = ;
);
);

在这种情况下,watch 能够检测到变量的变化。

【讨论】:

【参考方案3】:

我看不到您在任何地方都在使用SeatsCtrl-controller?它是如何使用的?您是否验证它已被激活,并且查询已实际执行?

检查 SeatsCtrl 是否正在使用的最快方法是在其中添加 console.log('SeatsCtrl actived!');。如果不是,则将ng-controller="SeatsCtrl" 添加到div

您也可以直接在控制器内的座位上放置一个监视和日志,以确保它不是范围界定的问题。

【讨论】:

好吧,我实际上是通过 app.js 中的路由器使用 SeatsCtrl,很抱歉之前没有告诉它。我已经编辑了帖子以使其更清晰。 我明白了。尽管如此,您是否验证了控制器是否运行,并且查询是否按预期返回座位?就像我说的那样,在 SeatsCtrl 中添加一个监视至少会显示查询是否正常运行,在这种情况下,它可能是范围界定的问题。 已检查。只有当我使用超时时,查询才会运行。这与指令中发生的问题相同。通过Ajax(通过$response)获取数据似乎完全是同步问题......顺便说一句,范围没问题。【参考方案4】:

我也有这个问题。我认为这是Angular中的一个问题。在 GitHub 上有一张发给 "beef up $resource futures" 的票,它可能会解决这个问题,因为您真正需要的是访问您拥有的资源的承诺对象。

或者,观察者可以等到承诺解决后再触发。

与此同时,解决此问题的一种更优雅的不需要超时的方法是重新分配从 $resource 上的成功回调中监视的范围属性。这将导致观察者在适当的时间再次触发。

没有延迟的超时只是将延迟函数的评估放在当前堆栈的末尾 - 在您的情况下,您的资源恰好在那时解决了,但如果服务器有一个问题,这可能是一个问题星期一的情况。

例子:

$scope.myAttr = MyResource.fetch(
  function (resource)  $scope.myAttr = new MyResource(angular.copy(resource)); 
);

【讨论】:

以上是关于AngularJS:$scope.$watch 没有更新从自定义指令上的 $resource 获取的值的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS中的$watch(),$digest()和$apply()

AngularJS:$scope.$watch 没有更新从自定义指令上的 $resource 获取的值

一步步构建自己的AngularJS——scope之$watch及$digest

关于angularJS的$watch的 一些小用法

AngularJS scope.watch 以非常慢的速度触发并且错过了更改

AngularJS $watch 监听