I am new to Angularjs and learning. I am trying to build an app which calls an api service to get a value and update it in a dashboard.
The html code is as below,
<div class="span3" ng-controller="rookieController">
<div class="chart" ng-attr-data-percent="{{count}}"> {{count}} </div>
<div class="chart-bottom-heading"><span class="label label-info">Rookie</span>
</div>
The result is,
The ´{{count}}´ gets evaluated to a value in the outerhtml but the ´{{count}}´ in the innerhtml doesn't get evaluated. I debugged the code and when I add a breakpoint the innerHTML ´{{count}}´ gets evaluated.
This is kind of confusing. I think it is because the data is not loaded when the innerHTML is rendered, but then I tried evaluating the expression way before it is called in this tag and it evaluated perfectly. Then the data loading theory doesnt add up.
Actually the data-percent value is given as input to easypiechart jquery, since the jquery triggers before the data is available it doesn't animate.
Can someone explain on how this whole rendering thing works. Any help would be deeply appreciated.
The controller code,
opsApp.controller('rookieController',function($scope,$http) {
$http.get('/api/rookieCount')
.success(function(data) {
$scope.count = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
});
Update: Jquery where the directive is evaluated,
$.easyPieChart = function(el, options) {
var addScaleLine, animateLine, drawLine, easeInOutQuad, renderBackground, renderScale, renderTrack,
_this = this;
this.el = el;
this.$el = $(el);
this.$el.data("easyPieChart", this);
this.init = function() {
var percent;
_this.options = $.extend({}, $.easyPieChart.defaultOptions, options);
percent = parseInt(_this.$el.data('percent'), 10);
The line
percent = parseInt(_this.$el.data('percent'), 10);
is where the attribute is getting evaluated.
It should be like this:
<div class="chart" ng-attr-data-percent="count"> {{count}} </div>
Update:
I have got the problem : It is actually the angular controller loading the data late.
The GET for HTML gets fired and the HTML rendering starts
I have defined the angular app and then an angular controller around the <div>. The controller fires a GET for the API
Now before the controller can return the data to $scope, the jquery for the chart gets called and it sees an undefined value for the expression {{count}}
An ugly hack: I used the setTimeout function to call the jquery to fire approx 500ms afterwards. I know it is downright ugly but since I am learning angular now and I have not quite wrapped my head around $q and $promise. Still if somebody can suggest some elegant solutions, I will be glad. Thanks everybody for the help though. Appreciate it.
Related
I am using AngularJS + select2 (not ui-select).
Then in my view I have:
<select
name="rubros"
id="rubros"
class="select2 form-control"
ng-model="vm.comercio.tags"
ng-options="rubro.nombre for rubro in vm.rubros track by rubro.id"
multiple>
</select>
As you can see , the select is bind to a variable called "comercio.tags", that is an array of objects.
Well, here is the funny thing: the tags are displayed sometimes, and sometimes they don't. Even though the binding is working.
And the behavior is random; I can press F5 in my browser and the error appear and goes randomly.
Please take a look at the images:
The tags are retrieved by a get request ($http).
I don't know what is going on here. Because the behavior is randomly reproduced.
Update:
Add code requested by helper member
//controller initialization before this
var scope = this;
var id = $routeParams.id; //the ID of the commerce/store I want to edit (preload) in the page
//variable where I save the retrievedcommerce/store
scope.comercio = {
tags:[]
};
/*
HTTP request to retrieve the commerce/store with "id"
The model retrieved has a tags attribute that is correctly filled (as you can see in the images,
in the input on top of the select2, I used to debug)
*/
$http.get("http://localhost:8000/api/comercio/" + id).then(function (response) {
scope.comercio = response.data.model;
},
function (response) {
scope.comercio = null;
});
//other controllers instructions and declarations
As people suggested , the reason of this issue is because select2 is a jQuery plugin and we have to attach it to the angular "refresh"/compile/digest/watch ... cycle .. in other words, we need to attach select2 to the angularJS app lifecycle.
How do we do it ? using a directive. The official documentation is really extensive but you can appreciate the solution with this little piece of code.
app.directive("appSelect2", function($timeout) {
return {
restrict: "A",
link: function (scope, element, attrs) {
jQuery(element).select2();
scope.$watch(attrs.ngModel, function () {
$timeout(function () {
element.trigger('change.select2');
}, 100);
});
}
};
});
With this directive , and adding the "app-select2" attribute to the select2 input declared in your html ... it works perfectly.
I appreciate the help provided very much, thanks a lot.
I have an angular controller and a timeout calling a function that is setting a variable that an ng-show relies on. It seems the variable is successfully being changed, but the html element is still showing up.
The JS in my controller is:
setTimeout(function () {
console.log('showAlert is - ' + $scope.showAlert);
$scope.showAlert = false;
console.log('showAlert now is - ' + $scope.showAlert);
$scope.message = '';
}, 3000);
which is happening in the success function of an $http.post call (if that matters.
and the HTML is:
<h3 ng-show="showAlert">{[{message}]}</h3>
What appears in the console is:
showAlert is - true
showAlert now is - false
So it's being changed successfully, it just doesn't seem the template is following suit. It is correctly hidden when the page is loaded, and $scope.showAlert is originally set to false.
This seems like a very straightforward example, I don't know why this wouldn't be working. It acts the same if I put the tag into a contain as well.
Thank you!
As you are making changes to the scope after 3 sec by calling settimeout funtion, the DOM might be already loaded and uses the initial value of $scope.showalert, if the value got changed later, inorder to apply that new change, you can try placing $scope.$apply() at the end of our function.
So I am working on a application that has many many sections that are loaded via jquery ajax, some when it starts and some in response to other sections, and some are even nested. What I wan't to be able to do is say "Oh, the content in mainContainer changed?" Well, go compile the code in that section.
The reason is that I would like to be able to put ui-sref's into parts of the code we don't otherwise want to mess with yet. I came up with a solution but it isn't working the way I thought, I added a $watch to a custom directive but it fires a bazillion times. Here is the code.
Hopefully some one can explain how to get it to run once only when the content in X <div> is changed via one of the many ajax calls that happen, that way I don't need to immediately turn all of the old code into angular and can link to new code that is using angular.
app.directive('ngMainContainer', function ($compile, $timeout) {
console.log("main container directive");
return function (scope, elem, attrs) {
scope.$watch(function () {
return elem;
}, function (val) {
console.log("Something changed. " + val);
$timeout(function () {
$compile(val)(scope);
}, 0);
});
};
});
again, I thought putting a watch on the element passed into the directive was the answer however this runs even when I move the mouse and click on the page somewhere outside of the container that was loaded via jquery ajax.
Here is a solution that may work for you. I place directive ngMainContainer on our element we want to observe the html contents of.
In our controller I append <p>{{ content }}</p> which I first $compile, then update the scope var afterwards like $scope.html = angular.element(myDiv).html(). This is where the our directive $watch fires.
Here is the full working example. The <button> is appending html, but this should emulate the resolution of an ajax call request complete.
JSFiddle Link
app.directive('ngMainContainer', [function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
scope.$watch('html', function(n, o) {
if(n !== o) {
console.log(elem.contents())
// html has changed, do something!
}
});
}
}
}]);
app.controller('ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.content = "scope content";
var myDiv = document.getElementById('main');
$scope.appendContent = function() { // Replicate Ajax Callback
var dynamic = $compile('<p ng-cloak>{{ content }}</p>')($scope); // Compile content
angular.element(myDiv).append(dynamic);
$scope.html = angular.element(myDiv).html()
}
}]);
Edit
Here is a way to do this while appending content to the <div> with no notion of angular on the code that is performing the appending
Updated JSFiddle
<button id="noAngular" onclick="noAngularAppend()">Append Content - No Angular</button>
function noAngularAppend() {
angular.element(document.getElementById('main')).scope().externallyAppend('<p ng-cloak>{{ content }}</p>');
}
Directive Function (inject $compile)
scope.externallyAppend = function(element) {
var node = $compile(element)(scope);
elem.append(node);
scope.$apply(scope.html = elem.contents());
}
Sal, I up voted your answer because it ultimately did help me figure this out but in the end it wasn't quite what I was going for.
This is what I ended up with. In the controller for the main application there is function with only two lines in it.
$scope.doReshing = function (element) {
$compile(angular.element(element))($scope);
$scope.$apply();
}
Then in which ever partial that has an ng directive in it right at the top of the document.ready() I put the following.
var removeableContentContainer = $('#removableContentContainer');
angular.element(removeableContentContainer).scope().doReshing(removeableContentContainer);
This does exactly what I want. It lets me take an existing application change virtually nothing about how it works and lets me add ui-srefs in the "old" code and not have to worry about how or when it loads, when it does load, the document.ready() fires and it will tell angular that it needs to compile. This was my goal, you can pull bits of code in at any point in time and have them essentially act as though they were parts of that page with it's controller.
I am not too sure I am taking the best or correct approach to this, but basically what I am tyring to do is call a function in a directive. The reason being is I'm trying to clear a scope out that is controlled in the directive. For reference I am using this https://github.com/darylrowland/angucomplete directive, and the desired effect is when I add the selected item, it would clear out of the input.
So what I am trying to do is after I add the selectedObject to my list where I am storing (the add function) I am calling a broadcast to the directive like so
$scope.$broadcast('angucomplete:clearInput');
So for reference, here is the entire add function that is called
$scope.addLesson = function(){
var skillCheck = true;
for(i=0;i<$scope.lessonsHere.length;i++){
if($scope.lessonsHere[i].id === $scope.testObj.originalObject.id ){
errorOffScreen("You cannot add the same Lesson more than once");
skillCheck = false;
}
}
if(skillCheck){
$scope.lessonsHere.push($scope.testObj.originalObject);
$scope.testObj = {};
$scope.$broadcast('angucomplete:clearInput');
}
}
So in the directive itself I just have
$scope.clearInput = function(responseData, str) {
console.log("hit!");
};
It seems it does not work, I am not sure if this is the right approach, but I found another directive that calls itself from the controller with a broadcast so I figured it must be a good starting point.
Would appreciate any help here, as I am a bit in the dark with this topic. Thanks for reading!
I have a function within my angular controller, I'd like this function to be run on document ready but I noticed that angular runs it as the dom is created.
function myController($scope)
{
$scope.init = function()
{
// I'd like to run this on document ready
}
$scope.init(); // doesn't work, loads my init before the page has completely loaded
}
Anyone know how I can go about this?
We can use the angular.element(document).ready() method to attach callbacks for when the document is ready. We can simply attach the callback in the controller like so:
angular.module('MyApp', [])
.controller('MyCtrl', [function() {
angular.element(document).ready(function () {
document.getElementById('msg').innerHTML = 'Hello';
});
}]);
http://jsfiddle.net/jgentes/stwyvq38/1/
See this post How to execute angular controller function on page load?
For fast lookup:
// register controller in html
<div data-ng-controller="myCtrl" data-ng-init="init()"></div>
// in controller
$scope.init = function () {
// check if there is query in url
// and fire search in case its value is not empty
};
This way, You don't have to wait till document is ready.
Angular has several timepoints to start executing functions. If you seek for something like jQuery's
$(document).ready();
You may find this analog in angular to be very useful:
$scope.$watch('$viewContentLoaded', function(){
//do something
});
This one is helpful when you want to manipulate the DOM elements. It will start executing only after all te elements are loaded.
UPD: What is said above works when you want to change css properties. However, sometimes it doesn't work when you want to measure the element properties, such as width, height, etc. In this case you may want to try this:
$scope.$watch('$viewContentLoaded',
function() {
$timeout(function() {
//do something
},0);
});
Angular initializes automatically upon DOMContentLoaded event or when
the angular.js script is evaluated if at that time document.readyState
is set to 'complete'. At this point Angular looks for the ng-app
directive which designates your application root.
https://docs.angularjs.org/guide/bootstrap
This means that the controller code will run after the DOM is ready.
Thus it's just $scope.init().
The answer
$scope.$watch('$viewContentLoaded',
function() {
$timeout(function() {
//do something
},0);
});
is the only one that works in most scenarios I tested. In a sample page with 4 components all of which build HTML from a template, the order of events was
$document ready
$onInit
$postLink
(and these 3 were repeated 3 more times in the same order for the other 3 components)
$viewContentLoaded (repeated 3 more times)
$timeout execution (repeated 3 more times)
So a $document.ready() is useless in most cases since the DOM being constructed in angular may be nowhere near ready.
But more interesting, even after $viewContentLoaded fired, the element of interest still could not be found.
Only after the $timeout executed was it found. Note that even though the $timeout was a value of 0, nearly 200 milliseconds elapsed before it executed, indicating that this thread was held off for quite a while, presumably while the DOM had angular templates added on a main thread. The total time from the first $document.ready() to the last $timeout execution was nearly 500 milliseconds.
In one extraordinary case where the value of a component was set and then the text() value was changed later in the $timeout, the $timeout value had to be increased until it worked (even though the element could be found during the $timeout). Something async within the 3rd party component caused a value to take precedence over the text until sufficient time passed. Another possibility is $scope.$evalAsync, but was not tried.
I am still looking for that one event that tells me the DOM has completely settled down and can be manipulated so that all cases work. So far an arbitrary timeout value is necessary, meaning at best this is a kludge that may not work on a slow browser. I have not tried JQuery options like liveQuery and publish/subscribe which may work, but certainly aren't pure angular.
I had a similar situation where I needed to execute a controller function after the view was loaded and also after a particular 3rd-party component within the view was loaded, initialized, and had placed a reference to itself on $scope. What ended up working for me was to setup a watch on this scope property and firing my function only after it was initialized.
// $scope.myGrid property will be created by the grid itself
// The grid will have a loadedRows property once initialized
$scope.$watch('myGrid', function(newValue, oldValue) {
if (newValue && newValue.loadedRows && !oldValue) {
initializeAllTheGridThings();
}
});
The watcher is called a couple of times with undefined values. Then when the grid is created and has the expected property, the initialization function may be safely called. The first time the watcher is called with a non-undefined newValue, oldValue will still be undefined.
Here's my attempt inside of an outer controller using coffeescript. It works rather well. Please note that settings.screen.xs|sm|md|lg are static values defined in a non-uglified file I include with the app. The values are per the Bootstrap 3 official breakpoints for the eponymous media query sizes:
xs = settings.screen.xs // 480
sm = settings.screen.sm // 768
md = settings.screen.md // 992
lg = settings.screen.lg // 1200
doMediaQuery = () ->
w = angular.element($window).width()
$scope.xs = w < sm
$scope.sm = w >= sm and w < md
$scope.md = w >= md and w < lg
$scope.lg = w >= lg
$scope.media = if $scope.xs
"xs"
else if $scope.sm
"sm"
else if $scope.md
"md"
else
"lg"
$document.ready () -> doMediaQuery()
angular.element($window).bind 'resize', () -> doMediaQuery()
If you're getting something like getElementById call returns null, it's probably because the function is running, but the ID hasn't had time to load in the DOM.
Try using Will's answer (towards the top) with a delay. Example:
angular.module('MyApp', [])
.controller('MyCtrl', [function() {
$scope.sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
angular.element(document).ready(function () {
$scope.sleep(500).then(() => {
//code to run here after the delay
});
});
}]);
Why not try with what angular docs mention https://docs.angularjs.org/api/ng/function/angular.element.
angular.element(callback)
I've used this inside my $onInit(){...} function.
var self = this;
angular.element(function () {
var target = document.getElementsByClassName('unitSortingModule');
target[0].addEventListener("touchstart", self.touchHandler, false);
...
});
This worked for me.
$scope.$on('$ViewData', function(event) {
//Your code.
});