AngularJS:service $ broadcast和$ watch在接收控制器上没有触发

Pie*_*tup 25 javascript angularjs

AngularJS noob在我的Angular Enlightenment路上:)

情况如下:

我在我的模块'app'中实现了一个服务'AudioPlayer',并且注册如下:

 app.service('AudioPlayer', function($rootScope) {

     // ...

     this.next = function () {
         // loads the next track in the playlist
         this.loadTrack(playlist[++playIndex]);     
     };

     this.loadTrack = function(track) {
         // ... loads the track and plays it
         // broadcast 'trackLoaded' event when done
         $rootScope.$broadcast('trackLoaded', track); 
     };
 }
Run Code Online (Sandbox Code Playgroud)

这里是'接收器'控制器(主要用于UI /表示逻辑)

app.controller('PlayerCtrl', function PlayerCtrl($scope, AudioPlayer) {


    // AudioPlayer broadcasts the event when the track is loaded

    $scope.$on('trackLoaded', function(event, track) {
        // assign the loaded track as the 'current' 
        $scope.current = track;
    });

    $scope.next = function() {
        AudioPlayer.next();
    };
}
Run Code Online (Sandbox Code Playgroud)

在我的视图中,我显示当前的曲目信息,如下所示:

<div ng-controller="PlayerCtrl">
    <button ng-click="next()"></button>
    // ...
    <p id="info">{{current.title}} by {{current.author}}</p>
</div>
Run Code Online (Sandbox Code Playgroud)

next()方法在PlayerCtrl中定义,它只是在AudioPlayer服务上调用相同的方法.

问题

当有手动交互时(即当我点击下一个()按钮时)这很好 - 流程如下:

  1. PlayerCtrl拦截点击并触发自己的next()方法
  2. 它反过来触发AudioPlayer.next()方法
  3. 它寻找播放列表中的下一首曲目并调用loadTrack()方法
  4. loadTrack()$广播'trackLoaded'事件(用它发送轨道本身)
  5. PlayerCtrl侦听广播事件并将轨道分配给当前对象
  6. 视图正确更新,显示current.title和current.author信息

但是,当在"背景"中从AudioService内调用next()方法时(即,当轨道结束时),会发生从1到5的所有步骤,但视图不会收到有关更改的通知在PlayerCtrl的'当前'对象中.

我可以清楚地看到在PlayerCtrl中分配的新轨道对象,但就好像视图没有得到更改通知.我是菜鸟,我不确定这是否有任何帮助,但我尝试过在PlayerCtrl中添加$ watch表达式

$scope.$watch('current', function(newVal, oldVal) {
    console.log('Current changed');
})
Run Code Online (Sandbox Code Playgroud)

仅在"手动"交互期间打印出来...

再次,就像我说的,如果我在$ on侦听器中添加一个console.log(current),如下所示:

$scope.$on('trackLoaded', function(event, track) {
    $scope.current = track;
    console.log($scope.current);
});
Run Code Online (Sandbox Code Playgroud)

这可以随时正确打印.

我究竟做错了什么?

(ps我正在使用AudioJS作为HTML5音频播放器,但我认为这不应该归咎于此......)

小智 21

当您有一个click事件时,$ scope会更新,而不需要使用$ apply

$scope.$apply(function () {
    $scope.current = track;
});
Run Code Online (Sandbox Code Playgroud)

  • 先生,我的一天.但是我发现这样做总是如此:`$ scope.current = track; $范围$适用().` (4认同)
  • @PierloUpitup最好将它放在`$ apply`函数中,因为它在`try/catch/finally`中运行代码:`try/catch`处理错误,`finally`使用`$ digest`运行你的代码无论错误如何. (3认同)

rew*_*ten 7

因为查看摘要内部结构是不安全的,最简单的方法是使用$timeout:

$timeout(function () {
    $scope.current = track;
}, 0);
Run Code Online (Sandbox Code Playgroud)

回调始终在良好的环境中执行.

编辑:事实上,应该包含在应用阶段的功能是

 this.loadTrack = function(track) {
     // ... loads the track and plays it
     // broadcast 'trackLoaded' event when done
     $timeout(function() { $rootScope.$broadcast('trackLoaded', track); });
 };
Run Code Online (Sandbox Code Playgroud)

否则广播将被错过.

~~~~~~

实际上,替代方案可能更好(至少从语义的角度来看)并且它将在摘要周期内部或外部同等地工作:

$scope.$evalAsync(function (scope) {
    scope.current = track;
});
Run Code Online (Sandbox Code Playgroud)
  • 优点$scope.$apply:您不必知道自己是否处于消化周期.
  • 关于以下方面的优势$timeout:您并不真正想要超时,并且您可以在没有额外0参数的情况下获得更简单的语法.