AngularJS. Como chamar a função do controlador de fora do componente do controlador

190

Como posso chamar a função definida no controlador a partir de qualquer local da página da web (fora do componente do controlador)?

Funciona perfeitamente quando pressiono o botão "obter". Mas eu preciso chamá-lo de fora do controlador div. A lógica é: por padrão, minha div está oculta. Em algum lugar do menu de navegação, pressiono um botão e ele deve mostrar () minha div e executar a função "get". Como eu posso conseguir isso?

Minha página da web é:

<div ng-controller="MyController">
  <input type="text" ng-model="data.firstname" required>
  <input type='text' ng-model="data.lastname" required>

  <form ng-submit="update()"><input type="submit" value="update"></form>
  <form ng-submit="get()"><input type="submit" value="get"></form>
</div>

Meus js:

   function MyController($scope) {
      // default data and structure
      $scope.data = {
        "firstname" : "Nicolas",
        "lastname" : "Cage"
      };

      $scope.get = function() {
        $.ajax({
           url: "/php/get_data.php?",
           type: "POST",
           timeout: 10000, // 10 seconds for getting result, otherwise error.
           error:function() { alert("Temporary error. Please try again...");},
           complete: function(){ $.unblockUI();},
           beforeSend: function(){ $.blockUI()},
           success: function(data){
            json_answer = eval('(' + data + ')');
            if (json_answer){
                $scope.$apply(function () {
                  $scope.data = json_answer;
            });
            }
        }
    });
  };

  $scope.update = function() {
    $.ajax({
        url: "/php/update_data.php?",
        type: "POST",
        data: $scope.data,
        timeout: 10000, // 10 seconds for getting result, otherwise error.
        error:function() { alert("Temporary error. Please try again...");},
        complete: function(){ $.unblockUI();},
        beforeSend: function(){ $.blockUI()},
        success: function(data){ }
      });
    };
   }
Pavel Zdarov
fonte
2
Quando você diz "... em algum lugar do menu de navegação, pressiona um botão ...", você quer dizer que essa navegação faz parte de outro controlador e deseja chamar o get()MyController a partir de outro controlador?
Callmekatootie
1
Por enquanto, o menu de navegação não é um controlador. Apenas html. Não tenho certeza se é possível chamar a função de controlador de html / javascript, é por isso que postei esta pergunta. Mas sim, lógico para tornar o menu de navegação como um controlador separado. Como posso chamar a função MyController.get () do NavigationMenu.Controller?
Pavel Zdarov

Respostas:

331

Aqui está uma maneira de chamar a função do controlador de fora dela:

angular.element(document.getElementById('yourControllerElementID')).scope().get();

onde get()é uma função do seu controlador.

Você pode mudar

document.getElementById('yourControllerElementID')` 

para

$('#yourControllerElementID')

Se você estiver usando jQuery.

Além disso, se sua função significa alterar alguma coisa na sua Visualização, você deve chamar

angular.element(document.getElementById('yourControllerElementID')).scope().$apply();

para aplicar as alterações.

Mais uma coisa, você deve observar que os escopos são inicializados após o carregamento da página, portanto, métodos de chamada fora do escopo sempre devem ser feitos após o carregamento da página. Caso contrário, você não chegará ao escopo.

ATUALIZAR:

Com as versões mais recentes do angular, você deve usar

angular.element(document.getElementById('yourControllerElementID')).injector().‌​get('$rootScope')

E sim, isso é, de fato, uma prática ruim , mas às vezes você só precisa de coisas feitas de maneira rápida e suja.

Dmitry Mina
fonte
30
Este parece ser um cheiro de código desde a filosofia de angular é não misturar código DOM com código Angular ...
JoeCool
8
portanto, se você não deve misturar o código DOM com o código angular, se deseja que certas animações do JQuery sejam acionadas em reação a uma alteração variável no seu controlador Angular, como exatamente você faz isso? Seria trivial para fazer a partir do controlador, mas não tenho idéia de como fazê-lo de forma limpa
Jan
3
Não consegui fazer com que a $applypeça funcionasse para mim, até envolver o código em uma função, por exemplo..scope.$apply(function() { scope.get() });
surfitscrollit
2
Na verdade, eu poderia acessar o controlador com angular.element(document.getElementById('yourControllerElementID')).scope().controller; For ex. if use: angular.element(document.getElementById('controller')).scope().get() lança e erro indefinido, mas se eu usá- angular.element(document.getElementById('controller')).scope().controller.get()lo funciona.
vrunoa
2
É possível que essa solução não funcione mais no Angular 1.4.9? Não consigo acessar scope()no angular.element(...), porque ele retorna indefinido e um vardump do elemento / objeto angular diz que a função scopeestá localizada dentro do __proto__objeto-.
Smamatti 9/02/16
37

Eu encontrei um exemplo na internet.

Um cara escreveu esse código e funcionou perfeitamente

HTML

<div ng-cloak ng-app="ManagerApp">
    <div id="MainWrap" class="container" ng-controller="ManagerCtrl">
       <span class="label label-info label-ext">Exposing Controller Function outside the module via onClick function call</span>
       <button onClick='ajaxResultPost("Update:Name:With:JOHN","accept",true);'>click me</button>
       <br/> <span class="label label-warning label-ext" ng-bind="customParams.data"></span>
       <br/> <span class="label label-warning label-ext" ng-bind="customParams.type"></span>
       <br/> <span class="label label-warning label-ext" ng-bind="customParams.res"></span>
       <br/>
       <input type="text" ng-model="sampletext" size="60">
       <br/>
    </div>
</div>

JAVASCRIPT

var angularApp = angular.module('ManagerApp', []);
angularApp.controller('ManagerCtrl', ['$scope', function ($scope) {

$scope.customParams = {};

$scope.updateCustomRequest = function (data, type, res) {
    $scope.customParams.data = data;
    $scope.customParams.type = type;
    $scope.customParams.res = res;
    $scope.sampletext = "input text: " + data;
};



}]);

function ajaxResultPost(data, type, res) {
    var scope = angular.element(document.getElementById("MainWrap")).scope();
    scope.$apply(function () {
    scope.updateCustomRequest(data, type, res);
    });
}

Demo

* Fiz algumas modificações, veja o original: font JSfiddle

Roger Ramos
fonte
2
Obrigado Roger, muito útil!
Eduardo
Trabalhando com uma biblioteca legada de validação jQuery que DEVE ser usada. Então, é quer 1: reescrever a biblioteca, 2: criar directiva para embrulhar a biblioteca, 3: 2 linhas de código para chamar o angular submeter quando válida ...
Michael K
se eu não usar apply, a função que atualizará os dados da visualização não será refletida?
Monojit Sarkar
13

A solução angular.element(document.getElementById('ID')).scope().get()parou de funcionar para mim no angular 1.5.2. Sombody menciona em um comentário que isso também não funciona na versão 1.4.9. Corrigi-o armazenando o escopo em uma variável global:

var scopeHolder;
angular.module('fooApp').controller('appCtrl', function ($scope) {
    $scope = function bar(){
        console.log("foo");        
    };
    scopeHolder = $scope;
})

chamada do código personalizado:

scopeHolder.bar()

se você deseja restringir o escopo apenas a este método. Minimizar a exposição de todo o escopo. use a seguinte técnica.

var scopeHolder;
angular.module('fooApp').controller('appCtrl', function ($scope) {
    $scope.bar = function(){
        console.log("foo");        
    };
    scopeHolder = $scope.bar;
})

chamada do código personalizado:

scopeHolder()
user1121883
fonte
Isso funcionou muito bem para mim (mesmo de dentro de um componente). Existe uma desvantagem em fazer isso além da "prática ruim de chamar coisas angulares de fora da angular" que não consigo evitar no meu cenário? stackoverflow.com/questions/42123120/…
RichC
Graças senhor por sua ajuda eu estava procurando por uma solução para isso a partir de um tempo
Ibrahim Amer
11

A resposta de Dmitry funciona bem. Acabei de fazer um exemplo simples usando a mesma técnica.

jsfiddle: http://jsfiddle.net/o895a8n8/5/

<button onclick="call()">Call Controller's method from outside</button>
<div  id="container" ng-app="" ng-controller="testController">
</div>

.

function call() {
    var scope = angular.element(document.getElementById('container')).scope();
      scope.$apply(function(){
        scope.msg = scope.msg + ' I am the newly addded message from the outside of the controller.';
    })
    alert(scope.returnHello());
}

function testController($scope) {
    $scope.msg = "Hello from a controller method.";
    $scope.returnHello = function() {
        return $scope.msg ; 
    }
}
Razan Paul
fonte
7

Prefiro incluir a fábrica como dependências dos controladores do que injetar sua própria linha de código: http://jsfiddle.net/XqDxG/550/

myModule.factory('mySharedService', function($rootScope) {
    return sharedService = {thing:"value"};
});

function ControllerZero($scope, mySharedService) {
    $scope.thing = mySharedService.thing;

ControllerZero. $ Inject = ['$ scope', 'mySharedService'];

getsetbro
fonte
Hmm. @Anton comentou um violino abaixo (em maio de 13) que faz AMBOS.
Jesse Chisholm
5

Pode valer a pena considerar se ter o seu menu sem escopo associado é o caminho certo a seguir. Não é realmente o caminho angular.

Mas, se for o caminho que você precisa seguir, poderá fazê-lo adicionando as funções ao $ rootScope e, nessas funções, usando $ broadcast para enviar eventos. seu controlador usa $ on para ouvir esses eventos.

Outra coisa a considerar se você acabar tendo seu menu sem escopo é que, se você tiver várias rotas, todos os seus controladores precisarão ter suas próprias atualizações e obter funções. (isso pressupõe que você tenha vários controladores)

Anton
fonte
você pode dar um exemplo simples de como chamar a função .get () do ControllerOne do ControllerTwo? minha lógica é que cada controlador tenha suas próprias funções .get () .update (). Terei MainMenuController a partir do qual preciso executar (de acordo com o item de menu) .get () do controlador necessário.
Pavel Zdarov
ps, não meu código, mas mostra como ter vários controlers partilha de funcionalidade
Anton
4

Eu uso para trabalhar com $ http, quando um deseja obter algumas informações de um recurso, faço o seguinte:

angular.module('services.value', [])

.service('Value', function($http, $q) {

var URL = "http://localhost:8080/myWeb/rest/";

var valid = false;

return {
    isValid: valid,
    getIsValid: function(callback){
        return $http.get(URL + email+'/'+password, {cache: false})
                    .success(function(data){
            if(data === 'true'){ valid = true; }
        }).then(callback);
    }}
    });

E o código no controlador:

angular.module('controllers.value', ['services.value'])

.controller('ValueController', function($scope, Value) {
    $scope.obtainValue = function(){
        Value.getIsValid(function(){$scope.printValue();});
    }

    $scope.printValue = function(){
        console.log("Do it, and value is " Value.isValid);
    }
}

Envio para o serviço que função tem que chamar no controlador

rodrimmb
fonte
3

Como tenho várias rotas e vários controladores, não consegui obter a resposta aceita para funcionar. Eu descobri que adicionar a função à janela funciona:

fooModule.controller("fooViewModel", function ($scope, fooService, $http, $q, $routeParams, $window, $location, viewModelHelper, $interval) {
    $scope.initFoo = function () {
        // do angular stuff
    }
    var initialize = function () {
        $scope.initFoo();
    }

    initialize();

    window.fooreinit = initialize;

}

Então, fora do controlador, isso pode ser feito:

function ElsewhereOnThePage() {
    if (typeof(fooreinit) == 'function') { fooreinit(); }
}
jaybro
fonte
0

Chame a função Âmbito Angular de fora do controlador.

// Simply Use "Body" tag, Don't try/confuse using id/class.

var scope = angular.element('body').scope();             
scope.$apply(function () {scope.YourAngularJSFunction()});      
Sam Ruben
fonte
-1

Eu sou um usuário da estrutura Ionic e o que eu achei que forneceria consistentemente o escopo $ do controlador atual é:

angular.element(document.querySelector('ion-view[nav-view="active"]')).scope()

Suspeito que isso possa ser modificado para se ajustar à maioria dos cenários, independentemente da estrutura (ou não), localizando a consulta que terá como alvo os elementos DOM específicos que estão disponíveis apenas durante uma determinada instância do controlador.

Matt Ray
fonte