2015-09-04 4 views
0

Я подозреваю, что ответ «вы не можете» - но, возможно, вы можете показать мне лучший подход.Как я могу наблюдать за изменениями свойств Угловой службы?

Рассмотрите эту очень простую игру. Вы нажимаете на кнопку, и она отслеживает, сколько раз вы нажимали. В конце концов, однако, это будет намного сложнее - поэтому у меня есть услуга Player, которая содержит значение timesClicked.

angular.module('ClickGame', []) 

angular.module('ClickGame').factory('Player', function() { 
    var svc = {}; 
    svc.timesClicked = 0; 
    return svc; 
}); 

angular.module('ClickGame').controller('MainController', [ '$scope', 'Player', function($scope, Player) { 

    // In practice, I wouldn't necessarily expose Player directly on the 
    // scope like this, but it makes for simpler demo code. 
    $scope.Player = Player; 

    $scope.click = function() { 
     Player.timesClicked++; 
    }; 

} ]); 

HTML, довольно очевидны:

<html ng-app="ClickGame"> 

<body ng-controller="MainController"> 

    <p><button type="button" ng-click="click()">Click me!</button></p> 

    <p>You have clicked {{ Player.timesClicked }} times.</p> 

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> 
    <script src="demo.js"></script> 

</body> 

</html> 

До сих пор, так хорошо. Здесь я сталкиваюсь с проблемами: я хотел бы добавить систему достижений, которая будет награждать значки игроку, когда они достигнут определенных целей (например, нажав кнопку пять раз). Это кажется, что она принадлежит в своем компоненте, поэтому я создаю вторую услугу под названием Achievements, который впрыскивает Player:

app.factory('Achievements', [ 'Player', function(Player) { 

    // I can't do this, because neither Achievements nor Player have a scope. 
    $scope.$watch('Player.timesClicked', function(newValue, oldValue) { 
     if (newValue >= 5) { 
      console.log('ACHIEVEMENT UNLOCKED: Clicked 5 Times'); 
      // TODO: unwatch this model, to conserve resources and prevent 
      // the achievement from being awarded more than once 
     } 
    }); 

} ]); 

Посмотреть проблему?

подходы Я рассмотрел и отклонил:

  • Использование $interval опрашивать Player.timesClicked периодически. Это, очевидно, ужасно по целому ряду причин.

  • Сделать Achievements директивой вместо службы, поскольку директивы имеют объем. Тем не менее, это потребовало бы, чтобы я поместил тег <achievement> в свой index.html, как своего рода метод боковой двери для создания экземпляра этой области и сохранения ее в течение всего жизненного цикла приложения. Я рассматривал этот подход несколько раз, но это похоже на неправильное использование директив. Там должен быть более чистый путь.

  • У Player$broadcast события на корневой сферы, когда timesClicked увеличивается на единицу, и слушать это событие в Achievements. Мне это не нравится. По мере того, как игра будет усложняться, в итоге получится много шума передачи. Во всяком случае, это старое «просто сделать это глобальное» решение - эффективное, но обычно это не очень хорошая идея в конечном итоге :)

  • В Достижениях создайте новую специальную область с помощью $rootScope.$new(), прикрепите к ней игрока, а затем $watch это. (Или я мог бы создать новую область в Player, а затем выставить эту область как свойство svc.) Честно говоря, я не уверен, что это даже сработает - но когда я нахожу себя таким, как это, я подозреваю, что мне нужно возвращать назад и реорганизовывать что-то.

Есть ли другой подход, который я рассматриваю? Есть ли способ рефакторинга моего приложения, так что timesClicked хранится в месте, которое проще $watch (но все же легко делиться между различными частями моего приложения)?

Извините, если мне не хватает чего-то сверх очевидного; Я смотрел на угловом коде весь день, и я, вероятно, нужен перерыв :)

Update: Следует отметить, что регистрируя $watch изнутри MainController не будет работать.Хотя я сохранил этот демонстрационный код простым, фактическое приложение является SPA, использующим ngRoute, поэтому MainController будет выгружен, если игрок перейдет на другой маршрут. Но достижения следует все же контролировать и присуждать.

+0

завод и обслуживание не поддерживает $ scope –

+0

@SheraliTurdiyev, но он поддерживает $ rootScope. Как бы то ни было, использование $ watch сильно зависит от производительности, особенно когда у вас их много. Я думаю, что он ищет шаблон наблюдателя, см .: http://stackoverflow.com/questions/12576798/how-to-watch-service-variables/17558885#17558885](http://stackoverflow.com/questions/12576798/how-to-watch-service-variables/17558885 # 17558885) – iH8

+0

Похоже, что первым аргументом $ watch также может быть функция (docs здесь, хотя я не могу ссылаться на определенную часть, поэтому ctrl + f для «смотреть» и посмотреть таблицу параметров: https://docs.angularjs.org/api/ng/type/$rootScope.Scope). вы пытались использовать $ rootScope в Достижении и смотрели 'function() {return Player.timesClicked}' вместо «Player.links»? – martiansnoop

ответ

0
Inject Achievement Service in controller and put this service in your scope 

    app.controller('MainController' , ['Achievements' ,function(Achievements){ 

    $scope.achivement = Achievements; //Put this service in your scope 
     $scope.$watch('achivement.Player.links',function(newVal,oldVal){ 
     //do your things 
    }); 
    }]) 
+0

К сожалению, это не сработает, потому что, как я забыл упомянуть, MainController не всегда будет загружен. Хотя это не показано в примере кода, это будет SPA, использующий ngRoute - так что независимо от того, сколько часов должно гарантировать, что ценность должна оставаться в живых, пока приложение работает. – greenie2600

1

Простым и эффективным методом было бы использовать метод pub/sub, но с обратными вызовами, а не с событиями. Вот что я хотел бы сделать:

<html ng-app="ClickGame"> 
<body ng-controller="MainController"> 
    <p><button type="button" ng-click="click()">Click me!</button></p> 
    <p>You have clicked {{ timesClicked }} times.</p> 
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script> 
    <script src="demo.js"></script> 
</body> 
</html> 

angular.module('ClickGame', []) 
.factory('Player', function() { 
    var timesClicked = 0, timesClickedChangedCallbacks = []; 


    return { 
     getTimesClicked: function() { return timesClicked; }, 
     setTimesClicked: function(val) { 
      timesClicked = val; 
      timesClickedChangedCallbacks.forEach(function(cb) { cb(val); }); 
     }, 
     onTimesClickedChanged: function(cb) { 
      if (cb && typeof cb == 'function') { 
       timesClickedChangedCallbacks.push(cb); 
      } 
     } 
    }; 
}) 
.factory('Achievements', [ 'Player', function(Player) { 
    Player.onTimesClickedChanged(function(val) { 
     if (val == 5) { 
      console.log('ACHIEVEMENT UNLOCKED: Clicked 5 Times'); 
     } 
    }); 
} ]) 
.controller('MainController', [ '$scope', 'Player', function($scope, Player) { 
    $scope.timesClicked = Player.getTimesClicked(); 

    $scope.click = function() { 
     $scope.timesClicked++; 
     Player.setTimesClicked($scope.timesClicked); 
    }; 
}]); 

Вы также можете добавить способ отказаться от подписки на изменения timesClicked в службе игрока.

Смежные вопросы