AngularJS: A maneira correta de vincular a propriedades de um serviço

162

Estou procurando a melhor prática de como vincular a uma propriedade de serviço no AngularJS.

Eu trabalhei com vários exemplos para entender como vincular às propriedades em um serviço criado usando o AngularJS.

Abaixo, tenho dois exemplos de como vincular às propriedades em um serviço; ambos trabalham. O primeiro exemplo usa ligações básicas e o segundo exemplo usa $ scope. $ Watch para vincular às propriedades do serviço

Um destes exemplos é preferido ao vincular propriedades em um serviço ou há outra opção que eu não saiba que seria recomendada?

A premissa desses exemplos é que o serviço deve atualizar suas propriedades "lastUpdated" e "calls" a cada 5 segundos. Depois que as propriedades do serviço são atualizadas, a exibição deve refletir essas alterações. Ambos os exemplos funcionam com sucesso; Gostaria de saber se existe uma maneira melhor de fazê-lo.

Ligação básica

O código a seguir pode ser visualizado e executado aqui: http://plnkr.co/edit/d3c16z

<html>
<body ng-app="ServiceNotification" >

    <div ng-controller="TimerCtrl1" style="border-style:dotted"> 
        TimerCtrl1 <br/>
        Last Updated: {{timerData.lastUpdated}}<br/>
        Last Updated: {{timerData.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.timerData = Timer.data;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

A outra maneira de resolver a ligação às propriedades de serviço é usar $ scope. $ Watch no controlador.

$ scope. $ watch

O código a seguir pode ser visualizado e executado aqui: http://plnkr.co/edit/dSBlC9

<html>
<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.$watch(function () { return Timer.data.lastUpdated; },
                function (value) {
                    console.log("In $watch - lastUpdated:" + value);
                    $scope.lastUpdated = value;
                }
            );

            $scope.$watch(function () { return Timer.data.calls; },
                function (value) {
                    console.log("In $watch - calls:" + value);
                    $scope.calls = value;
                }
            );
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 5000);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>
</html>

Estou ciente de que posso usar $ rootscope. $ Broadcast no serviço e $ root. $ On no controlador, mas em outros exemplos que criei que usam $ broadcast / $ na primeira transmissão não são capturados pelo controlador, mas as chamadas adicionais transmitidas são acionadas no controlador. Se você conhece uma maneira de resolver o problema de transmissão $ rootscope. $, Forneça uma resposta.

Mas, para reafirmar o que mencionei anteriormente, gostaria de conhecer as melhores práticas de como vincular a propriedades de um serviço.


Atualizar

Esta pergunta foi originalmente feita e respondida em abril de 2013. Em maio de 2014, Gil Birman forneceu uma nova resposta, que mudei como a resposta correta. Como a resposta de Gil Birman tem muito poucos votos positivos, minha preocupação é que as pessoas que leem essa pergunta desconsiderem sua resposta em favor de outras respostas com muito mais votos. Antes de tomar uma decisão sobre qual é a melhor resposta, recomendo vivamente a resposta de Gil Birman.

Mike Barlow - BarDev
fonte
Acho que a resposta de Josh David Miller é melhor que a de Gil Birman. E usar $ watch, $ watchGroup e $ watchCollection pode torná-lo ainda melhor. A separação de preocupações é muito importante em aplicativos de tamanho médio a grande.
22615 Jonathan
@ Barev Eu acho que ambas as respostas não foram úteis, e os novos desenvolvedores entenderiam completamente errado.
Zalaboza 06/04
O problema seu perguntando sobre é sobre o comportamento nativa variável JavaScript em referenciar objetos, acrescentei uma explicação pouco abaixo
Zalaboza

Respostas:

100

Considere alguns prós e contras da segunda abordagem :

  • 0 em {{lastUpdated}} vez de {{timerData.lastUpdated}}, o que poderia ser tão facilmente {{timer.lastUpdated}}, o que eu poderia argumentar que é mais legível (mas não vamos discutir ... eu estou dando a este ponto uma classificação neutra para que você decida por si mesmo)

  • +1 Pode ser conveniente que o controlador atue como um tipo de API para a marcação, de modo que, se de alguma forma a estrutura do modelo de dados for alterada, você possa (em teoria) atualizar os mapeamentos de API do controlador sem tocar no parcial html.

  • -1 No entanto, a teoria nem sempre é prática e eu costumo encontrar-me ter que modificar marcação e lógica do controlador quando as mudanças são chamados para, de qualquer maneira . Portanto, o esforço extra de escrever a API nega sua vantagem.

  • -1 Além disso, essa abordagem não é muito SECA.

  • -1 Se você deseja vincular os dados ao ng-modelseu código, fica ainda menos SECO, pois é necessário empacotá-lo novamente $scope.scalar_valuesno controlador para fazer uma nova chamada REST.

  • -0.1 Há um pequeno impacto no desempenho, criando observadores extras. Além disso, se as propriedades dos dados forem anexadas ao modelo que não precisam ser observadas em um controlador específico, elas criarão uma sobrecarga adicional para os observadores profundos.

  • -1 E se vários controladores precisarem dos mesmos modelos de dados? Isso significa que você tem várias APIs para atualizar a cada alteração de modelo.

$scope.timerData = Timer.data;está começando a parecer muito tentador agora ... Vamos mergulhar um pouco mais nesse último ponto ... De que tipo de mudanças de modelo estávamos falando? Um modelo no back-end (servidor)? Ou um modelo que é criado e vive apenas no front-end? Nos dois casos, o que é essencialmente a API de mapeamento de dados pertence à camada de serviço front-end (uma fábrica ou serviço angular). (Observe que seu primeiro exemplo - minha preferência - não possui essa API na camada de serviço , o que é bom porque é simples o suficiente para não ser necessário).

Em conclusão , tudo não precisa ser dissociado. E, quanto à dissociação total da marcação do modelo de dados, as desvantagens superam as vantagens.


Controladores, em geral , não devem estar cheios de $scope = injectable.data.scalar's. Em vez disso, eles devem ser polvilhados com $scope = injectable.data's, promise.then(..)' s e$scope.complexClickAction = function() {..} 's

Como uma abordagem alternativa para obter o desacoplamento de dados e, portanto, o encapsulamento de visão, o único lugar em que realmente faz sentido desacoplar a visão do modelo é com uma diretiva . Mas mesmo lá, não $watchdimensione valores nas funções controllerou link. Isso não economizará tempo nem tornará o código mais sustentável ou legível. Isso nem tornará os testes mais fáceis, pois testes robustos em angular geralmente testam o DOM resultante de qualquer maneira . Em vez disso, em uma diretiva exija sua API de dados em forma de objeto e use apenas os $watchcampos criados por ng-bind.


Exemplo http://plnkr.co/edit/MVeU1GKRTN4bqA3h9Yio

<body ng-app="ServiceNotification">
    <div style="border-style:dotted" ng-controller="TimerCtrl1">
        TimerCtrl1<br/>
        Bad:<br/>
        Last Updated: {{lastUpdated}}<br/>
        Last Updated: {{calls}}<br/>
        Good:<br/>
        Last Updated: {{data.lastUpdated}}<br/>
        Last Updated: {{data.calls}}<br/>
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script type="text/javascript">
        var app = angular.module("ServiceNotification", []);

        function TimerCtrl1($scope, Timer) {
            $scope.data = Timer.data;
            $scope.lastUpdated = Timer.data.lastUpdated;
            $scope.calls = Timer.data.calls;
        };

        app.factory("Timer", function ($timeout) {
            var data = { lastUpdated: new Date(), calls: 0 };

            var updateTimer = function () {
                data.lastUpdated = new Date();
                data.calls += 1;
                console.log("updateTimer: " + data.lastUpdated);

                $timeout(updateTimer, 500);
            };
            updateTimer();

            return {
                data: data
            };
        });
    </script>
</body>

ATUALIZAR : Finalmente voltei a esta pergunta para acrescentar que não acho que nenhuma das abordagens esteja "errada". Originalmente, eu havia escrito que a resposta de Josh David Miller estava incorreta, mas, retrospectivamente, seus pontos são completamente válidos, especialmente seu ponto sobre a separação de preocupações.

Separando as preocupações de lado (mas tangencialmente relacionadas), há outra razão para a cópia defensiva que não considerei. Esta questão lida principalmente com a leitura de dados diretamente de um serviço. Mas e se um desenvolvedor da sua equipe decidir que o controlador precisa transformar os dados de alguma maneira trivial antes que a exibição os exiba? (Se os controladores devem transformar os dados de maneira alguma é outra discussão.) Se ela não fizer uma cópia do objeto primeiro, poderá inconscientemente causar regressões em outro componente de exibição que consome os mesmos dados.

O que essa pergunta realmente destaca são as deficiências arquitetônicas do aplicativo angular típico (e realmente de qualquer aplicativo JavaScript): forte acoplamento de preocupações e mutabilidade de objetos. Recentemente, fiquei encantado com o aplicativo de arquitetura com o React e estruturas de dados imutáveis. Fazer isso resolve maravilhosamente os dois problemas a seguir:

  1. Separação de preocupações : um componente consome todos os seus dados por meio de adereços e depende muito pouco de singletons globais (como serviços Angular) e não sabe nada sobre o que aconteceu acima na hierarquia de visualizações.

  2. Mutabilidade : todos os objetos são imutáveis, o que elimina o risco de mutação involuntária de dados.

O Angular 2.0 está agora no caminho certo para emprestar muito do React para alcançar os dois pontos acima.

Gil Birman
fonte
1
Mais Eu tenho trabalhado com o AngularJS quanto mais eu entendo. Acredito que os controladores AngularJS devem ser o mais simples e fino possível. Ao adicionar $ watches no controlador, isso complica a lógica. Apenas referenciando o valor no serviço, é muito mais simples e parece ser mais do modo AngularJS. -
Mike Barlow - BarDev
3
Os "dez mandamentos" do AngularJS. É declarativo por uma razão.
pilau
A resposta de Josh David Miller não é "INCORRETA". Tem uma hora e um lugar pra tudo.
JoshuaDavid
Acho que você está certo, @FireCoding. Estou pensando em atualizar a resposta.
22914 Gil
@GilBirman excelente resposta. Você se importaria de escrever e exemplo para a diretiva? Para ilustrar o que você disse "o único lugar em que realmente faz sentido desacoplar a exibição do modelo é com uma diretiva. [...] Em vez disso, em uma diretiva, exija sua API de dados em forma de objeto e use apenas $ observadores criados por ng-bind ".
Klode
78

Na minha perspectiva, $watchseria a melhor maneira de praticar.

Você pode realmente simplificar um pouco o seu exemplo:

function TimerCtrl1($scope, Timer) {
  $scope.$watch( function () { return Timer.data; }, function (data) {
    $scope.lastUpdated = data.lastUpdated;
    $scope.calls = data.calls;
  }, true);
}

É tudo o que você precisa.

Como as propriedades são atualizadas simultaneamente, você precisa apenas de um relógio. Além disso, como eles vêm de um único objeto bastante pequeno, mudei para apenas observar a Timer.datapropriedade. O último parâmetro passado para $watchdiz para verificar a igualdade profunda, em vez de apenas garantir que a referência seja a mesma.


Para fornecer um pouco de contexto, a razão pela qual eu preferiria esse método a colocar o valor do serviço diretamente no escopo é garantir uma separação adequada das preocupações. Sua visão não precisa saber nada sobre seus serviços para operar. O trabalho do controlador é colar tudo; seu trabalho é obter os dados de seus serviços e processá-los da maneira que for necessária e, em seguida, fornecer à sua visão as especificidades necessárias. Mas não acho que seu trabalho seja apenas repassar o serviço para a visualização. Caso contrário, o que o controlador está fazendo lá? Os desenvolvedores do AngularJS seguiram o mesmo raciocínio quando optaram por não incluir nenhuma "lógica" nos modelos (por exemplo,if instruções).

Para ser justo, provavelmente há várias perspectivas aqui e estou ansioso por outras respostas.

Josh David Miller
fonte
3
Você pode elaborar um pouco? Você prefere os relógios $ porque a vista é menos acoplada ao serviço? {{lastUpdated}}{{timerData.lastUpdated}}
Ou
2
O @BarDev, para usar Timer.dataem um $ watch, Timerdeve ser definido no $ scope, porque a expressão da string que você passa para $ watch é avaliada em relação ao escopo. Aqui está um desentupidor que mostra como fazer isso funcionar. O parâmetro objectEquality está documentado aqui - terceiro parâmetro - mas não é realmente muito bem explicado.
Mark Rajcok
2
Em termos de desempenho, $watché bastante ineficiente. Veja as respostas na stackoverflow.com/a/17558885/932632 e stackoverflow.com/questions/12576798/...
Krym
11
@Kyrm Isso provavelmente é verdade em algumas circunstâncias, mas ao lidar com desempenho, precisamos procurar os aprimoramentos "clinicamente significativos", em vez de apenas os generalizados estatisticamente significativos. Se houver um problema de desempenho em um aplicativo existente, ele deverá ser resolvido. Caso contrário, é apenas um caso de otimização prematura, que leva a códigos mais difíceis de ler e propensos a erros que não seguem as práticas recomendadas e que não demonstram benefícios.
Josh David Miller
1
Supondo que o observador use uma função para chamar o getter, então sim. Isso funciona bem. Eu também sou um defensor dos serviços que retornam instâncias em uma coleção, onde os recursos getter e setter do es5 são razoavelmente bons.
Josh David Miller
19

Tarde para a festa, mas para futuros googlers - não use a resposta fornecida.

O JavaScript possui um mecanismo de passagem de objetos por referência, enquanto apenas passa uma cópia superficial para os valores "números, seqüências de caracteres etc."

No exemplo acima, em vez de vincular atributos de um serviço, por que não expomos o serviço ao escopo?

$scope.hello = HelloService;

Essa abordagem simples tornará o angular capaz de fazer ligações bidirecionais e todas as coisas mágicas que você precisa. Não corte seu controlador com observadores ou marcação desnecessária.

E se você estiver preocupado com a exibição acidental de seus atributos de serviço, use-o definePropertypara torná-lo legível, enumerável, configurável ou definir getters e setters. Você pode obter muito controle, tornando seu serviço mais sólido.

Dica final: se você gasta seu tempo trabalhando mais em seu controlador do que em seus serviços, está fazendo errado :(.

Nesse código de demonstração específico que você forneceu, recomendo que você faça:

 function TimerCtrl1($scope, Timer) {
   $scope.timer = Timer;
 }
///Inside view
{{ timer.time_updated }}
{{ timer.other_property }}
etc...

Editar:

Como mencionei acima, você pode controlar o comportamento de seus atributos de serviço usando defineProperty

Exemplo:

// Lets expose a property named "propertyWithSetter" on our service
// and hook a setter function that automatically saves new value to db !
Object.defineProperty(self, 'propertyWithSetter', {
  get: function() { return self.data.variable; },
  set: function(newValue) { 
         self.data.variable = newValue; 
         // let's update the database too to reflect changes in data-model !
         self.updateDatabaseWithNewData(data);
       },
  enumerable: true,
  configurable: true
});

Agora em nosso controlador, se fizermos

$scope.hello = HelloService;
$scope.hello.propertyWithSetter = 'NEW VALUE';

nosso serviço alterará o valor de propertyWithSettere também lançará o novo valor no banco de dados de alguma forma!

Ou podemos adotar qualquer abordagem que quisermos.

Consulte a documentação do MDN para defineProperty.

Zalaboza
fonte
Tenho certeza que é isso que eu descrevi acima $scope.model = {timerData: Timer.data};, apenas anexando-o ao modelo em vez de diretamente no Scope.
Scott Silvi
1
AFAIK, a referência de objeto js funciona para tudo no Serviço. Codificação como esta: $ scope.controllerVar = ServiceVar, tudo o que você faz em $ scope.controllerVar também é feito no ServiceVar.
Kai Wang
A @KaiWang concorda que, a menos que você decida usar o DefineAttribute, poderá fazer com que seus serviços tenham uma função setter para impedir que os controladores alterem acidentalmente seus dados de serviço.
Zalaboza
12

Eu acho que essa pergunta tem um componente contextual.

Se você está simplesmente puxando dados de um serviço e irradiando essas informações para a visualização, acho que a ligação direta à propriedade do serviço é ótima. Eu não quero escrever muito código clichê para simplesmente mapear propriedades de serviço para modelar propriedades para consumir em minha opinião.

Além disso, o desempenho em angular é baseado em duas coisas. A primeira é quantas ligações existem em uma página. O segundo é o quão caras são as funções getter. Misko fala sobre isso aqui

Se você precisar executar lógica específica da instância nos dados do serviço (em oposição à massagem de dados aplicada no próprio serviço), e o resultado disso impactar o modelo de dados exposto à exibição, eu diria que um $ watcher é apropriado, como desde que a função não seja terrivelmente cara. No caso de uma função cara, eu sugeriria que os resultados fossem armazenados em cache em uma variável local (ao controlador), executando suas operações complexas fora da função $ watcher e vinculando seu escopo ao resultado disso.

Como uma ressalva, você não deve suspender nenhuma propriedade diretamente do seu escopo $. A $scopevariável NÃO é o seu modelo. Tem referências ao seu modelo.

Na minha opinião, a "melhor prática" para simplesmente irradiar informações do serviço para baixo para exibição:

function TimerCtrl1($scope, Timer) {
  $scope.model = {timerData: Timer.data};
};

E então sua visão conteria {{model.timerData.lastupdated}}.

Scott Silvi
fonte
cuidado com essa sugestão, alguém que não seja especialista em javascript pode tentar fazer isso com uma propriedade que seja uma string. Nesse caso, o javascript não faz uma referência ao objeto, mas copia a string em bruto. (e isso é ruim porque ele não é atualizado, se ele for alterado)
Valerio
7
Eu não cobri isso com a minha 'advertência' de que você sempre deve usar o ponto (ou seja, não use $ scope, mas off $ scope.model). Se você tiver $ scope.model.someStringProperty e fizer referência a model.someStringProperty na sua visualização, ele será atualizado, pois os observadores internos estarão no objeto, não no prop.
Scott Silvi
6

Com base nos exemplos acima, pensei em lançar uma maneira de vincular de forma transparente uma variável de controlador a uma variável de serviço.

No exemplo abaixo, as alterações na $scope.countvariável Controller serão refletidas automaticamente na countvariável Service .

Na produção, estamos usando a ligação this para atualizar um ID em um serviço que, em seguida, busca assincronamente dados e atualiza seus vars de serviço. Vinculação adicional que significa que os controladores são atualizados automaticamente quando o serviço é atualizado.

O código abaixo pode ser visto trabalhando em http://jsfiddle.net/xuUHS/163/

Visão:

<div ng-controller="ServiceCtrl">
    <p> This is my countService variable : {{count}}</p>
    <input type="number" ng-model="count">
    <p> This is my updated after click variable : {{countS}}</p>

    <button ng-click="clickC()" >Controller ++ </button>
    <button ng-click="chkC()" >Check Controller Count</button>
    </br>

    <button ng-click="clickS()" >Service ++ </button>
    <button ng-click="chkS()" >Check Service Count</button>
</div>

Serviço / Controlador:

var app = angular.module('myApp', []);

app.service('testService', function(){
    var count = 10;

    function incrementCount() {
      count++;
      return count;
    };

    function getCount() { return count; }

    return {
        get count() { return count },
        set count(val) {
            count = val;
        },
        getCount: getCount,
        incrementCount: incrementCount
    }

});

function ServiceCtrl($scope, testService)
{

    Object.defineProperty($scope, 'count', {
        get: function() { return testService.count; },
        set: function(val) { testService.count = val; },
    });

    $scope.clickC = function () {
       $scope.count++;
    };
    $scope.chkC = function () {
        alert($scope.count);
    };

    $scope.clickS = function () {
       ++testService.count;
    };
    $scope.chkS = function () {
        alert(testService.count);
    };

}
TomDotTom
fonte
Esta é uma solução incrível, obrigado, isso me ajudou muito, maneira muito inteligente de fazê-lo :)
Javis Perez
2

Eu acho que é uma maneira melhor de vincular o serviço em si, em vez dos atributos nele.

Aqui está o porquê:

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">

  <div ng-controller="BindToServiceCtrl as ctrl">
    ArrService.arrOne: <span ng-repeat="v in ArrService.arrOne">{{v}}</span>
    <br />
    ArrService.arrTwo: <span ng-repeat="v in ArrService.arrTwo">{{v}}</span>
    <br />
    <br />
    <!-- This is empty since $scope.arrOne never changes -->
    arrOne: <span ng-repeat="v in arrOne">{{v}}</span>
    <br />
    <!-- This is not empty since $scope.arrTwo === ArrService.arrTwo -->
    <!-- Both of them point the memory space modified by the `push` function below -->
    arrTwo: <span ng-repeat="v in arrTwo">{{v}}</span>
  </div>

  <script type="text/javascript">
    var app = angular.module("BindToService", []);

    app.controller("BindToServiceCtrl", function ($scope, ArrService) {
      $scope.ArrService = ArrService;
      $scope.arrOne = ArrService.arrOne;
      $scope.arrTwo = ArrService.arrTwo;
    });

    app.service("ArrService", function ($interval) {
      var that = this,
          i = 0;
      this.arrOne = [];
      that.arrTwo = [];

      $interval(function () {
        // This will change arrOne (the pointer).
        // However, $scope.arrOne is still same as the original arrOne.
        that.arrOne = that.arrOne.concat([i]);

        // This line changes the memory block pointed by arrTwo.
        // And arrTwo (the pointer) itself never changes.
        that.arrTwo.push(i);
        i += 1;
      }, 1000);

    });
  </script>
</body> 

Você pode jogar neste plunker .

Trantor Liu
fonte
1

Prefiro manter meus observadores o menos possível. Minha razão é baseada em minhas experiências e pode-se argumentar teoricamente.
O problema com o uso de observadores é que você pode usar qualquer propriedade no escopo para chamar qualquer um dos métodos em qualquer componente ou serviço que desejar.
Em um projeto do mundo real, em breve você terminará com uma cadeia de métodos não rastreáveis (melhor dizendo, difícil de rastrear) sendo chamada e valores alterados, o que torna especialmente trágico o processo de integração.

Reyraa
fonte
0

Vincular qualquer dado que envie serviço não é uma boa ideia (arquitetura), mas se você precisar mais, sugiro duas maneiras de fazer isso

1) você pode obter os dados que não estão dentro do seu serviço. Você pode obter dados dentro do seu controlador / diretiva e não terá problemas para vinculá-los a qualquer lugar

2) você pode usar os eventos angularjs. Sempre que quiser, você pode enviar um sinal (de $ rootScope) e capturá-lo onde quiser. Você pode até enviar dados sobre esse eventName.

Talvez isso possa te ajudar. Se você precisar de mais exemplos, aqui está o link

http://www.w3docs.com/snippets/angularjs/bind-value-between-service-and-controller-directive.html

Hazarapet Tunanyan
fonte
-1

A respeito

scope = _.extend(scope, ParentScope);

Onde ParentScope é um serviço injetado?

tomascharad
fonte
-2

As soluções mais elegantes ...

app.service('svc', function(){ this.attr = []; return this; });
app.controller('ctrl', function($scope, svc){
    $scope.attr = svc.attr || [];
    $scope.$watch('attr', function(neo, old){ /* if necessary */ });
});
app.run(function($rootScope, svc){
    $rootScope.svc = svc;
    $rootScope.$watch('svc', function(neo, old){ /* change the world */ });
});

Além disso, eu escrevo EDAs (arquiteturas orientadas a eventos), de modo que costumo fazer algo como a seguinte [versão simplificada]:

var Service = function Service($rootScope) {
    var $scope = $rootScope.$new(this);
    $scope.that = [];
    $scope.$watch('that', thatObserver, true);
    function thatObserver(what) {
        $scope.$broadcast('that:changed', what);
    }
};

Em seguida, coloquei um ouvinte no meu controlador no canal desejado e apenas mantive meu escopo local atualizado dessa maneira.

Em conclusão, não há muito de um "Best Practice" - em vez disso, a sua principalmente preferência - contanto que você está mantendo coisas sólidas e empregando acoplamento fraco. A razão pela qual eu defenderia esse último código é porque as EDAs têm o menor acoplamento possível por natureza. E se você não estiver muito preocupado com esse fato, evitemos trabalhar juntos no mesmo projeto.

Espero que isto ajude...

Cody
fonte