At what stage of the compile / link process are variables from the isolated scope of a directive bound to the parent (controller) scope? I have an application in which I want to call a directive api automatically, as soon as the view is loaded.
I understood that scope binding happens in the directive linking phase, so that post linking, the variables exposed on the isolated scope should be available on the parent scope.
However, I find that this is not the case, as demonstrated in the code below (plunker here).
//plunker code
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.buttonClick = function() {
console.log("In buttonClick function, call is: " + this.call);
this.call();
}
$scope.$on("LinkComplete", function(event) {
console.log("In LinkComplete, call is: " + event.currentScope.call);
//event.currentScope.call();
});
console.log("In Constructor, call is: " + this.call);
})
.directive('myDirective', function(){
return {
scope: {
myMethod: '='
},
controller: function ($scope) {
$scope.myMethod = function() {
alert("method called");
};
},
link: function postLink(scope)
{
scope.$emit("LinkComplete");
}
};
});
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div my-directive my-method="call"></div>
<button ng-click="buttonClick()">Call</button>
</body>
Note that the code attempts to access the linked variable (which points to a method on the directive controller) twice during the view initialisation, and on both occasions, the variable is undefined. I wouldn't expect the variable to be available during the main controller constructor, but I would expect it to be available during the post-link event handler. Once the view is loaded, the bound variables are available (click Call button to witness).
How can I access the bound variables from the controller, without requiring the user to click on a button or the like?
That's a good question, when you see some words like 'x is y in z stage' you need to be careful on the accuracy, always dig into the source code to prove it.
Your plunker is using v1.2.27, checkout this line:
https://github.com/angular/angular.js/blob/v1.2.27/src/ng/compile.js#L1492
isolateScope.$watch(function parentValueWatch() {
var parentValue = parentGet(scope);
if (!compare(parentValue, isolateScope[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
isolateScope[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = isolateScope[scopeName]);
}
}
return lastValue = parentValue;
}, null, parentGet.literal);
This will be evaluated in next $digest cycle and by then parentScope.call will be assigned. At the same time, postLink function is executed synchronously right below it:
https://github.com/angular/angular.js/blob/v1.2.27/src/ng/compile.js#L1575
// POSTLINKING
for (i = postLinkFns.length - 1; i >= 0; i--) {
try {
linkFn = postLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
After postLink has been executed, controller got the event but parentScope.call has not been initialized yet via $digest.
So if you add a setTimeout to check, it looks like what you want:
$scope.$on("LinkComplete", function(event) {
setTimeout(function () {
console.log("In LinkComplete, call is: " + event.currentScope.call);
//event.currentScope.call();
});
});
Related
I'm new to Angular. I'm trying to use components (1.6). In the parent, I have an $http.get that gets data from a service and then assigns the response to a $scope variable. That scope variable is passed to a child component using one-way binding <. In the JavaScript, if I alert the variable passed in, I get "undefined", however, the html template in the child does show the variable. It's like there is a race condition happening and I don't know how to tell it to wait until the data from the service is loaded.
In my parent.js:
(function (angular) {
'use strict';
$http.get("http://localhost:52422/api/PayOffYourCc")
.then(function mySucces(response) {
$scope.baseline = response.data;
}
,
function myError(respone) {
$scope.baseline = response.statusText;
}
);
})(window.angular);
In my parent HTML template:
<thermometer baseline="baseline"></thermometer>
In my child component:
(function (angular) {
'use strict';
function drawChart(baselineVal) {
alert(baselineVal);
}
function ThermometerController($scope) {
var ctrl = this;
ctrl.$onInit = function () {
drawChart(ctrl.baseline);
};
}
angular.module('payOffYourCcApp').component('thermometer', {
templateUrl: '../PayOffYourCC/partials/thermometer.html',
transclude: true,
controller: ThermometerController,
bindings: {
baseline: '<'
}
});
})(window.angular);
In my child html template:
<div>
baseline:{{$ctrl.baseline}}
</div>
In the html, {{$ctrl.baseline}} is displayed fine, but when I alert it in the .js, it's undefined. Why is that? How can I make sure the {{$ctrl.baseline}} is in scope before the javascript loads?
Use the $onChanges life-cycle hook:
function ThermometerController($scope) {
var ctrl = this;
/* REPLACE THIS
ctrl.$onInit = function () {
drawChart(ctrl.baseline);
}; */
// WITH THIS
ctrl.$onChanges = function (changesObj) {
if (changesObj.baseline && changesObj.baseline.currentValue) {
drawChart(changesObj.baseline.currentValue);
};
};
}
The controller needs to wait for the data to come from the server. By using the $onChanges life-cycle hook, the drawChart function will be called when the data becomes available and will be called on subsequent updates.
For more information, see AngularJS Comprehensive Directive API Reference - Life-Cycle Hooks.
With Angular component, you should privilege communication from the child back to the parent since it allows a very cheap binding (&)
You can communicate from the parent to the child but it is more expensive (=).
I will give you an example on how to do it.
It is a not tested solution but you should have the idea.
You should change your parent to have a child api to transmit the data :
JS Parent :
(function (angular) {
'use strict';
$http.get("http://localhost:52422/api/PayOffYourCc")
.then(function mySucces(response) {
$scope.baseline = response.data;
$ctrl.apiChild.transmit(response.data);
}
,
function myError(respone) {
$scope.baseline = response.statusText;
}
);
})(window.angular);
HTML Parent :
<thermometer api="$ctrl.apiChild"></thermometer>
Change your child to have a function to receive the data from the parent and also change the binding to "=" :
JS Child :
(function (angular) {
'use strict';
function drawChart(baselineVal) {
alert(baselineVal);
}
function ThermometerController($scope) {
var ctrl = this;
ctrl.$onInit = function () {
drawChart(ctrl.baseline);
ctrl.api = {};
ctrl.api.transmit = ctrl.transmitData;
};
this.transmitData = function transmitData(data){
// here you get the data from the parent to the child
}
}
angular.module('payOffYourCcApp').component('thermometer', {
templateUrl: '../PayOffYourCC/partials/thermometer.html',
transclude: true,
controller: ThermometerController,
bindings: {
api : '='
}
});
})(window.angular);
This is the result of the fact that $http requests are asynchronous. The alert that happens when the child component initializes prints undefined, because at that instance the $http request that is retrieving the data has not returned yet. Since you're using > binding however, the template in your child component will update with the correct value as soon as the request resolves (which is pretty darn fast), so you don't ever see undefined actually printed in the template. In fact, I don't think angular will print undefined, I think it's just blank. So to your eye, it looks like it has the right value right away, when, in reality, it was momentarily undefined while the $http request was resolving.
New to creating custom directives. It renders fine on the initial render. However, I am trying to $watch for changes to the original data, and then, trigger an update.
As a quick test, I created a button and used jQuery to update the costPerDay.costs array (by hand)...but the $watch still doesn't fire & my breakpoint wasn't reached.
Thanks for the help...
MY CONTROLLER LOOKS LIKE:
The GET is mocked to return an object, not a promise, so ignore that particular line. Once I get the $watch working, I will update this part of the code accordingly.
// CONTROLLER
application.controller('HomeIndexController', function ($scope, costPerDayDataService) {
var vm = this;
// Internal
vm.on = {
databind: {
costPerDay: function () {
// The GET is mocked to return an object, not a promise, so ignore this line
var costPerDay = costPerDayDataService.get();
$scope.data.costPerDay = costPerDay;
}
}
};
vm.databind = function () {
vm.on.databind.costPerDay();
};
// Scope
$scope.data = {
costPerDay: {}
};
$scope.on = {
alterCosts: function (e) {
var costs = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300];
$scope.data.costPerDay.costs = costs;
}
}
// Databind
vm.databind();
});
MY ELEMENT LOOKS LIKE:
This renders fine initially. I need to automate updates.
<ul id="sparks" class="pull-down pull-left">
<li cost-per-day-sparkline costperday="data.costPerDay">
</li>
</ul>
MY DIRECTIVE LOOKS LIKE:
I am just trying to get ONE of them to work...I will obviously remove the others when I get a working example. And yes, I am aware you should NOT update the $parent directly. I'm just trying to find a combination that works before I get fancy.
define([], function () {
'use strict';
function CostPerDaySparklineDirective() {
return {
replace: true,
restrict: "AE",
scope: {
costperday: "=costperday"
},
templateUrl: '/modules/templates/sparklines/costperdaysparklinetemplate.html',
link: function (scope, elem, attrs) {
// This fails
scope.$watch('costperday', function (newval) {
// ... code to update will go here
}, true);
// This fails
scope.$watch('costperday', function (newval) {
// ... code to update will go here
});
// This fails
scope.$parent.$watch('data.costPerDay.costs', function (newval) {
// ... code to update will go here
});
// This renders initially, but fails to fire again
scope.$watch('scope.$parent.data.costPerDay.costs', function (newval) {
var eleSparkline = $('.sparkline', elem);
eleSparkline.sparkline(scope.costperday.costs, { type: "bar" });
});
}
};
}
return CostPerDaySparklineDirective;
});
UPDATE:
Even using ng-click to test the $watch fails to hit the breakpoint...
<a ng-click="on.alterCosts()">Change Costs</a>
In this case I'd run $scope.$apply(); in your alterCosts method to trigger a template digest. This will update the value in the DOM, which your directive catches, and subsequently triggers the $watch.
For more information on $apply(), https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
"$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events.."
In this particular scenario you're changing the value from a DOM event.
in this situation I would watch the actual get of the costPerDayDataService vs listening to the controllers scope variable. so in your controller you would 'set' the variable in costPerDayDataService and in your directive you would just inject your service and watch the get function. OR if you are using 1.3.x > you can use bindToController which I believe eliminates the whole need for watches.
bindToController: {
costperday: '='
}
I've written a pretty simple test app as follows:
angular.module('tddApp', [])
.controller('MainCtrl', function ($scope, $rootScope, BetslipService) {
$scope.displayEvents = [
{
id: 1,
name: 'Belarus v Ukraine',
homeTeam: 'Belarus',
awayTeam: 'Ukraine',
markets: {home: '2/1', draw: '3/2', away: '5/3'},
display: true
}
];
$scope.betslipArray = BetslipService.betslipArray;
$scope.oddsBtnCallback = BetslipService.addToBetslip;
$scope.clearBetslip = BetslipService.clearBetslip;
})
.directive('oddsButton', function () {
return {
template: '<div class="odds-btn">{{market}}</div>',
replace: true,
scope: {
market: '#',
marketName: '#',
eventName: '#',
callback: '&'
},
link: function (scope, element) {
element.on('click', function() {
scope.callback({
name: scope.eventName,
marketName: scope.marketName,
odds:scope.market
});
});
}
};
})
.factory ('BetslipService', function ($rootScope) {
var rtnObject = {};
rtnObject.betslipArray = [];
rtnObject.addToBetslip = function (name, marketName, odds) {
rtnObject.betslipArray.push({
eventName: name,
marketName: marketName,
odds: odds
});
};
rtnObject.clearBetslip = function () {
rtnObject.betslipArray = [];
};
return rtnObject;
});
I've assigned an array to a controller variable. I've also assigned functions to modify the array. To add an object to the array the callback is called by a directive with isolate scope. There's some strange behaviour happening that I don't quite understand:
=> clicking the directive runs the callback in the service. I've done some debugging and it seems that the controller variable is updated but it doesn't show in the html.
=> clicking the button to clear the array isn't working as expected. The first time it's causing an element to display, after which it has no effect.
I think that this may have to do with the nested ng-repeats creating their own scopes
NB
I fixed the array not clearing by changing the function in the service to:
while (rtnObject.betslipArray.length > 0) {
rtnObject.betslipArray.pop();
}
// instead of
rtnObject.betslipArray = [];
This makes sense as the service variable was being pointed at a new object while the old reference would persist in the controller.
I got the html to update by wrapping the callback call in the directive in a scope.$apply().
This part I dont really understand. How can scope.$apply() called in the directive have an effect on the controller scope when the directive has an isolate scope? updated fiddle: http://jsfiddle.net/b6ww0rx8/7/
Any thought's greatly appreciated
jsfiddle: http://jsfiddle.net/b6ww0rx8/5/
C
I got it working here: http://jsfiddle.net/b6ww0rx8/8/
Added $q, $scope.$emit and $timeout clauses to help with communications between your directive / service and controller.
I would like to also say that I wouldn't assign service functions to a controller $scope, You should define functions in the controller that call service functions.
Instead of this:
$scope.clearBetslip = BetslipService.clearBetslip;
Do this:
$scope.clearBetslip = function(){
BetslipService.clearBetslip().then(function(){
$scope.betslipArray = BetslipService.getBetslipArray();
});
};
I have a function which I want to call after page content is loaded. I read about $viewContentLoaded and it doesn't work for me. I am looking for something like
document.addEventListener('DOMContentLoaded', function () {
//Content goes here
}, false);
Above call doesn't work for me in AngularJs controller.
According to documentation of $viewContentLoaded, it supposed to work
Emitted every time the ngView content is reloaded.
$viewContentLoaded event is emitted that means to receive this event you need a parent controller like
<div ng-controller="MainCtrl">
<div ng-view></div>
</div>
From MainCtrl you can listen the event
$scope.$on('$viewContentLoaded', function(){
//Here your view content is fully loaded !!
});
Check the Demo
Angular < 1.6.X
angular.element(document).ready(function () {
console.log('page loading completed');
});
Angular >= 1.6.X
angular.element(function () {
console.log('page loading completed');
});
fixed - 2015.06.09
Use a directive and the angular element ready method like so:
js
.directive( 'elemReady', function( $parse ) {
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
})
}
}
})
html
<div elem-ready="someMethod()"></div>
or for those using controller-as syntax...
<div elem-ready="vm.someMethod()"></div>
The benefit of this is that you can be as broad or granular w/ your UI as you like and you are removing DOM logic from your controllers. I would argue this is the recommended Angular way.
You may need to prioritize this directive in case you have other directives operating on the same node.
You can directly call it by adding {{YourFunction()}} after HTML element.
Here is a Plunker Link.
I had to implement this logic while handling with google charts. what i did was that at the end of my html inside controller definition i added.
<body>
-- some html here --
--and at the end or where ever you want --
<div ng-init="FunCall()"></div>
</body>
and in that function simply call your logic.
$scope.FunCall = function () {
alert("Called");
}
var myM = angular.module('data-module');
myM.directive('myDirect',['$document', function( $document ){
function link( scope , element , attrs ){
element.ready( function(){
} );
scope.$on( '$viewContentLoaded' , function(){
console.log(" ===> Called on View Load ") ;
} );
}
return {
link: link
};
}] );
Above method worked for me
you can call javascript version of onload event in angular js. this ng-load event can be applied to any dom element like div, span, body, iframe, img etc. following is the link to add ng-load in your existing project.
download ng-load for angular js
Following is example for iframe, once it is loaded testCallbackFunction will be called in controller
EXAMPLE
JS
// include the `ngLoad` module
var app = angular.module('myApp', ['ngLoad']);
app.controller('myCtrl', function($scope) {
$scope.testCallbackFunction = function() {
//TODO : Things to do once Element is loaded
};
});
HTML
<div ng-app='myApp' ng-controller='myCtrl'>
<iframe src="test.html" ng-load callback="testCallbackFunction()">
</div>
If you're getting a $digest already in progress error, this might help:
return {
restrict: 'A',
link: function( $scope, elem, attrs ) {
elem.ready(function(){
if(!$scope.$$phase) {
$scope.$apply(function(){
var func = $parse(attrs.elemReady);
func($scope);
})
}
else {
var func = $parse(attrs.elemReady);
func($scope);
}
})
}
}
I was using {{myFunction()}} in the template but then found another way here using $timeout inside the controller. Thought I'd share it, works great for me.
angular.module('myApp').controller('myCtrl', ['$timeout',
function($timeout) {
var self = this;
self.controllerFunction = function () { alert('controller function');}
$timeout(function () {
var vanillaFunction = function () { alert('vanilla function'); }();
self.controllerFunction();
});
}]);
Running after the page load should partially be satisfied by setting an event listener to the window load event
window.addEventListener("load",function()...)
Inside the module.run(function()...) of angular you will have all access to the module structure and dependencies.
You can broadcast and emit events for communications bridges.
For example:
module set onload event and build logic
module broadcast event to controllers when logic required it
controllers will listen and execute their own logic based on module onload processes.
If you want certain element to completely loaded, Use ng-init on that element .
e.g. <div class="modal fade" id="modalFacultyInfo" role="dialog" ng-init="initModalFacultyInfo()"> ..</div>
the initModalFacultyInfo() function should exist in the controller.
I found that if you have nested views - $viewContentLoaded gets triggered for every of the nested views. I've created this workaround to find the final $viewContentLoaded. Seems to work alright for setting $window.prerenderReady as required by Prerender (goes into .run() in the main app.js):
// Trigger $window.prerenderReady once page is stable
// Note that since we have nested views - $viewContentLoaded is fired multiple
// times and we need to go around this problem
var viewContentLoads = 0;
var checkReady = function(previousContentLoads) {
var currentContentLoads = Number(viewContentLoads) + 0; // Create a local copy of the number of loads
if (previousContentLoads === currentContentLoads) { // Check if we are in a steady state
$window.prerenderReady = true; // Raise the flag saying we are ready
} else {
if ($window.prerenderReady || currentContentLoads > 20) return; // Runaway check
$timeout(function() {checkReady(currentContentLoads);}, 100); // Wait 100ms and recheck
}
};
$rootScope.$on('$stateChangeSuccess', function() {
checkReady(-1); // Changed the state - ready to listen for end of render
});
$rootScope.$on('$viewContentLoaded', function() {
viewContentLoads ++;
});
var myTestApp = angular.module("myTestApp", []);
myTestApp.controller("myTestController", function($scope, $window) {
$window.onload = function() {
alert("is called on page load.");
};
});
The solution that work for me is the following
app.directive('onFinishRender', ['$timeout', '$parse', function ($timeout, $parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
if (!!attr.onFinishRender) {
$parse(attr.onFinishRender)(scope);
}
});
}
if (!!attr.onStartRender) {
if (scope.$first === true) {
$timeout(function () {
scope.$emit('ngRepeatStarted');
if (!!attr.onStartRender) {
$parse(attr.onStartRender)(scope);
}
});
}
}
}
}
}]);
Controller code is the following
$scope.crearTooltip = function () {
$('[data-toggle="popover"]').popover();
}
Html code is the following
<tr ng-repeat="item in $data" on-finish-render="crearTooltip()">
I use setInterval to wait for the content loaded. I hope this can help you to solve that problem.
var $audio = $('#audio');
var src = $audio.attr('src');
var a;
a = window.setInterval(function(){
src = $audio.attr('src');
if(src != undefined){
window.clearInterval(a);
$('audio').mediaelementplayer({
audioWidth: '100%'
});
}
}, 0);
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.
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
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/
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';
})
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");
});
}
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';
})
}
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';
});
};
You can also try:
function change() {
var scope = angular.element( document.getElementById('outer') ).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
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/
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
<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);
});
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>
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.