i'm trying to update a scope list inside a callback function. This apparently works fine, but, after some seconds, console gets error: [$rootScope:infdig]. I tried to disable two-way databinding, but, the error continues.
Controller:
app.controller('ChapterCtrl', function ($rootScope, $scope, Services, chapter) {
$rootScope.headerTitle = chapter.name;
$scope.terms = [];
cctdbterms.webdb.getTermsByChapter(chapter.id, function(tx, results) {
$scope.terms = results.rows;
$scope.$apply();
});
});
View:
<div class="view" ng-repeat="term in terms">
<div ng-bind-html="term.description"></div>
</div>
The answer that a find is seraching is: "Problem was that the filter was providing a different array each time hence causing a loop" from Why do I get an infdig error?
Thinking about it, I solved in a simple way, interating my returning list:
app.controller('ChapterCtrl', function ($rootScope, $scope, Services, chapter) {
$rootScope.headerTitle = chapter.name;
$scope.terms = [];
cctdbterms.webdb.getTermsByChapter(chapter.id, function(tx, results) {
for (var i = 0; i < results.rows.length; i++) {
var term = results.rows[i];
$scope.terms.push(term);
}
$scope.$apply();
});
});
Related
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>
New to AngularJS and I guess I don't understand how to call one Promise method from another with the same factory. Every time my code gets to the $http.get within processPerson, I get a Function Expected error in IE, or an Object is not a Function error in Chrome. I've tried reorganizing code many times, multiple factories, etc, and generally get the same error. The only time I can get this to work is if I combine the functions where the processPerson function is embedded within the success of the getPersonnel.
Code:
(function(){
var app = angular.module('hrSite', ['personnel']);
app.controller('PersonnelController', function($scope, personnelFactory){
var personnelPromise = personnelFactory.getPersonnel();
personnelPromise.then(function(personnel){
var perDefs = new Array();
$.each(personnel.data.value, function( i, person ){
var perDef = personnelFactory.processPerson(person);
perDefs.push(perDef);
});
$q.all(perDefs).then(function(){
$scope.personnel = personnel.data.value;
});
});
});
})();
(function(){
var personnelModule = angular.module('personnel', []);
personnelModule.factory('personnelFactory', function($http, $q) {
var getPersonnel = function(){
return $http.get("/sites/Development/_api/web/lists/getbytitle('Personnel')/items");
};
var processPerson = function(person){
var deferred = $q.defer();
$http.get("/sites/Development/_api/web/lists/getbytitle('Personnel Skills')/items?$select=*,Skill/Id,Skill/Title&$filter=PersonId eq '"+person.Id+"'&$expand=Skill").then(function(skills){
person.Skills = skills.data.value;
person.SkillsId = [];
$.each(skills.data.value, function( j, skill ){
person.SkillsId.push(skill.Id);
});
deferred.resolve();
});
return deferred.promise();
};
return {getPersonnel: getPersonnel,
processPerson: processPerson}
});
})();
Nevermind - I figured it out. I was migrating code from a jQuery project and in jQuery, you return a promise like this:
return deferred.promise();
Since Angular has its own deferred feature, $q, I began using that, without realizing that the notation to return a promise was slightly different:
return deferred.promise;
No () in that, which was really screwing things up. Now everything seems to be working fine.
I have a 'messages' factory that will query my database for a list of messages.
I'm using the list of messages in two different places. Once to add a message count indicator, and then once to show a list of messages. Since I'm injecting the service into two different controllers, it seems like it's creating two instances of my factory, and hitting the database twice for the list.
How would I set things up to only ask for the list once, and use the list for both display and count purposes in both controllers?
My factory looks like this:
myApp.factory('messagesService', [
'$rootScope',
function($rootScope) {
var messages = [];
function query() {
// Would actually hit the database asyncronously
messages = ['one', 'two', 'three', 'four'];
console.log('query');
$rootScope.$emit('messages.update');
}
function all() {
return messages;
}
return {
query: query,
all: all
}
}
]);
My controllers are using blocks like this to watch for changes:
$rootScope.$on('messages.update', function() {
$scope.messagesCount = messagesService.all().length;
});
But it means i need a messagesService.query(); in each controller for things to be reliable.
So here are a few jsFiddle examples of it as I have things now:
Doesn't work (only updates the header): http://jsfiddle.net/TSLfc/1/
Works but would break if I didn't load the dashboard controller:
http://jsfiddle.net/TSLfc/2/
Works every time, but queries the server twice:
http://jsfiddle.net/TSLfc/3/
Is there a better way to organize my code? Should I build out the messages factory into it's own full module?
Here (Plunkr) is how I would do it:
I have gone back and modified my previous answer, updating with what we discussed in the comments below as well as using promises instead of the timeout as an asynchronous simulation I was showing before (see revision history for reference).
I also removed every variable/function that didn't need to be returned to the controller from the service object, if it doesn't need to be accessed via the controller than it doesn't need to be included on the returned object.
var myApp = angular.module('myApp', []);
myApp.factory('messagesService', [
'$q',
'$rootScope',
'$http',
function ($q, $rootScope, $http) {
var mService = {};
mService.messages = [];
var queryInit = false;
// We don't need to access this function in the controller
// So I am not going to attach to the returned object
var getMessages = function () {
// Stops each controller from getting messages when loaded
if (!queryInit) {
queryInit = true;
// Using the $q promise library we use 'then()' to handle
// What happens after the async call is returned
// The first function parameter is the success/resolve callback
// The second function parameter is the error/reject callback
mService.query().then(function (successResults) {
// Tell all of the controllers that the data has changed
$rootScope.$broadcast('messages.update');
}, function (errorResults) {
console.error(errorResults);
});
}
};
// Used to force an update from the controller if needed.
mService.query = function () {
var deferred = $q.defer();
$http.get('path/to/file.php')
.success(function (data, status, headers, config) {
// assign the returned values appropriately
mService.messages = data;
// this callback will be called asynchronously
// when the response is available
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject(data);
});
return deferred.promise;
};
mService.getCount = function () {
return mService.messages.length;
};
mService.all = function () {
return mService.messages;
};
// Initialize the messages
// so we don't need to get the messages in each controller
getMessages();
return mService;
}]);
In your html, on your first controller setup an init function (ng-init="init()") that instantiates the factory:
<div ng-app="myApp">
<div ng-controller="HeaderCtrl" class="header" ng-init="init()">
Messages Count: {{ messageCount }}
</div>
<div ng-controller="DashboardCtrl" class="dashboard">
<ul ng-repeat="message in messages">
<li>{{ message }}</li>
</ul>
<button ng-click="getMessages()">Check for new messages.</button>
</div>
</div>
And in your controllers you just have the $rootScope.$on('messages.update' fn) and you can call manually by calling the services query() function which returns the promise:
myApp.controller('HeaderCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messageCount = messagesService.getCount();
});
// Manual call, if needed
$scope.getMessageCount = function () {
messagesService.query().then(function (successCallback) {
$scope.messageCount = messagesService.getCount();
});
};
}]);
myApp.controller('DashboardCtrl', [
'$scope',
'$rootScope',
'messagesService',
function ($scope, $rootScope, messagesService) {
$rootScope.$on('messages.update', function () {
$scope.messages = messagesService.all();
});
// Manual call, if needed
$scope.getMessages = function () {
messagesService.query().then(function (successCallback) {
$scope.messages = messagesService.all();
$rootScope.$broadcast('messages.update');
});
}
}]);
You can set cache:true on a $http request. There are numerous ways to data bind within angular without needing to use the $broadcast approach you are using. Also note, $broadcast from a scope will be receievd by all descendent scopes, so no need to inject $rootSCope just for that purpose, can listen on $scope.
Here's one approach that controllers use promise of $http to retrieve data. I used a button click to retrive data for DashControl so can see that request does get cached
myApp.factory('messagesService',function($http) {
return{
query:function query(callback) {
/* return promise of the request*/
return $http.get('messages.json',{ cache:true}).then(function(res){
/* resolve what data to return, can set additional properties of the service here if desired*/
return res.data
}).then(callback);
}
}
});
myApp.controller('HeaderCtrl',function($scope, messagesService) {
messagesService.query(function(messages){
$scope.messagesCount = messages.length;
});
});
myApp.controller('DashboardCtrl', function($scope, messagesService) {
/* use button click to load same data, note in console no http request made*/
$scope.getMessages=function(){
messagesService.query(function(messages){
$scope.messages = messages;
})
}
});
Essentially in this scenario, whatever controller calls the factory service first will generate the data cache
DEMO
I would do it like that:
myApp.factory('messagesService', function() {
var expose = {
messages: []
};
expose.query = function () {
// Would actually hit the database asyncronously
expose.messages = ['one', 'two', 'three', 'four'];
console.log('query');
};
// Initialization
expose.query();
return expose;
}
);
And in your controllers:
$scope.messagesCount = messagesService.messages.length;
Model with broadcasting and pre-hitting database looks heavy for me.
So here is code, that can be embedded in service:
var sv = this;
var deferred = sv.$q.defer();
if (sv._running) {
return sv._running;
}
sv._running = deferred;
It based on reusing promise. To make it query database once - just don't set sv._running to false and it will always return first obtained result.
Folks I have my application setup as below:
var myApp = angular.module('app', []);
myApp.factory('MotorList', ['$resource', function($resource) {
return $resource(baseURL + 'MotorList.json', {}, {} );
}]);
myApp.factory('MotorDataManager', function(MotorList) {
var List;
MotorList.query().$then(function(value){
List = value.data;
})
return {
getFullList: function() {
return List;
}
anotherFunction: function { ... }
}
});
myApp.controller('MainCtrl', function($scope,MotorDataManager){
$scope.tableData = MotorDataManager.getFullList();
})
IN my front-end I have a ng-repeat that loops through $scope.tableData.
However the issue I am facing is that $scope.tableData never gets rendered. The resource is working fine. It does return data, however I feel this is a timing issue but I am not sure how to resolve it.
Certainly, this is a timing issue. When you call MotorDataManager.getFullList(), you are getting undefined because the callback which sets it never gets set. So, $scope.tableData is undefined.
You need $scope.tableData to have a reference to something that changes. Here is one way to do it:
myApp.factory('MotorDataManager', function(MotorList) {
var list = [];
MotorList.query().$then(function(value){
angular.forEach(value, function(item) {
list.push(item);
});
});
return {
getFullList: function() {
return list;
}
}
});
myApp.controller('MainCtrl', function($scope,MotorDataManager){
$scope.tableData = MotorDataManager.getFullList();
});
In this example, you are now returning an array, so to start with, $scope.tableData will be an empty array. But that will be OK, because you now have a reference to something. When the $resource returns, it will populate the array (which is the same reference) so your controller will now have a populated array. Angular's data binding and digestion logic should take care of the rest.
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.