Passando o escopo atual para um serviço AngularJS

106

É correto passar a "corrente" $scopepara um serviço AngularJS?

Estou em uma situação em que tenho um $ serviço sabendo que é consumido por apenas um controlador e gostaria de ter uma referência ao escopo do controlador nos próprios métodos de $ serviço.

Isso é filosoficamente correto?

Ou é melhor transmitir eventos para $ rootScope e depois fazer meu controlador ouvi-los?

SC
fonte
1
Você pode nos contar mais especificamente o que está tentando fazer? Talvez empurrar o escopo para o serviço não seja necessário?
ganaraj
Bem, não é tão difícil. Simplesmente, gostaria de poder acessar as $scopepropriedades e ligar $scope.$applyquando necessário.
SC
Além disso, digamos que eu queira aplicar as alterações provenientes do $ serviço no $ escopo. Espero que esteja mais claro agora.
SC
8
Eu sugiro que você coloque as propriedades $ scope que deseja que seu serviço acesse no próprio serviço (em vez de tê-las no controlador). Os serviços são lugares melhores para armazenar modelos / dados do que os controladores.
Mark Rajcok
@MarkRajcock Estou tentando entender esse problema também. Atualmente estou apenas chamando um serviço e anexando os dados servidos ao controlador $scope... como o controlador acessaria diretamente os dados no serviço e os passaria para a visualização sem fazer isso?
drjimmie1976

Respostas:

67

Para permitir que o controlador saiba quando algo assíncrono acontece, use promessas angulares .

Para provocar o $apply, você não precisa do escopo, você pode chamar $rootScope.$apply, pois não há diferença chamá-lo em um escopo específico ou na raiz.

Em relação à leitura da variável, seria melhor se você recebesse parâmetros. Mas você também pode lê-lo de um escopo como um parâmetro de objeto, mas eu escolheria o parâmetro, que tornaria sua interface de serviço muito mais clara.

Caio cunha
fonte
Acho que essa é a resposta que melhor resolve minhas dúvidas de iniciante em AngularJS.
SC
@Caio Cunha Você poderia explicar por que não é uma boa ideia passar um escopo? Estou tendo exatamente esse problema. Desejo adicionar algumas coisas a $scopeatravés de uma chamada para um serviço usando uma executeSql()função assíncrona . Olhando para 3 opções (1) usar um retorno de chamada na função assíncrona, então chamar $scope.$apply... isso funciona, mas é feio (2) passar $scopepara a função assíncrona, então chamar theScope.$apply()... isso também funciona (3) usar uma promessa. ..não tentei isso ainda. Por que uma promessa é a melhor maneira? Obrigado!
drjimmie1976
15

Eu diria que se sua funcionalidade é específica para apenas um controlador, você não precisa de um serviço.

As tarefas dos controladores são manipular o modelo específico, enquanto um serviço deve lidar com tarefas globais. Prefiro seguir esse paradigma em vez de confundir as coisas.

Isso é o que os documentos dizem

Serviço

Serviços angulares são singletons que realizam tarefas específicas comuns a aplicativos da web

Controlador

No Angular, um controlador é uma função JavaScript (tipo / classe) que é usada para aumentar as instâncias do Escopo angular, excluindo o escopo raiz.

PS: Além disso, se você precisar digerir, você também pode injetar o $ rootScope no seu serviço.

F Lekschas
fonte
2
Obrigado. Ótima resposta. Analisando profundamente a minha situação, a questão é que estou usando o Service porque quero um único. Há apenas um controlador consumindo o serviço, mas esse controlador pode ser instanciado várias vezes durante o ciclo de vida do aplicativo, então eu realmente quero que o serviço esteja sempre no mesmo estado.
SC
De qualquer forma, o esclarecimento sobre como chamar $applyou $digestpara o $ rootScope faz todo o sentido para mim.
SC
1
Eu ainda o manteria separado em um Serviço para teste.
bluehallu de
Se a funcionalidade for específica para uma instância de um controlador, você pode pular a implementação de um serviço, mas caso contrário, não, pois você sacrifica a capacidade de ter o estado compartilhado entre essas instâncias. Além disso, só porque há um único tipo de controlador hoje, isso não significa que não haverá um controlador diferente que possa aproveitar a funcionalidade amanhã. Além disso, um serviço pode evitar o inchaço do controlador. Portanto, não é tanto uma questão de saber se há um único controlador, mas qual é a funcionalidade que está em questão.
Nick
9

Sim. Você pode passar o $ escopo para o serviço ao inicializá-lo. No construtor de serviço, você pode atribuir o escopo a algo como this._scope e então referenciar o escopo dentro do serviço!

angular.module('blah').controller('BlahCtrl', function($scope, BlahService) {

    $scope.someVar = 4;

    $scope.blahService = new blahService($scope);

});

angular.module('blah').factory('blahService', function() {

    //constructor
    function blahService(scope) {
        this._scope = scope;

        this._someFunction()
    }

    //wherever you'd reference the scope
    blahService.prototype._someFunction = function() {

        this._scope['someVar'] = 5;

    }

    return blahService;

});
user12121234
fonte
1
+1, porém, gostaria de ver uma maneira de saber automaticamente cada controlador $scopesempre que aquele determinado controlador injetar o serviço - para não ter que chamar um método no serviço e passar manualmente $scopepara ele.
Cody
2
@Cody Eu não recomendo isso, pois é contraditório com Dependency Injection
Coldstar
Combinado, +1 por atirar em mim! Provavelmente açougueiros DIP também - eu diria, correndo o risco de ser redundante.
Cody
Você está misturando serviços com fábricas aqui. Esta solução usa uma fábrica, que difere de um serviço. A principal diferença é que um serviço retorna um objeto (singleton). Enquanto uma fábrica retorna uma função, que pode ser instanciada ( new MyFunction()). A pergunta era sobre um serviço, onde ligar newnão é uma opção.
Karvapallo
@Karvapallo Bom argumento. Como contraponto, acredito que os serviços angulares se referem a uma fábrica, serviço e provedor (ng-wat).
user12121234
6

Pessoalmente, acredito que passar $scopepara um serviço é uma má ideia , porque cria uma referência meio circular: o controlador depende do serviço e o serviço depende do escopo do controlador.

Além de confundir as relações, coisas como essa acabam atrapalhando o catador de lixo.

Minha abordagem preferida é colocar um objeto de domínio no escopo do controlador e passá-lo para o serviço. Desta forma, o serviço funciona independentemente de ser usado dentro de um controlador ou talvez dentro de outro serviço no futuro.

Por exemplo, se o serviço deve enviar e remover elementos de uma matriz errors, meu código será:

var errors = [];
$scope.errors = errors;
$scope.myService = new MyService(errors);

O serviço interage então com o controlador operando em errors. É claro que tenho que ser cauteloso em nunca apagar a referência de array inteira, mas no final do dia essa é uma preocupação geral de JS.

Eu nunca gostaria de usar transmissão, $applye / ou coisas semelhantes, porque em mim, as boas práticas OO sempre superarão qualquer magia Angular.

Marco Faustinelli
fonte
1
Este código tem alguma diferença com este ?:$scope.errors = []; $scope.myService = new MyService($scope.errors);
Soldeplata Saketos
@SoldeplataSaketos - Sim, é verdade. errorsvive independentemente de $scope. Esse é o ponto principal desta resposta. Por favor, verifique o link que forneci no texto. Felicidades.
Marco Faustinelli
1
Se bem entendi, seu código $scope.errorsestá apontando para var errors, e os erros de variável parecem redundantes para mim, pois é apenas outro ponteiro. Uma situação semelhante que eu posso pensar e que é descaradamente redundante é este pedaço de código: const errors = errors2 = errors3 = []; $scope.errors = errors;. Você concorda que apenas com a parte do código que você forneceu parece var errors = []redundante?
Soldeplata Saketos
Não, não é. Repito-me palavra por palavra: errorsvive independentemente de $scope. Você precisa entender o que é um objeto de domínio, bem como o que é uma varatribuição. Se o link que forneci não for suficiente, há muitos outros materiais disponíveis.
Marco Faustinelli,
Bem, isso vai depender exclusivamente da implementação da função MyService(errors). No meu entendimento, o serviço deve gerar uma matriz de registro com base no parâmetro (neste caso, um ponteiro). Para mim, esse é um padrão ruim, pois os serviços são singletons no angular. Se a implementação do serviço estiver bem programada, deve-se gerar o array em uma variável interna (para continuar sendo um singleton). Portanto, não faz sentido inicializar a variável fora do serviço.
Soldeplata Saketos