AngularJS. Cómo llamar a la función del controlador desde fuera del componente del controlador

190

¿Cómo puedo llamar a la función definida bajo el controlador desde cualquier lugar de la página web (fuera del componente del controlador)?

Funciona perfectamente cuando presiono el botón "obtener". Pero necesito llamarlo desde fuera del controlador div. La lógica es: por defecto, mi div está oculto. En algún lugar del menú de navegación presiono un botón y debería mostrar () mi div y ejecutar la función "get". ¿Cómo puedo lograr esto?

Mi página web es:

<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>

Mi 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
fuente
2
Cuando dice "... en algún lugar del menú de navegación, presiona un botón ...", ¿quiere decir que esta navegación es parte de otro controlador y desea llamar a get()MyController desde otro controlador?
callmekatootie
1
Por ahora el menú de navegación no es un controlador. Solo html. No estoy seguro de si es posible llamar a la función del controlador desde html / javascript, por eso publiqué esta pregunta. Pero sí, es lógico hacer que el menú de navegación sea un controlador separado. ¿Cómo puedo llamar a la función MyController.get () desde NavigationMenu.Controller?
Pavel Zdarov

Respuestas:

331

Aquí hay una manera de llamar a la función del controlador desde afuera:

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

donde get()es una función de su controlador.

Puedes cambiar

document.getElementById('yourControllerElementID')` 

a

$('#yourControllerElementID')

Si está utilizando jQuery.

Además, si su función significa cambiar algo en su Vista, debe llamar

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

para aplicar los cambios.

Una cosa más, debe tener en cuenta es que los ámbitos se inicializan después de cargar la página, por lo que los métodos de llamada desde fuera del alcance siempre deben realizarse después de cargar la página. De lo contrario, no llegará al alcance en absoluto.

ACTUALIZAR:

Con las últimas versiones de angular, debe usar

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

Y sí, esta es, de hecho, una mala práctica , pero a veces solo necesitas hacer las cosas rápido y sucio.

Dmitry Mina
fuente
30
Esto parece un olor a código ya que la filosofía de Angular es no mezclar el código DOM con el código Angular ...
JoeCool
8
entonces, si no se supone que mezcle el código DOM con el código angular, si desea que ciertas animaciones JQuery se activen en respuesta a un cambio variable en su controlador Angular, ¿cómo exactamente hace eso? Sería trivial para hacerlo desde el controlador, pero no tengo ni idea de cómo hacerlo de forma limpia
Ene
3
No pude hacer que la $applypieza funcionara para mí, hasta que envolví el código en una función, por ejemplo..scope.$apply(function() { scope.get() });
surfitscrollit
2
De hecho, podría acceder al controlador con angular.element(document.getElementById('yourControllerElementID')).scope().controller; Por ej. if use: angular.element(document.getElementById('controller')).scope().get() throws y error indefinido, pero si lo uso angular.element(document.getElementById('controller')).scope().controller.get()funciona.
vrunoa
2
¿Es posible que esta solución ya no funcione en Angular 1.4.9? No puedo acceder scope()en el angular.element(...), porque devuelve indefinido y un vardump del elemento / objeto angular dice que la función scopese encuentra dentro del objeto __proto__.
Smamatti
37

He encontrado un ejemplo en internet.

Algún tipo escribió este código y funcionó perfectamente

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);
    });
}

Manifestación

* Hice algunas modificaciones, ver original: fuente JSfiddle

Roger Ramos
fuente
2
Gracias Roger, muy útil!
Eduardo
Trabajar con una biblioteca de validación jQuery heredada que DEBE usarse. Por lo tanto, es o bien 1: reescribir la biblioteca, 2: crear la directiva para envolver la biblioteca, 3: 2 líneas de código para llamar al presentar angular cuando válido ...
Michael K
si no uso apply, entonces la función que actualizará los datos de la vista no se reflejará?
Monojit Sarkar
13

La solución angular.element(document.getElementById('ID')).scope().get()dejó de funcionar para mí en angular 1.5.2. Sombody menciona en un comentario que esto tampoco funciona en 1.4.9. Lo arreglé almacenando el alcance en una variable global:

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

llamar desde código personalizado:

scopeHolder.bar()

si desea restringir el alcance solo a este método. Para minimizar la exposición de todo el alcance. use la siguiente técnica.

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

llamar desde código personalizado:

scopeHolder()
usuario1121883
fuente
Esto funcionó muy bien para mí (incluso desde el interior de un componente). ¿Hay alguna desventaja en hacer esto además de la "mala práctica para llamar a cosas angulares desde fuera angular" que no puedo evitar en mi escenario? stackoverflow.com/questions/42123120/…
RichC
Gracias señor por su ayuda yo estaba buscando una solución para esto desde hace tiempo
Ibrahim Amer
11

La respuesta de Dmitry funciona bien. Acabo de hacer un ejemplo simple usando la misma 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
fuente
7

Prefiero incluir la fábrica como dependencias de los controladores que inyectarles su propia línea 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
fuente
Hmm @Anton comentó un violín a continuación (en mayo '13) que AMBOS.
Jesse Chisholm
5

Puede valer la pena considerar si tener su menú sin ningún alcance asociado es el camino correcto. No es realmente la forma angular.

Pero, si es el camino que debe seguir, puede hacerlo agregando las funciones a $ rootScope y luego dentro de esas funciones usando $ broadcast para enviar eventos. su controlador luego usa $ on para escuchar esos eventos.

Otra cosa a tener en cuenta si termina teniendo su menú sin alcance es que si tiene varias rutas, entonces todos sus controladores tendrán que tener su propio upate y obtener funciones. (esto supone que tiene varios controladores)

Anton
fuente
¿Puedes dar algún ejemplo simple de cómo llamar a la función .get () de ControllerOne desde ControllerTwo? mi lógica es que cada controlador tendrá sus propias funciones .get () .update (). Tendré MainMenuController desde el que necesito ejecutar (de acuerdo con el elemento del menú) .get () del controlador necesario.
Pavel Zdarov
ps, no es mi código, pero muestra cómo tener múltiples controladores compartiendo funcionalidad
Anton
4

Solía ​​trabajar con $ http, cuando quiero obtener información de un recurso hago lo siguiente:

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);
    }}
    });

Y el código en el 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);
    }
}

Envío al servicio qué función debo llamar en el controlador

rodrimmb
fuente
3

Tengo múltiples rutas y múltiples controladores, por lo que no pude obtener la respuesta aceptada para trabajar. Descubrí que agregar la función a la ventana 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;

}

Luego, fuera del controlador, esto se puede hacer:

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

Llame a la función de alcance angular desde fuera del 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
fuente
-1

Soy un usuario de Ionic Framework y el que encontré que siempre proporcionaría el $ scope del controlador actual es:

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

Sospecho que esto se puede modificar para adaptarse a la mayoría de los escenarios, independientemente del marco (o no) al encontrar la consulta que se dirigirá a los elementos DOM específicos que están disponibles solo durante una instancia de controlador determinada.

Matt Ray
fuente