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.
Related
I have an angular service to handle my promises, but I am not sure how to manipulate the values on $scope from within the service. I understand what I am doing is wrong, but don't understand what I need to do to be right.
In my service:
.service('resolvePromiseService', function(){
var publicInterface = {
resolvePromise: resolvePromise
}
function resolvePromise(promise, resultObject, callBackFunction, spinner){
spinner ++;
promise.then(function(result){
resultObject = result;
if(callBackFunction){
callBackFunction();
}
});
promise['catch'(function(error){
//generic error handling
});
promise['finally'(function(){
spinner--;
});
}
and in my controller which calls the service
var getInfoPromise = dataAccessService.getInfoByLocationId(locationId).$promise;
resolvePromiseService.resolvePromise(getInfoPromise, $scope.locationInfo, $scope.setUpLocation, $scope.loadingSpinner);
Inside the resolvePromise function, I am seeing the values come in as expected and being updated as expected, but I think I am misunderstanding the way $scope is passed around. I believe I am replacing the angular objects with vanilla javascript objects.
The best option would be for you to pass in your scope object or rethink how you are handling promises.
The reason this is happening is to do with modifying references.
In your example
$scope.loadingSpinner = 5; // ref1 - val 5
function resolvePromise(promise, resultObject, callBackFunction, spinner){
// when we enter the function both $scope.loadingSpinner and spinner are both
// referencing the same variable
spinner; // ref1 - val5
// after we increment spinner the local reference is no longer pointing
// at the reference
spinner; // ref2 - val6
// therefore we are not actually updating the $scope.loadingSpinner
//reference just the local spinner one
}
I have included a snippet to demonstrate this point - you need to keep in mind that you are reassigning the reference which is not what you intend to do in this instance.
angular
.module('Test',[])
.service('IncrementService', incrementService)
.controller('MyController', myController)
function incrementService() {
this.increment = function(valToInc) {
valToInc++;
console.log(valToInc);
}
this.incrementScopeField = function($scope, field) {
$scope[field]++;
console.log($scope[field]);
}
}
myController.$inject = ['$scope', 'IncrementService'];
function myController($scope, IncrementService) {
$scope.number = 5;
$scope.inc = function() {
IncrementService.increment($scope.number);
}
$scope.inc2 = function() {
IncrementService.incrementScopeField($scope, 'number');
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='Test' ng-controller='MyController'>
<button ng-click='inc()'>increment value</button><button ng-click='inc2()'>increment scope</button>
<br/>{{ number }}
</div>
I have a very simple angular app that pushes data in without refreshing the page using setInterval. Now, how can I listen or watch for new data/changes, so that if the new value/data differ from the previous one a new css style will be applied to that particular new value (for example it will change the font color to red).
My code is below:
view:
<h1>{{title}}</h1>
<ul>
<li ng-repeat="friend in friends"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>
</ul>
data:
angular
.module ('myApp')
.factory ('Friends', ['$http', function ($http) {
return {
get: function () {
return $http.get ('users.json').then (function (response) {
return response.data;
});
}
};
}]);
Controller:
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', function ($scope, Friends) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = setInterval(function(){
$scope.loadData();
},5000);
}]);
many thanks
Use $interval instead of setInterval, since it triggers a digest loop it will update your data automatically
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', '$interval' function ($scope, Friends, $interval) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = $interval(function(){
$scope.loadData();
},5000);
}]);
My recommendation would be to manually compare each friend item and assign a changeFlag whenever the data has changed.
To start, keep a reference to the old data and whenever new data comes in, compare the two, like this:
var oldData = undefined; // Somewhere in initialization.
...
Friends.get().then(function (response) {
var newData = response;
if (oldData && JSON.stringify(oldData) != JSON.stringify(newData))
{
$scope.friends = newData;
$scope.$apply(); // Force the entire page to be redrawn. You can do style bindings to change a style.
}
oldData = response;
}
This will get you half-way to your goal. You will only be refreshing the page whenever something has changed, but there is no indication as to which friend has changed. I imagine this is what you are attempting to accomplish. You want to highlight those friends that have changed.
To do this we could simply create a comparison function that applies a flag to each object that has changed. However, this code assumes that some property on each friend remains fixed. This is normally why an id property is given to each item in a database. I'm going to assume you have an id property for each friend that never changes regardless if their name, age, email, etc. does.
var changeFlagFriendsObjects = function(oldData, newData) {
var idToOldDataMap = {};
oldData.forEach(function (friend) {
idToOldDataMap[friend.id] = friend;
});
newData.forEach(function (friend) {
var oldFriendData = idToOldDataMap[friend.id];
friend.changeFlag = JSON.stringify(oldFriendData) != JSON.stringify(friend);
});
};
// You would call changeFlagFriendsObjects in the other example above. I'm sure this would be easy to figure out how to place.
Regarding binding styles in the HTML to properties, see here.
An example would be like the following:
<!-- Apply the 'highlight' style when changeFlag is true -->
<li ng-repeat="friend in friends" ng-style="highlight={changeFlag: true}"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>
This is a simplistic example of a problem I am having. I am clearly missing something in my understanding of Angular.
A Plunker is here: http://plnkr.co/edit/VLqA22dDTgk5PyPlCOGH?p=preview
And a copy-paste of the pertinent bits below:
<div ng-controller="myController">
<div>message: <label ng-model="message"></label></div>
<div></div><button ng-click="start()">Get Message</button></div>
</div>
var app = angular.module("app", []);
app.service('GetMessage', function() {
var message;
var start = function () {
this.message = 'Hello World';
};
return {
message: this.message,
start: start
}
});
app.controller('myController', function ($scope, GetMessage) {
$scope.message = GetMessage.message;
$scope.start = function () {
GetMessage.start();
console.warn('started..');
};
});
I would expect that the label directive would be 2-way bound to the factory's message property, so that when the start() function is called and the message is updated, that the page would be too.
To update the label in this way, do I need to broadcast an event to $rootScope, listen for it in the controller, and then update the label? It seems a very manual way of doing it.. surely there is a better way.
Thank you.
You are currently using a primitive data type, which means the following line will copy the value into a new variable:
$scope.message = GetMessage.message;
Updating one will not affect the other.
An easy solution is to use an object instead:
var message = { value: '' };
var start = function() {
message.value = 'Hello World';
};
And:
$scope.message = GetMessage.message;
Now the reference to the object would be copied into a new variable instead and both would refer to the same object.
Another issue is that you are using ngModel on a label to display the value, which will not work. ngModel is normally used on input, select and textarea elements.
You can instead use ng-bind:
<label ng-bind="message.value"></label>
Or the less verbose shortcut:
<label>{{message.value}}</label>
Demo: http://plnkr.co/edit/rUgN94DAIOfQGI8tr9kl?p=preview
If you prefer to keep using primtive values you need to handle it another way. For example by using events like you mentioned. Another solution is to register a watcher to watch for changes and update the scope variable:
app.controller('myController', function($scope, GetMessage) {
var watchExpression = function() {
return GetMessage.message;
};
var listener = function(newValue, oldValue) {
if (newValue === oldValue) return;
$scope.message = newValue;
}
$scope.$watch(watchExpression, listener);
$scope.start = function() {
GetMessage.start();
console.warn('started..');
};
});
Demo: http://plnkr.co/edit/klSl1DCUlQI3Z5ih16sq?p=preview
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 recently chose AngularJS over ember.js for a project I am working on, and have been very pleased with it so far. One nice thing about ember is its built in support for "computed properties" with automatic data binding. I have been able to accomplish something similar in Angular with the code below, but am not sure if it is the best way to do so.
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// "Computed Property"
$scope.$watch(navigation.getCurrentPageNumber, function(newVal, oldVal, scope) {
scope.currentPageNumber = newVal;
});
$scope.totalPages = navigation.getTotalPages();
}]);
// 'navigation' service
angular.module('mathSkills.services', [])
.factory('navigation', function() {
var currentPage = 0,
pages = [];
return {
getCurrentPageNumber: function() {
return currentPage + 1;
},
getTotalPages: function() {
return pages.length;
}
};
});
// HTML template
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber}} of {{totalPages}}
</div>
I would like for the UI to update whenever the currentPage of the navigation service changes, which the above code accomplishes.
Is this the best way to solve this problem in AngularJS? Are there (significant) performance implications for using $watch() like this? Would something like this be better accomplished using custom events and $emit() or $broadcast()?
While your self-answer works, it doesn't actually implement computed properties. You simply solved the problem by calling a function in your binding to force the binding to be greedy. I'm not 100% sure it'd work in all cases, and the greediness might have unwanted performance characteristics in some situations.
I worked up a solution for a computed properties w/dependencies similar to what EmberJS has:
function ngCreateComputedProperty($scope, computedPropertyName, dependentProperties, f) {
function assignF($scope) {
var computedVal = f($scope);
$scope[computedPropertyName] = computedVal;
};
$scope.$watchCollection(dependentProperties, function(newVal, oldVal, $scope) {
assignF($scope);
});
assignF($scope);
};
// in some controller...
ngCreateComputedProperty($scope, 'aSquared', 'a', function($scope) { return $scope.a * $scope.a } );
ngCreateComputedProperty($scope, 'aPlusB', '[a,b]', function($scope) { return $scope.a + $scope.b } );
See it live: http://jsfiddle.net/apinstein/2kR2c/3/
It's worth noting that $scope.$watchCollection is efficient -- I verified that "assignF()" is called only once even if multiple dependencies are changed simultaneously (same $apply cycle).
"
I think I found the answer. This example can be dramatically simplified to:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// Property is now just a reference to the service's function.
$scope.currentPageNumber = navigation.getCurrentPageNumber;
$scope.totalPages = navigation.getTotalPages();
}]);
// HTML template
// Notice the first binding is to the result of a function call.
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber()}} of {{totalPages}}
</div>
Note that with ECMAScript 5 you can now also do something like this:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', function(navigation, $scope) {
$scope.totalPages = navigation.getTotalPages();
Object.defineProperty($scope, 'currentPageNumber', {
get: function() {
return navigation.getCurrentPageNumber();
}
});
]);
//HTML
<div ng-controller="nav">Problem {{currentPageNumber}} of {{totalPages}}</div>