AngularJS. Comment appeler la fonction du contrôleur depuis l'extérieur du composant du contrôleur

190

Comment puis-je appeler la fonction définie sous le contrôleur à partir de n'importe quel endroit de la page Web (en dehors du composant du contrôleur)?

Cela fonctionne parfaitement lorsque j'appuie sur le bouton "get". Mais je dois l'appeler de l'extérieur du contrôleur div. La logique est la suivante: par défaut, mon div est masqué. Quelque part dans le menu de navigation, j'appuie sur un bouton et il devrait afficher () mon div et exécuter la fonction "get". Comment puis-je y parvenir?

Ma page Web est:

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

Mon 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
la source
2
Lorsque vous dites "... quelque part dans le menu de navigation vous appuyez sur un bouton ...", voulez-vous dire que cette navigation fait partie d'un autre contrôleur et que vous souhaitez appeler le get()de MyController depuis un autre contrôleur?
callmekatootie
1
Pour l'instant, le menu de navigation n'est pas un contrôleur. Juste html. Je ne sais pas s'il est possible d'appeler la fonction de contrôleur à partir de html / javascript, c'est pourquoi j'ai posté cette question. Mais oui, logique de faire du menu de navigation un contrôleur séparé. Comment puis-je appeler la fonction MyController.get () à partir de NavigationMenu.Controller?
Pavel Zdarov

Réponses:

331

Voici un moyen d'appeler la fonction du contrôleur de l'extérieur:

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

get()est une fonction de votre contrôleur.

Vous pouvez changer

document.getElementById('yourControllerElementID')` 

à

$('#yourControllerElementID')

Si vous utilisez jQuery.

De plus, si votre fonction implique de changer quoi que ce soit sur votre vue, vous devez appeler

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

pour appliquer les modifications.

Une autre chose à noter est que les étendues sont initialisées après le chargement de la page, donc l'appel de méthodes en dehors de l'étendue doit toujours être fait après le chargement de la page. Sinon, vous n'obtiendrez pas du tout la portée.

METTRE À JOUR:

Avec les dernières versions d'angular, vous devez utiliser

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

Et oui, c'est, en fait, une mauvaise pratique , mais parfois vous avez juste besoin que les choses soient faites vite et sale.

Dmitry Mina
la source
30
Cela semble être une odeur de code puisque la philosophie d'Angular n'est pas de mélanger le code DOM avec le code Angular ...
JoeCool
8
Donc, si vous n'êtes pas censé mélanger le code DOM avec le code Angular, si vous voulez que certaines animations JQuery se déclenchent en réaction à un changement de variable dans votre contrôleur Angular, comment faites-vous exactement cela? Ce serait trivial à faire à partir du contrôleur, mais je ne sais pas comment le faire proprement
janvier
3
Je ne pouvais pas faire fonctionner la $applypièce pour moi, jusqu'à ce que j'encapsule le code dans une fonction, par exemple..scope.$apply(function() { scope.get() });
surfitscrollit
2
En fait, je pourrais accéder au contrôleur avec angular.element(document.getElementById('yourControllerElementID')).scope().controller; For ex. si utilisation: angular.element(document.getElementById('controller')).scope().get() jette et erreur non définie, mais si je l'utilise, angular.element(document.getElementById('controller')).scope().controller.get()cela fonctionne.
vrunoa
2
Est-il possible que cette solution ne fonctionne plus dans Angular 1.4.9? Je ne peux pas accéder scope()dans le angular.element(...), car il renvoie undefined et un vardump de l'élément / objet angulaire indique que la fonction scopeest située dans le __proto__-object.
Smamatti le
37

J'ai trouvé un exemple sur Internet.

Un gars a écrit ce code et a parfaitement fonctionné

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

Démo

* J'ai fait quelques modifications, voir l'original: police JSfiddle

Roger Ramos
la source
2
Merci Roger, très utile!
Eduardo du
Travailler avec une bibliothèque de validation jQuery héritée qui DOIT être utilisée. Donc, c'est soit 1: réécrire la bibliothèque, 2: créer une directive pour envelopper la bibliothèque, 3: 2 lignes de code pour appeler la soumission angulaire lorsqu'elle est valide ...
Michael K
si je n'utilise pas appliquer, la fonction qui mettra à jour les données de vue ne sera pas reflétée?
Monojit Sarkar
13

La solution a angular.element(document.getElementById('ID')).scope().get()cessé de fonctionner pour moi dans angulaire 1.5.2. Sombody mentionne dans un commentaire que cela ne fonctionne pas non plus dans la version 1.4.9. Je l'ai corrigé en stockant la portée dans une variable globale:

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

appel depuis un code personnalisé:

scopeHolder.bar()

si vous souhaitez limiter la portée à cette méthode uniquement. Pour minimiser l'exposition de toute la portée. utilisez la technique suivante.

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

appel depuis un code personnalisé:

scopeHolder()
user1121883
la source
Cela a très bien fonctionné pour moi (même de l'intérieur d'un composant). Y a-t-il un inconvénient à faire cela autre que la simple "mauvaise pratique d'appeler des trucs angulaires de l'extérieur anguleux" que je ne peux pas éviter dans mon scénario? stackoverflow.com/questions/42123120/…
RichC
Merci monsieur pour votre aide Je cherchais une solution pour cela depuis un certain temps
Ibrahim Amer
11

La réponse de Dmitry fonctionne très bien. Je viens de faire un exemple simple en utilisant la même technique.

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

Je préfère inclure l'usine en tant que dépendances sur les contrôleurs plutôt que de leur injecter leur propre ligne de code: 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'];

getetbro
la source
Hmm. @Anton a commenté un violon ci-dessous (en mai 2013) qui fait LES DEUX.
Jesse Chisholm
5

Il peut être utile de se demander si le fait d'avoir votre menu sans portée associée est la bonne voie à suivre. Ce n'est pas vraiment la voie angulaire.

Mais, si c'est la voie à suivre, vous pouvez le faire en ajoutant les fonctions à $ rootScope, puis dans ces fonctions en utilisant $ broadcast pour envoyer des événements. votre contrôleur utilise ensuite $ on pour écouter ces événements.

Une autre chose à considérer si vous finissez par avoir votre menu sans portée est que si vous avez plusieurs routes, tous vos contrôleurs devront avoir leur propre mise à jour et obtenir des fonctions. (cela suppose que vous avez plusieurs contrôleurs)

Anton
la source
pouvez-vous donner un exemple simple de la façon d'appeler la fonction .get () de ControllerOne à partir de ControllerTwo? ma logique est que chaque contrôleur aura ses propres fonctions .get () .update (). J'aurai MainMenuController à partir duquel je dois exécuter (selon l'élément de menu) .get () du contrôleur nécessaire.
Pavel Zdarov
ps, pas mon code, mais il montre comment avoir plusieurs fonctionnalités de partage de contrôleurs
Anton
4

J'utilise pour travailler avec $ http, quand je veux obtenir des informations d'une ressource, je fais ce qui suit:

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

Et le code dans le contrôleur:

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

J'envoie au service quelle fonction doit appeler dans le contrôleur

rodrimmb
la source
3

J'ai plusieurs routes et plusieurs contrôleurs, donc je n'ai pas pu obtenir la réponse acceptée. J'ai trouvé que l'ajout de la fonction à la fenêtre fonctionne:

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;

}

Ensuite, en dehors du contrôleur, cela peut être fait:

function ElsewhereOnThePage() {
    if (typeof(fooreinit) == 'function') { fooreinit(); }
}
Jaybro
la source
0

Appelez la fonction Angular Scope depuis l'extérieur du contrôleur.

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

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

Je suis un utilisateur du framework Ionic et celui que j'ai trouvé qui fournirait systématiquement la portée $ du contrôleur actuel est:

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

Je soupçonne que cela peut être modifié pour s'adapter à la plupart des scénarios quel que soit le cadre (ou non) en trouvant la requête qui ciblera le ou les éléments DOM spécifiques qui ne sont disponibles que pendant une instance de contrôleur donnée.

Matt Ray
la source