Подтвердить что ты не робот

AngularJS: служба $broadcast и $watch не запускаются на приемном контроллере

AngularJS noob здесь, на моем пути к Angular Просветлению:)

Здесь ситуация:

Я реализовал сервис "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); 
     };
 }

и здесь контроллер приемника (в основном для логики интерфейса/представления)

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();
    };
}

в моих представлениях Показываю текущую информацию о треке, например:

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

метод next() определен в PlayerCtrl, и он просто вызывает тот же метод в службе AudioPlayer.

Проблема

Это прекрасно работает при ручном взаимодействии (т.е. когда я нажимаю на кнопку next()) - поток следующий:

  • PlayerCtrl перехватывает щелчок и запускает свой собственный метод next()
  • который в свою очередь запускает метод AudioPlayer.next()
  • который ищет следующую дорожку в списке воспроизведения и вызывает метод loadTrack()
  • loadTrack() $транслирует событие trackLoaded (отправляет сам трек)
  • PlayerCtrl прослушивает широковещательное событие и назначает трек текущему объекту
  • представление обновляется корректно, показывая информацию current.title и current.author

Однако, когда метод next() вызывается из AudioService в фоновом режиме (т.е. когда дорожка завершена), все шаги от 1 до 5 выполняются, но представление не получает уведомления изменения в текущем объекте PlayerCtrl.

Я ясно вижу, что новый объект дорожки назначается в PlayerCtrl, но он как будто представление не получает уведомления об изменении. Я noob, и я не уверен, что это поможет, но я попытался добавить выражение $watch в PlayerCtrl

$scope.$watch('current', function(newVal, oldVal) {
    console.log('Current changed');
})

который печатается только во время "ручных" взаимодействий...

Опять же, как я уже сказал, если я добавлю console.log(текущий) в $on listener так:

$scope.$on('trackLoaded', function(event, track) {
    $scope.current = track;
    console.log($scope.current);
});

это печатает правильно все время.

Что я делаю неправильно?

(ps Я использую AudioJS для аудиоплеера HTML5, но я не думаю, что это тот, кто виноват здесь...)

4b9b3361

Ответ 1

Когда у вас есть событие click, область $update обновляется, без события вам нужно будет использовать $apply

$scope.$apply(function () {
    $scope.current = track;
});

Ответ 2

Как не заглядывать в внутренности дайджеста, самый простой способ - использовать $timeout:

$timeout(function () {
    $scope.current = track;
}, 0);

Обратный вызов выполняется всегда в хорошей среде.

EDIT: Фактически, функция, которая должна быть завернута в фазу применения,

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

В противном случае трансляция будет пропущена.

~~~~~~

На самом деле альтернатива может быть лучше (по крайней мере, с семантической точки зрения), и она будет работать одинаково внутри или вне цикла дайджеста:

$scope.$evalAsync(function (scope) {
    scope.current = track;
});
  • Преимущество по отношению к $scope.$apply: вам не нужно знать, находитесь ли вы в цикле дайджеста.
  • Преимущество по отношению к $timeout: вам не нужен тайм-аут, и вы получаете более простой синтаксис без дополнительного параметра 0.

Ответ 3

// apply changes
$scope.current = track;
try {
 if (!$scope.$$phase) {
  $scope.$apply($scope.current);
 }
} catch (err) {
 console.log(err);
}

Ответ 4

Пробовал все, он работал у меня с $rootScope.$applyAsync(function() {});