139

I'm trying to see if there's a simple way to access the internal scope of a controller through an external javascript function (completely irrelevant to the target controller)

I've seen on a couple of other questions here that

angular.element("#scope").scope();

would retrieve the scope from a DOM element, but my attempts are currently yielding no proper results.

Here's the jsfiddle: http://jsfiddle.net/sXkjc/5/

I'm currently going through a transition from plain JS to Angular. The main reason I'm trying to achieve this is to keep my original library code intact as much as possible; saving the need for me to add each function to the controller.

Any ideas on how I could go about achieving this? Comments on the above fiddle are also welcome.

dk123
  • 18,684
  • 20
  • 70
  • 77
  • FYI according to the docs using `.scope()` requires the Debug Data to be enable but [using Debug Data in production is not recommended](https://docs.angularjs.org/guide/production#disabling-debug-data) for speed reasons. The solutions below seem to revolve around `scope()` – rtpHarry Dec 05 '14 at 15:12
  • @rtpHarry is right. Answers below which requires usage of scope() are deprecated. See my answer here http://stackoverflow.com/a/34078750/319302 – Cagatay Kalan Mar 08 '16 at 22:38

12 Answers12

231

You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of angularjs like a jquery/javascript event handler.

function change() {
    alert("a");
    var scope = angular.element($("#outer")).scope();
    scope.$apply(function(){
        scope.msg = 'Superhero';
    })
}

Demo: Fiddle

thecodeparadox
  • 86,271
  • 21
  • 138
  • 164
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • Thanks for the answer. I've tried the Fiddle you've attached but I still get the same "You are great" string even after I click on "click me". I get the alert, just not the string change. Are you seeing changes? – dk123 Mar 15 '13 at 05:04
  • 2
    @dk123 `angular.element("#scope")` is not working, though `angular.element($("#scope"))` is working, you need to have jquery also – Arun P Johny Mar 15 '13 at 05:11
  • Thanks, for the fiddle and the docs link - it works great. It would have been nice though if something along the lines of `scope.msg("superhero");` could have worked though without the apply :D – dk123 Mar 15 '13 at 05:24
  • Let me get this straight. I thought the point of a MVC is to separate the model and the view. Yet in order to change a variable not inside the $scope, one needs to access a DOM element and then get the scope. That seems totally counter productive. – dooderson Nov 05 '13 at 23:57
  • 1
    I know it been a while, but I hope some can answer me on this ... Why does var scope = angular.element($("#outer")).scope(); have to be declared inside the change function? If I move it to the global space it's a no go? – Marc M. Nov 06 '13 at 09:47
  • 1
    @MarcM. I think it has to do with Angular's scope recreation. By the time you're using the change function, the previous scope the global var was pointing to may no longer exist (due to the recreation). – dk123 Dec 14 '13 at 03:43
  • Dumb question, but why is $scope not accessible from inside $apply() - given that $apply() is tied to the scope anyway? – antoine Jan 31 '15 at 00:28
  • 1
    angular.element($("div[ng-controller='myCtrl']")).scope(); is better than additional #outer in div element, i guess – wyverny Mar 21 '16 at 06:21
  • 1
    scope() gets undefined when we do: $compileProvider.debugInfoEnabled(false); in angular. So how can we make it work with debuginfoEnabled false? – Agnosco May 24 '16 at 16:42
  • This doesn't seem to work in a Jasmine test. Any caveats there? – ryanwebjackson Sep 18 '17 at 16:10
  • Re: scope() gets undefined: as per https://docs.angularjs.org/guide/production you can do `angular.reloadWithDebugInfo();`, after that it seems to work. – Paul I Oct 09 '19 at 18:01
26

It's been a while since I posted this question, but considering the views this still seems to get, here's another solution I've come upon during these last few months:

$scope.safeApply = function( fn ) {
    var phase = this.$root.$$phase;
    if(phase == '$apply' || phase == '$digest') {
        if(fn) {
            fn();
        }
    } else {
        this.$apply(fn);
    }
};

The above code basically creates a function called safeApply that calles the $apply function (as stated in Arun's answer) if and only Angular currently isn't going through the $digest stage. On the other hand, if Angular is currently digesting things, it will just execute the function as it is, since that will be enough to signal to Angular to make the changes.

Numerous errors occur when trying to use the $apply function while AngularJs is currently in its $digest stage. The safeApply code above is a safe wrapper to prevent such errors.

(note: I personally like to chuck in safeApply as a function of $rootScope for convenience purposes)

Example:

function change() {
    alert("a");
    var scope = angular.element($("#outer")).scope();
    scope.safeApply(function(){
        scope.msg = 'Superhero';
    })
}

Demo: http://jsfiddle.net/sXkjc/227/

dk123
  • 18,684
  • 20
  • 70
  • 77
  • 1
    Why does your safeApply function work? Seems like what you are saying is "execute the function by itself if Angular is in the $apply or $digest stages, otherwise use $apply() to apply the function".... But if you execute the function by itself.... how does that update any models? Seems like that would not be favorable behavior, unless there something going on I don't know about. Does some mechanism in Angular go an poll the $scope for changes that may have happened directly to it??? – trusktr Mar 26 '14 at 05:37
  • Plus, if you need to safeguard against those states, then I'd consider that a bug of the $apply() method that needs to be fixed. – trusktr Mar 26 '14 at 05:37
  • @trusktr From what I understand, executing the function normally is caught by angular if the function changes any models, and hence angular updates them in the next digest stage. – dk123 Mar 27 '14 at 00:54
  • @trusktr I'd agree though that if the normal $apply() can be applied without the safeguards, there would be nothing better. In essence, the only purpose of safeApply is to safeguard against the $apply() errors. Not sure though if this was a reported issue and now fixed, or still an ongoing one. – dk123 Mar 27 '14 at 00:56
  • I have event handler inside controller div so I could simplified it a little. But can it be simplified more in this case? Forked fiddle: http://jsfiddle.net/luckylooke/sXkjc/521/ – Luckylooke Jun 03 '14 at 11:03
  • 1
    Just because I stumbled over it: https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply(). _If you are doing if (!$scope.$$phase) $scope.$apply() it's because you are not high enough in the call stack._ – scheffield Sep 29 '14 at 14:44
18

Another way to do that is:

var extScope;
var app = angular.module('myApp', []);
app.controller('myController',function($scope, $http){
    extScope = $scope;
})
//below you do what you want to do with $scope as extScope
extScope.$apply(function(){
    extScope.test = 'Hello world';
})
Charleston
  • 1,539
  • 18
  • 10
  • 1
    I still don't understand why this comment isn't the best answer for this. After digging the internet for couple of days, with frustration and anger, finally this is what solved my issue. Thank you @Charleston. You are great, sir! – Rajkumar Aug 24 '16 at 04:44
13

we can call it after loaded

http://jsfiddle.net/gentletech/s3qtv/3/

<div id="wrap" ng-controller="Ctrl">
    {{message}}<br>
    {{info}}
    </div>
    <a  onClick="hi()">click me </a>

    function Ctrl($scope) {
        $scope.message = "hi robi";
        $scope.updateMessage = function(_s){
            $scope.message = _s;    
        };
    }

function hi(){
    var scope = angular.element(document.getElementById("wrap")).scope();
        scope.$apply(function() {
        scope.info = "nami";
        scope.updateMessage("i am new fans like nami");
    });
}
fallwind
  • 159
  • 1
  • 4
8

It's been a long time since I asked this question, but here's an answer that doesn't require jquery:

function change() {
    var scope = angular.element(document.querySelector('#outside')).scope();
    scope.$apply(function(){
        scope.msg = 'Superhero';
    })
}
dk123
  • 18,684
  • 20
  • 70
  • 77
3

Here's a reusable solution: http://jsfiddle.net/flobar/r28b0gmq/

function accessScope(node, func) {
    var scope = angular.element(document.querySelector(node)).scope();
    scope.$apply(func);
}

window.onload = function () {

    accessScope('#outer', function (scope) {
        // change any property inside the scope
        scope.name = 'John';
        scope.sname = 'Doe';
        scope.msg = 'Superhero';
    });

};
flobar
  • 121
  • 1
  • 8
2

You can also try:

function change() {
    var scope = angular.element( document.getElementById('outer') ).scope();
    scope.$apply(function(){
        scope.msg = 'Superhero';
    })
}
HarisH Sharma
  • 1,101
  • 1
  • 11
  • 38
1

The accepted answer is great. I wanted to look at what happens to the Angular scope in the context of ng-repeat. The thing is, Angular will create a sub-scope for each repeated item. When calling into a method defined on the original $scope, that retains its original value (due to javascript closure). However, the this refers the calling scope/object. This works out well, so long as you're clear on when $scope and this are the same and when they are different. hth

Here is a fiddle that illustrates the difference: https://jsfiddle.net/creitzel/oxsxjcyc/

Charlie
  • 31
  • 3
1

I'm newbie, so sorry if is a bad practice. Based on the chosen answer, I did this function:

function x_apply(selector, variable, value) {
    var scope = angular.element( $(selector) ).scope();
    scope.$apply(function(){
        scope[variable] = value;
    });
}

I'm using it this way:

x_apply('#fileuploader', 'thereisfiles', true);

By the way, sorry for my english

MrQwerty
  • 171
  • 1
  • 6
1

This is how I did for my CRUDManager class initialized in Angular controller, which later passed over to jQuery button-click event defined outside the controller:

In Angular Controller:

        // Note that I can even pass over the $scope to my CRUDManager's constructor.
        var crudManager = new CRUDManager($scope, contextData, opMode);

        crudManager.initialize()
            .then(() => {
                crudManager.dataBind();
                $scope.crudManager = crudManager;
                $scope.$apply();
            })
            .catch(error => {
                alert(error);
            });

In jQuery Save button click event outside the controller:

    $(document).on("click", "#ElementWithNgControllerDefined #btnSave", function () {
        var ngScope = angular.element($("#ElementWithNgControllerDefined")).scope();
        var crudManager = ngScope.crudManager;
        crudManager.saveData()
            .then(finalData => {
               alert("Successfully saved!");
            })
            .catch(error => {
               alert("Failed to save.");
            });
    });

This is particularly important and useful when your jQuery events need to be placed OUTSIDE OF CONTROLLER in order to prevent it from firing twice.

Antonio Ooi
  • 1,601
  • 1
  • 18
  • 32
0
<input type="text" class="form-control timepicker2" ng-model='programRow.StationAuxiliaryTime.ST88' />

accessing scope value

assume that programRow.StationAuxiliaryTime is an array of object

 $('.timepicker2').on('click', function () 
    {
            var currentElement = $(this);

            var scopeValues = angular.element(currentElement).scope();
            var model = currentElement.attr('ng-model');
            var stationNumber = model.split('.')[2];
            var val = '';
            if (model.indexOf("StationWaterTime") > 0) {
                val = scopeValues.programRow.StationWaterTime[stationNumber];
            }
            else {
                val = scopeValues.programRow.StationAuxiliaryTime[stationNumber];
            }
            currentElement.timepicker('setTime', val);
        });
Sreeraj
  • 31
  • 9
0

We need to use Angular Js built in function $apply to acsess scope variables or functions outside the controller function.

This can be done in two ways :

|*| Method 1 : Using Id :

<div id="nameNgsDivUid" ng-app="">
    <a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
    {{ nameNgsVar }}
</div>

<script type="text/javascript">

    var nameNgsDivVar = document.getElementById('nameNgsDivUid')

    function actNgsFnc()
    {
        var scopeNgsVar = angular.element(nameNgsDivVar).scope();
        scopeNgsVar.$apply(function()
        {
            scopeNgsVar.nameNgsVar = "Tst Txt";
        })
    }

</script>

|*| Method 2 : Using init of ng-controller :

<div ng-app="nameNgsApp" ng-controller="nameNgsCtl">
    <a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
    {{ nameNgsVar }}
</div>

<script type="text/javascript">

    var scopeNgsVar;
    var nameNgsAppVar=angular.module("nameNgsApp",[])
    nameNgsAppVar.controller("nameNgsCtl",function($scope)
    {
        scopeNgsVar=$scope;
    })

    function actNgsFnc()
    {
        scopeNgsVar.$apply(function()
        {
            scopeNgsVar.nameNgsVar = "Tst Txt";
        })
    }

</script>
Sujay U N
  • 4,974
  • 11
  • 52
  • 88