angularJS: Como chamar a função de escopo filho no escopo pai

95

Como posso chamar um método definido no escopo filho de seu escopo pai?

function ParentCntl() {
    // I want to call the $scope.get here
}

function ChildCntl($scope) {
    $scope.get = function() {
        return "LOL";    
    }
}

http://jsfiddle.net/wUPdW/

9 azul
fonte
2
Melhor aposta, definir um serviço, injetar esse serviço em ambos os controladores. Ou use o rootscope.
tymeJV

Respostas:

145

Você pode usar $broadcastde pai para filho:

function ParentCntl($scope) {

    $scope.msg = "";
    $scope.get = function(){
        $scope.$broadcast ('someEvent');
        return  $scope.msg;        
    }
}

function ChildCntl($scope) {               
    $scope.$on('someEvent', function(e) {  
        $scope.$parent.msg = $scope.get();            
    });

    $scope.get = function(){
        return "LOL";    
    }
}

Trabalhando violino: http://jsfiddle.net/wUPdW/2/

ATUALIZAÇÃO : Existe outra versão, menos acoplada e mais testável:

function ParentCntl($scope) {
    $scope.msg = "";
    $scope.get = function(){
        $scope.$broadcast ('someEvent');
        return  $scope.msg;        
    }

    $scope.$on('pingBack', function(e,data) {  
        $scope.msg = data;        
    });
}

function ChildCntl($scope) {               
    $scope.$on('someEvent', function(e) {  
        $scope.$emit("pingBack", $scope.get());        
    });

    $scope.get = function(){
        return "LOL";    
    }
}

Fiddle: http://jsfiddle.net/uypo360u/

Cherniv
fonte
Isso é bem legal! Mas e se você não (quiser) saber onde está o escopo do chamador (quero dizer, em $scope.$parentou em $scope.$parent.$parentetc.)? Ah, sim: passe um callback em params! :)
user2173353
@ user2173353 você está certo; existe mais uma maneira: $emitde um filho para um pai. Acho que é hora de atualizar minha resposta.
Cherniv
É uma boa ideia chamar o ouvinte global onde está em algum lugar, controlador filho, neste caso? Não é antipadrão e difícil de testar depois? Não deveríamos usar injeções ou smth?
Calmbird
@calmbird, cada coisa deve ser usada com cuidado, isso é certo .. Alguns preferem usar serviços em casos semelhantes. De qualquer forma, adicionei uma versão mais elegante (sem irritar $parent)
Cherniv
9
Essa abordagem é mais limpa. Passe um callback no $broadcaste você pode eliminar o pingBackcompletamente.
poshest
34

Deixe-me sugerir outra solução:

var app = angular.module("myNoteApp", []);


app.controller("ParentCntl", function($scope) {
    $scope.obj = {};
});

app.controller("ChildCntl", function($scope) {
    $scope.obj.get = function() {
            return "LOL";    
    };
});

Menos código e usando herança prototípica.

Plunk

Canttouchit
fonte
Alguém poderia explicar se essa abordagem é melhor do que a abordagem baseada em eventos mencionada acima, em quais casos e se for, por quê? e se você tiver controladores filhos com hierarquia multi lvl? isso funcionaria se o obj.get fosse definido dentro de um controlador filho dentro de um controlador filho, desde que nenhum dos controladores tenha o mesmo nome de função definido? Agradeço antecipadamente.
ZvKa
1
Deixe-me corrigir o que disse, o que não é totalmente verdade. Você está correto no que disse: a herança de escopo é aplicada apenas a uma direção ... filho -> pai. O que realmente está acontecendo aqui é que se você definir $ scope.get no escopo filho, 'get' será definido apenas no escopo filho (às vezes referido como definição primitiva). Mas quando você define $ scope.obj.get, o escopo filho procurará obj, que é definido no escopo pai, que está acima da hierarquia.
Canttouchit
3
Como isso funcionará se a criança tiver seu próprio escopo isolado?
Jantar de
2
A questão não se referia ao escopo isolado. No entanto, se você tiver um escopo isolado, poderá usar a vinculação de escopo isolado. Imo, a transmissão é um pouco confusa.
Canttouchit
2
Eu não acho que este exemplo é possível se você precisar acessar a função do controlador de criança do pai controlador , não modelo. Este exemplo só funciona por causa da ligação bidirecional no modelo, que presumo que continue pesquisando até que $scope.obj.get()seja uma função válida.
danyim
11

Registre a função da criança no pai quando a criança estiver inicializando. Usei a notação "como" para maior clareza no modelo.

MODELO

<div ng-controller="ParentCntl as p">
  <div ng-controller="ChildCntl as c" ng-init="p.init(c.get)"></div>
</div>

CONTROLADORES

...
function ParentCntl() {
  var p = this;
  p.init = function(fnToRegister) {
    p.childGet = fnToRegister;
  };
 // call p.childGet when you want
}

function ChildCntl() {
  var c = this;
  c.get = function() {
    return "LOL";    
  };
}

"Mas", você diz, " ng-init não deve ser usado desta forma !". Bem sim, mas

  1. essa documentação não explica por que não, e
  2. Não acredito que os autores da documentação consideraram TODOS os casos de uso possíveis para ele.

Eu digo que este é um bom uso para isso. Se você quiser me negar, por favor, comente com os motivos! :)

Gosto dessa abordagem porque mantém os componentes mais modulares. As únicas ligações estão no modelo e significa que

  • o Controlador filho não precisa saber nada sobre a qual objeto adicionar sua função (como na resposta de @ canttouchit)
  • o controle pai pode ser usado com qualquer outro controle filho que tenha uma função get
  • não requer transmissão, o que ficará muito feio em um grande aplicativo, a menos que você controle rigidamente o namespace do evento

Essa abordagem se aproxima mais da ideia de Tero de modularização com diretivas (observe que, em seu exemplo modularizado, contestantsa diretiva é passada de pai para "filho" NO MODELO).

Na verdade, outra solução pode ser considerar a implementação do ChildCntlcomo uma diretiva e usar a &vinculação para registrar o initmétodo.

mais elegante
fonte
1
Eu sei que este é um post antigo, mas isso parece uma má ideia porque os pais podem manter uma referência à criança depois que ela for destruída.
Amy Blankenship
Esta é uma solução muito mais limpa do que transmitir eventos. Não é perfeito, mas melhor. Embora a limpeza seja realmente um problema.
mcv
-1

Você pode fazer um objeto filho.

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


app.controller("ParentCntl", function($scope) {
    $scope.child= {};
    $scope.get = function(){
      return $scope.child.get(); // you can call it. it will return 'LOL'
    }
   // or  you can call it directly like $scope.child.get() once it loaded.
});

app.controller("ChildCntl", function($scope) {
    $scope.obj.get = function() {
            return "LOL";    
    };
});

Aqui, a criança está provando o destino do método get.

Ramu Agrawal
fonte