196

How I can call function defined under controller from any place of web page (outside of controller component)?

It works perfectly when I press "get" button. But I need to call it from outside of div controller. The logic is: by default my div is hidden. Somewhere in navigation menu I press a button and it should show() my div and execute "get" function. How I can achieve this?

My web page is:

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

My 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){ }
      });
    };
   }
Whitecat
  • 3,882
  • 7
  • 48
  • 78
Pavel Zdarov
  • 2,347
  • 3
  • 15
  • 23
  • 2
    When you say "...somewhere in navigation menu you press a button...", do you mean to say that this navigation is part of another controller and you wish to call the `get()` of MyController from the another controller? – callmekatootie May 23 '13 at 09:15
  • 1
    For now navigation menu is not a controller. Just html. Not sure if its possible to call controller function from html/javascript, thats why I posted this question. But yeah, logical to make navigation menu as separate controller. How I can call MyController.get() function from NavigationMenu.Controller ? – Pavel Zdarov May 23 '13 at 09:23

10 Answers10

339

Here is a way to call controller's function from outside of it:

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

where get() is a function from your controller.

You can switch

document.getElementById('yourControllerElementID')` 

to

$('#yourControllerElementID')

If you are using jQuery.

Also, if your function means changing anything on your View, you should call

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

to apply the changes.

One more thing, you should note is that scopes are initialized after the page is loaded, so calling methods from outside of scope should always be done after the page is loaded. Else you will not get to the scope at all.

UPDATE:

With the latest versions of angular, you should use

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

And yes, this is, in fact, a bad practice, but sometimes you just need things done quick and dirty.

Dmitry Mina
  • 3,842
  • 1
  • 14
  • 10
  • 31
    This seems like a code smell since Angular's philosophy is not to mix DOM code with Angular code... – JoeCool Oct 03 '13 at 18:42
  • 8
    so if you're not supposed to mix DOM code with Angular code, if you want certain JQuery animations to fire off in reaction to a variable change in your Angular controller- how exactly do you do that? It would be trivial to do from the controller, but I have no idea how to do it cleanly – Jan Nov 06 '13 at 21:06
  • 3
    I couldn't get the `$apply` part to work for me, until I wrapped the code into a function, e.g. `..scope.$apply(function() { scope.get() });` – shauneba Jan 07 '15 at 12:07
  • This is most probably related to the fact, that a very old version of angular was used back at that time. – Dmitry Mina Jan 10 '15 at 15:05
  • 2
    I actually could access the controller with ```angular.element(document.getElementById('yourControllerElementID')).scope().controller;``` For ex. if use: ```angular.element(document.getElementById('controller')).scope().get()``` throws and undefined error, but if i use ```angular.element(document.getElementById('controller')).scope().controller.get()``` it works. – vrunoa Feb 04 '15 at 16:42
  • @JoeCool You can just find the element using the controller's name directly, like this `angular.element($("[ng-controller='MyController']"))`. This has no reference to DOM and should remove your code smell :) – Hlung Feb 08 '15 at 07:38
  • Or more generically if you just have one controller: `angular.element(document.querySelector('[ng-controller]')).scope()` – Ian Clark Aug 18 '15 at 12:22
  • 2
    Is it possible, that this solution doesn't work in Angular 1.4.9 anymore? I cannot access `scope()` in the `angular.element(...)`, because it return undefined and a vardump of the angular element/object says that the function `scope` is located within the `__proto__`-object. – Smamatti Feb 09 '16 at 16:58
  • @JoeCool: Yes it is definitely a code smell, but it is very useful if you have to extend an existing Software Product using Plugins and the existing Software Product does not use AngularJS itself. – Harry Berry Apr 20 '16 at 16:07
  • 1
    This cannot be used in production. `element.scope()` only works with debugInfo enabled, and that should be disabled in production. See [docs](https://docs.angularjs.org/guide/production#disabling-debug-data) – ZachB Jun 17 '16 at 19:14
  • @ZachB, You can use `angular.element(document.getElementById('yourControllerElementID')).injector().get('$rootScope')` with the latest versions of angular. I will edit my answer, it's pretty old. – Dmitry Mina Jun 20 '16 at 10:02
  • How would you do this same thing if the controller was inside a component using angularjs v1.5.8? – RichC Feb 09 '17 at 14:14
  • old and update, booth solutions are not working on angularjs version 1.6.4. is there has any different way or solution for angularjs version 1.6.4? – Md. Sabbir Ahamed Jul 04 '17 at 13:07
38

I've found an example on the internet.

Some guy wrote this code and worked perfectly

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

*I did some modifications, see original: font JSfiddle

Matt
  • 179
  • 7
Roger Ramos
  • 568
  • 8
  • 22
  • 2
    Thanks Roger, very usefull! – Eduardo Jul 13 '15 at 22:00
  • Working with a legacy jQuery validation library which MUST be used. So, it's either 1: rewrite the library, 2: create directive to wrap the library, 3: 2 lines of code to call the angular submit when valid... – Michael K May 21 '17 at 18:37
  • if i do not use apply then function which will update view data will not be reflected ? – Monojit Sarkar Jun 06 '17 at 14:22
15

The solution angular.element(document.getElementById('ID')).scope().get() stopped working for me in angular 1.5.2. Sombody mention in a comment that this doesn't work in 1.4.9 also. I fixed it by storing the scope in a global variable:

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

call from custom code:

scopeHolder.bar()

if you wants to restrict the scope to only this method. To minimize the exposure of whole scope. use following technique.

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

call from custom code:

scopeHolder()
Salman Lone
  • 1,526
  • 2
  • 22
  • 29
Liviu Stirb
  • 5,876
  • 3
  • 35
  • 40
  • This worked great for me (even from inside a component). Is there a downside to doing this other than just the "bad practice to call angular stuff from outside angular" that I'm not able to avoid in my scenario? http://stackoverflow.com/questions/42123120/how-to-call-a-function-inside-an-angular-component-triggered-by-jquery-events/ – RichC Feb 09 '17 at 14:38
  • Thanks sir for your help I was searching for a solution for this from a while – Ibrahim Amer Sep 12 '19 at 12:20
11

Dmitry's answer works fine. I just made a simple example using the same 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
  • 13,618
  • 3
  • 69
  • 61
7

I would rather include the factory as dependencies on the controllers than inject them with their own line of 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'];

getsetbro
  • 1,798
  • 1
  • 22
  • 33
5

It may be worth considering if having your menu without any associated scope is the right way to go. Its not really the angular way.

But, if it is the way you need to go, then you can do it by adding the functions to $rootScope and then within those functions using $broadcast to send events. your controller then uses $on to listen for those events.

Another thing to consider if you do end up having your menu without a scope is that if you have multiple routes, then all of your controllers will have to have their own upate and get functions. (this is assuming you have multiple controllers)

Anton
  • 7,709
  • 5
  • 31
  • 33
  • can you give any simple example of how to call .get() function of ControllerOne from ControllerTwo? my logic is so each controller will have its own .get() .update() functions. I will have MainMenuController from which I need to execute (according to menu item) .get() of necessary controller. – Pavel Zdarov May 23 '13 at 10:21
  • ps, not my code, but it shows how to have multiple controlers sharing functionality – Anton May 23 '13 at 11:24
4

I use to work with $http, when a want to get some information from a resource I do the following:

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

And the code in the controller:

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

I send to the service what function have to call in the controller

rodrimmb
  • 41
  • 2
3

I have multiple routes and multiple controllers, so I could not get the accepted answer to work. I found that adding the function to the window works:

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;

}

Then outside the controller, this can be done:

function ElsewhereOnThePage() {
    if (typeof(fooreinit) == 'function') { fooreinit(); }
}
jaybro
  • 1,363
  • 1
  • 12
  • 23
0

Call Angular Scope function from outside the controller.

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

var scope = angular.element('body').scope();             
scope.$apply(function () {scope.YourAngularJSFunction()});      
Igor F.
  • 2,649
  • 2
  • 31
  • 39
sam ruben
  • 365
  • 2
  • 6
-1

I am an Ionic framework user and the one I found that would consistently provide the current controller's $scope is:

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

I suspect this can be modified to fit most scenarios regardless of framework (or not) by finding the query that will target the specific DOM element(s) that are available only during a given controller instance.

Matt Ray
  • 1,274
  • 1
  • 12
  • 26