I'm doing this:
<body>
<div ng-controller="PresentationCtrl">
Find
<div>
<ul class ="unstyled">
<li ng-repeat="p in presentations">
<img ng-src="{{p}}" alt="presentation">
</li>
</ul>
<div>
</div>
</body>
I have one placeholder element inside of presentations that is set when the function PresentationCtrl is hit.
When the findAll link is hit, I add an element to the array like so: $scope.presentations.push(sUrl);
But, when viewing my page, the list doesn't grow.
Does ng-repeat only fire once, or something? Can I force it to "refresh" and display the current values in presentations?
Here's my controller
The console.log before I push the element into the array gets hit. and displays the string that I expect.
function PresentationCtrl($scope){
$scope.presentations = ["http://angularjs.org/img/AngularJS-small.png"];
$scope.findAll = function(){
var socket=null;
socket = io.connect('http://[server]:3000');
socket.on('handshake',function(){
socket.emit('viewAll',{tenant:'qa'});
socket.on('returnAll',function(back){
for(i=0;i<back.length;i++){
for(j=0;j<back[i].slides.length;j++){
socket.emit('signUrl',(back[i].slides[j].location));
break;
}
}
socket.on('signedUrls',function(sUrl){
console.log("back from signedUrls" + sUrl);
$scope.presentations.push(sUrl);
});
});
});
};
}
it works fine.
http://plnkr.co/edit/agadSBFgz8YhWuDTtCPx?p=preview
Your situation might be different.
--------- EDIT ---------
ok, I got your situation. You are running a function not under watch of AngularJS.
Thus, AngularJS does not know that your variable is changed, basically $digest is not called.
To mimic native Javascript asynchronous call, I use setTimeout.
The following code does not work. It change the value, but it won't be watched.
setTimeout( function() {
$scope.presentations = [6,7,8,9];
}, 1000);
However this code does work. It change the value as expected
$timeout( function() {
$scope.presentations = [6,7,8,9];
}, 1000);
The difference between setTimeout and $timeout is that $timeout is running under watch of AngularJS.
Thus to make it work, you need to run $scope.apply() after it, or within it as a function.
This code does work with setTimeout.
setTimeout( function() {
$scope.presentations = [6,7,8,9];
$scope.$apply();
}, 1000);
Can't explain all to you in detail because I don't really mastered the AngularJS code.
This blog has a very good explanation about why we need to run $apply.
http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Just for the tip, to get the answer quickly, it's better to have a simplified example in jsfiddle or plunkr.
I know $http call cannot be demoed in plunkr or jsfiddle, but you can mimic your situation using setTimeout. It can make your situation understandable to readers most of time.
Related
I have this Jquery code in my application.js file where when the <p id="kraken_btc_eur"> is clicked it return a value fetch from an API.
This code works but I want it to work automatically, without having to click. I tried setInterval() but it does not seem to work as it does not update the value. The value I am trying to return change constantly in the API that is why I would like it to be returned automatically, the same way you can see in stock market websites.
$('#kraken_btc_eur').click(function(){
$.get('https://api.kraken.com/0/public/Ticker?pair=XXBTZEUR', function(data){
var kraken_btc_eur = data.result.XXBTZEUR.c[0]
$("#kraken_btc_eur").html("<p>"+kraken_btc_eur+"</p>");
// console.log(data.result.XXBTZEUR.c[0]);
})
});
Many thanks for yur guidance.
The easiest way would be to do it like this:
$(document).ready(function(){
setInterval(function(){
$.get('https://api.kraken.com/0/public/Ticker?pair=XXBTZEUR', function(data){
$("#kraken_btc_eur p").text(data.result.XXBTZEUR.c[0]);
});
}, 10000);
});
PS. I would suggest to improve it further by calling the next update on ajax call finish.
If there is a way in ui-grid that I can know a grid is finish updating the rows?(like a filter is being applied etc)? I want to run some function after the grid view changes.
I tried the following method:
$scope.filteredRows = $scope.gridApi.core.getVisibleRows($scope.gridApi.grid);
$scope.$watch('filteredRows', function(){console.log('view updated');});
The above approach works when the grid just finish initiating, after that, it won't work anymore.
I also tried using the filterChanged api:
$scope.gridApi.core.on.filterChanged($scope, function() {
console.log('filter changed');
foo();
});
The problem with this method is that although I can get notified when the filter is changed, but if the grid is very large, it needs some time to finish updating the view, and before that, the function foo() is being called before the grid update is finished.
Any idea will be appreciated.
I've seen use of $scope.grid.api.core.on.rowsRendered( $scope, $scope.col.updateAggregationValue ); in ui-grid-footer-cell.js. I'm not sure exactly when rowsRendered fires, but given it's being used to calculate aggregations and aggregations require knowledge whenever the rows are changed, and must run after the rowsProcessors finish running, there's a good chance that it's what you want.
EDIT: the framework to use it would be:
Define a function that you want to call when the visible rows have changed
var myFunction = function() {
do some stuff
};
Set this function to be called whenever rows are rendered
$scope.gridApi.core.on.rowsRendered( $scope, myFunction );
Well, I found a workaround, in order to call the function after the grid is updated, which takes some time, I added a delay in filterChanged event:
$scope.gridApi.core.on.filterChanged($scope, function() {
console.log('filter changed');
$timeout(foo(),800);
});
To use the $timeout, you need to add that to your controller first.
I currently have a few ng-repeats in my view for building an agenda. Whenever I have a ng-repeat I can include a directive checking if the ng-repeat is done to trigger a method. Works perfect...
The problem is, I have like 5 ng-repeats, I don't want to include the directive for all 5 ng-repeats and check in the method if all 5 have called the method...
I just want a way to detect if all my ng-repeats (and other angular stuff) is finished building the view so for example I can put appointments in the agenda via JQuery.
Because ofcourse, putting appointments in the agenda (divs) before the agenda (divs) have been created won't work.
Thanks in advance!
UPDATE:
I'm using a REST backend to get the appointments from. So i can't just retrieve the appointments and try to show them in the agenda because the view might not be finished generating (using the ng-repeats)....
So I need something that is triggerd ones the view is done generating (ALL ng-repeats must be done) so i can start placing the appointments.
You should use $viewContentLoaded rather than any directive as this fires after everything in the view loaded.
Documentation :
https://docs.angularjs.org/api/ngRoute/directive/ngView
Code Example :
$scope.$on('$viewContentLoaded', function(event) {
// Your Code Goes Here
});
Note that this will only work if you are using ng-view from routing.
thereĀ“s a trick in Angular using a $timeout to delay execution of certain parts of the code until all directives have redered. I'm not sure it will work in this instance but you could try.
In your controller simply add
$timeout(function() {
// code to execute after directives goes here
});
You're probably better off not using jQuery to populate the agenda, but using Angular instead. This allows you to just wait until the data is loaded, which is much easier to detect, and Angular will make sure the DOM gets uploaded whenever it can do so.
In your case you can probably do something like this:
Controller:
$scope.days = [];
//Here we create the days for the calendar (7 in this case)
for (var i = 0; i < 7; i++) {
var hours = [];
//and the hours, with an empty appointment array for each hour
for (var i = 0; i < 24; i++) {
hours.push({ appointments: [] });
}
$scope.days.push({
hours : hours
});
}
//Then we can get the appointments from your api
getAppointments().then(function(appointments) {
//and add the results to the arrays created above
appointments.forEach(function(appointment) {
//This is some simplified logic that only uses the day of the week
//and hour of the appointment. Your logic would probably a bit more complex
//to properly put the appointment in the correct array
var day = appointment.date.getDay();
var hour = appointment.date.getHour();
$scope.days[day].hours[hour].appointments.push(appointment);
});
});
Template:
<div class="days" ng-repeat="day in days">
<div class="hours" ng-repeat="hour in day.hours">
<!-- I assume you have one hours div in which all appointments for that hour will go -->
<div class="appointments" ng-repeat="appointment in hour">
{{ appointment.title }}
</div>
</div>
</div>
That said, if you really want to detect when the view has finished loading then you have a couple of options:
Wait for all your data being loaded and then use a $timeout to ensure it has been rendered.
It would look something like this:
var somePromise = getPromise();
var someOtherPromise = getOtherPromise();
$q.all([somePromise, someOtherPromise])
.then(function() {
//At this point all data is available for angular to render
$timeout(function() {
//and now everything should actually be rendered
});
Listen for the $viewContentLoaded, but this only works if you use ng-view and might fire too early if your data is loaded asynchronous (I'm not entirely sure about the details here, since I usually avoid detecting when the view is loaded).
If all of the above fails you could just continuously check if the desired elements are loaded on the page.
You can use angularjs's directive to make it. With directive you can set the ready event for any dom element.
For more detail, refer to this great post and question.
In angular we have an enrich method which runs some rest call to enrich a data object, then sets a variable which will cause a hidden details tab to be visible. Something like this overly simplified example:
$scope.enrich = function(team){
angular.forEach(team.members, function(member){
member.getSkills().then(function(skills){
member.skills=skills;
}
});
$scope.enrichFinished=true;
};
I'm getting exceptions in the detail pane which is opened when enrichFinished is true. The exceptions appear to be due to a filter which attempts to filter on member.skill and discovers skill is undefined for the member. I assume the problem is that we open the detail tab as soon as enrichFinished is set, which is before the then clause that sets member.skills=skill; thus we have a datarace where skills's may not yet be set by the time we try to filter on it.
What is the cleanest way to tell angular to wait to run the filter in the detail's tab until after I have actually generated and saved the data I need? The actual enrich method enriches 3-4 different variables within the for loop.
You can set $scope.enrichFinished=true; after all promisses solve, so you can use $q.all like this
$scope.enrich = function(team){
$q.all(team.members.map(
function(member){
return member.getSkills().then(
function(skills){
member.skills=skills;
}
);
}
)
).then(function(){
$scope.enrichFinished=true;
});
};
I was brought in to fix a website that was on fire a couple months back. I've got most things under control and I'm down to fixing various wish-list items. One of them involved some angular code that I just can't seem to get to do what I want. On some pages there are videos followed by a short quiz. I need to update the user's scores after each event. So far, this proved to be easy enough for the total score which looked like this:
<a id="updateafterscore" href="~/user/leaderboard/" class="fill-div">
{{ profile.currentScore }}
</a>
And that got updated with this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
So far, so good. However other elements on the page have, thus far, proved impossible to update. Here's what's on the page:
I added the "id="refreshvideo" myself so I could try to alter the tag. Finally, here's the angular module for simple-circle (I've left out the actual drawing code since it's not really relevant):
angular.module('thrive.shared').directive('simpleCircle', function() {
return{
replace: true,
template: '<canvas width="60" height="60" style="margin: -10px 0 0 -15px;"></canvas>',
restrict: 'E',
scope: {
value: '#',
color: '#',
bgColor: '#',
forecolor: '#',
radius: '#'
},
link: function (scope, elem, attrs) {
var multiplyLength = 1;
var canvasElem = elem[0];
var inMotion = false;
if (scope.value <= 2) {
multiplyLength = 5;
}
scope.$watch('value', function() {
drawCircle(canvasElem, scope.color, scope.value * multiplyLength, scope.value, scope.name);
});
function drawCircle(canvas, color, calculatedPoints, displayPoints, name) {
So, to the question: how the heck do I update the number that's displayed? I tried various things:
document.getElementById('refreshvideo').setAttribute('value', data.Data.VideoWatchedCount);
document.getElementById('refreshvideo').setAttribute('data-value', data.Data.VideoWatchedCount);
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
None of these things worked. I inspected the canvas element in the source in the browser and I could see the value and data-value tags change to whatever I set them, but the image remained unchanged. Am I setting the wrong thing? (Perhaps whatever $watch is watching) Do I have to force some kind of re-paint of a canvas element?
#charlietfl means your solution is not actually using AngularJS - you're completely bypassing it. Angular provides two-way data binding between Javascript data and the HTML DOM. All you do is tell it where to draw data, and it will do that for you automatically, keeping it up to date from then on as the data changes.
In Angular, you never call getElementById and certain never set innerHTML because then you block Angular from doing its thing - in many cases you actually break it. Every one of those instances introduces a new bug while "patching" another.
Go back to your example template line:
<a ..attributes...>{{ profile.currentScore }}</a>
When it sees this, Angular will create what it calls a "watcher" on profile.currentScore. If its value right now is '1', it will render this as <a ...>1</a>.
Every digest cycle, that watcher will tell it to look at profile.currentScore to see if it changed. This line of code is pretty typical in JS:
profile.currentScore = 42;
Angular will "see" this happen through that watcher, and will automatically update the rendered template. You do nothing else - and if you ever feel that you need to, it almost always means something else is wrong.
If you're running into this a lot, try the "standard quick-fix". We see this a lot with people who didn't architect an application properly, and they're doing data model updates outside Angular's digest cycle where it can't "see" them. Try wrapping your update code in an $apply() call:
$scope.$apply(function() {
profile.currentScore = 42;
});
If you have a LOT of updates to make and you don't want to nest the call, you can also cheat, like this:
// Lots of stuff...
profile.currentScore = 42;
// Lots more stuff...
$scope.$apply();
You will know right away if you need to do this. If it works, you need to do it. :) If you get an error message in your console saying you're already in a digest cycle, you do NOT need to do it (it's something else).
I mentioned that I thought perhaps I was modifying the wrong profile variable and so it wasn't refreshing. So I looked back a little bit in the code that is supplying the numbers:
angular.module('episodes').controller('episodeCtrl', ['$scope', '$rootScope', '$window', 'episode', 'relatedCourses', 'Video', 'episodeItems', 'profile', 'Profile',
function ($scope, $rootScope, $window, episode, relatedCourses, Video, episodeItems, profile, Profile) {
// stuff skipped....
onComplete: function () {
Video.complete({ videoId: item.item.id }).$promise.then(function () {
item.progress = "Completed";
$scope.loadNextItem();
$scope.profile = Profile.get(); // <<-- gotten from somewhere
$.ajaxSetup({ cache: false });
$.get('/user/getCurrentUserPointsModel', function (data) {
if (data == "")
return;
$scope.profile.currentScore = data.Data.CurrentScore;
$scope.profile.videosWatched = data.Data.VideoWatchedCount;
$scope.profile.testTakenAndCorrectAnswerCount = data.Data.TestTakenAndCorrectAnswerCount;
Profile.save(); // <-- added
The value in $scope.profile is pulled from Profile, but I don't fully get how that gets where it is. I suppose I will need to figure that out because there's another place where these updates have to happen that lack that Profile information. Anyways I added the last 4 lines in place of this:
document.getElementById('updateafterscore').innerHTML = data.Data.CurrentScore;
... and all worked according to plan. I guess I tackle the other part later when I figure out how the data gets to the controller.
You can't do this that way. It's not Angular way of dealing with data.
Read the documentation before https://docs.angularjs.org/tutorial/step_04
If you need to modify your DOM using document.. probably sth wrong is with your code.
BTW. Stop using globals like:
document.getElementById('updateafterscore')