I try to set up a timer by angular.js.
Could someone tell me why the method2 won`t work?
I had set up an alert to make sure the function had been triggered.
Many thanks.
HTML
<div ng-controller="MyController">
<h5>{{ clock }}</h5>
</div>
JS
function MyController($scope) {
alert("start point check");
var updateClock = function() {
$scope.clock = new Date();
};
//method1.
setInterval(function() {$scope.$apply(updateClock);}, 1000);
//method2.
//setInterval(updateClock, 1000);
updateClock();
};
Actually, you should be using $timeout instead of setInterval.
The reason second option does't work is that Angular $digest doesn't identify the changes made via setInterval. It's outside of Angular, hence you need to do a $scope.$apply.
check this link for better understanding of $apply.
Related
I am writing some basic unit tests for my AngularJS app. I have some bindings on the UI with a scope variable inside my directive whichis populated on the completion of a promise.
HTML:
<div id="parent">
<div id="child" ng-repeat="l in aud">
// Other Stuff
</div>
</div>
Directive:
link: function(scope){
service.getArray().$promise.then(function(data){
scope.aud = data;
}
Test:
describe('my module', function () {
var $compile: ICompileService, $rootScope: IScope, directive: JQuery<HTMLElement>;
// Load the myApp module, which contains the directive
beforeEach(angular.mock.module('my-module'));
beforeEach(angular.mock.module(($provide) => {
$provide.service('service', () => {
return {
getArray: () => {
return Promise.resolve(
["item1", "item2"]
);
}
}
});
// Store references to $rootScope and $compile
// so they are available to all tests in this describe block
beforeEach(inject(($httpBackend: IHttpBackendService, _$compile_: ICompileService, _$rootScope_: IRootScopeService) => {
$compile = _$compile_;
$rootScope = _$rootScope_.$new();
directive = $compile('<my-directive></my-directive>')($rootScope)
$rootScope.$apply();
}));
describe('account-utility directive', function () {
it('account utility directive details panel is shown on click', function () {
let list = directive.find("parent"); // Finds this
let listItems = list.find("child"); // Cannot find this. Throws error.
console.log(list); // innerHTML still shows ngrepeat unsubstituted by divs
expect(listItems.length).toBe(2);
});
});
});
I debugged the whole thing and the promise is resolved and the data is assigned to the scope variable 'aud'. However seems like my copy of scope for the test and the app are different. Whats going on here?
beforeEach((done) => {
directive = $compile('<my-directive></my-directive>')($rootScope);
$rootScope.$digest();
setTimeout(() => {
$rootScope.$digest();
done();
});
});
Done helps you wait till all asynchronous tasks are picked up from the stack.
apply()
works too
When the Angular's promise is resolved you need to notify it so it will run its dirty check.
In order to do that you need to invoke $rootScope.apply() inside yours it clause.
Think of it like that, your $rootScope.apply() call in the before each clause invokes your directive's link function, the that function registers a Promise resolvment in Angulars queue, but it not got processed.
Say you have a template like
<a ng-show=function()>a link</a>
My question is: when is function run? How can I tell angular that it is time to re-run the function?
Well, ng-show takes a Boolean value, so your best option for using it is to call your function somewhere in the logic that sets ng-show.
$scope.show = false;
$scope.showSomething = function(myCondition){
if(myCondition){
myFunction();
$scope.show = true;
}
};
<div ng-show='show'></div>
Any expression in ng-show is evaluated at every digest cycle, so the function is run on every digest cycle as well. Thus any change to the value returned by the function, will reflect in the view. If your function makes any changes to the application state that triggers a new digest cycle, you may run into an infinite digest loop (which isn't great).
So be careful about using functions in directives like ng-show. If the function is pure though (it doesn't have any side-effects), you should be safe using it.
Here's a plunker that shows the function being called on every digest.
(Note: in the plunker example, clicking the button actually triggers two digest cycles, since the click event triggers one, and toggling the $scope variable triggers another.)
ng-show="booleanVar" takes a condition or a Boolean value . You can change the value of condition expression to hide and show values at run time .
But if you want some function to be called when ng-show is triggered then you can use $watch in your model .
<div ng-app="myApp" ng-controller="myCtrl" ng-init="isDisplayed = true">
<div ng-show="isDisplayed">something</div>
<button ng-click="isDisplayed = !isDisplayed">Toggle</button>
</div>
var myApp = angular.module('myApp', [])
.controller('myCtrl', function($scope, $log) {
$scope.$watch('isDisplayed', function(newValue, oldValue) {
if (newValue !== oldValue) {
$log.log('Changed!');
}
});
});
See details here
I have a timer that would re-submit the page at intervals. Is there an angular way to do that?
Right now I was just submitting the form inside a script:
<script type="text/javascript" defer="defer">
window.setTimeout( function () {
document.getElementById( "srchForm" ).submit();
}, 300000 ); //5 min
</script>
<form id="srchForm" ng-controller="OutageViewController" ng-submit="loadData(0)">
<p id="div_srchForm"> ....</p>
<button id="btnReload" type="submit" >Submit</button>
</form>
but it would give me 404 error status:
404 - File or directory not found.
The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable
Use $interval service to schedule a function for repeated execution with a time interval in between.
Example that used the $interval service to schedule a function call every 3 seconds:
var app = angular.module('MyApp', []);
app.controller('MainCtrl', function($scope, $interval) {
$scope.loadData = function() {
console.log("loadData - Interval occurred");
};
$interval(function() {
$scope.loadData();
}, 3000);
});
<form id="formX" ng-controller="MainCtrl" ng-submit="loadData(0)">
<p id="div_srchForm"> ....</p>
<button id="btnReload" type="submit">Submit</button>
</form>
https://plnkr.co/edit/yqMW9ZENoqZwfDtqlsTy
I would rather ask why you are trying to simulate a button click in the first place. As others have hinted at, browser automation as a user experience is typically a poor choice and reflects poor organization in your software design. I would recommend using the $timeout already available in Angular to do this from the controller instead. Not only does this put your front end logic where it belongs,in the controller, but also gives you a clean teardown mechanism so your timer doesn't continue I run if the controller isn't active (and prevents you from writing another hack to mitigate that).
I have found a non-Angular way to work around to get to the ng-submit handler:
Looks like I cannot call form.submit in the callback. When I did that, they can't "see" ng-submit and it lost me where the call has taken me when it complained not finding "resources" which is the page itself! My work-around to connect to the ng-submit is,
Now instead of doing form.submit in the callback, I click the submit button which is just primitive js but it did the job!
<script type="text/javascript" defer="defer">
window.setTimeout( function () {
document.getElementById( "btnReload" ).click();
//window.location.reload();
}, 300000 ); //5 min
</script>
The button activated ng-submit because of the way the scope of submit was set up. Just don't ask me why clicking the button is working but form-submitting is not working!
After all, I still want to know the Angular Way to do it, not my primitive js !!
I am trying to update a span's text after a call back function in Angular.
Here is my HTML:
<div ng-controller="onDragController">
<div id="draggableArea">
<div id="rectangle1" data-drag="true" jqyoui-draggable="{onDrag: 'dragCallback'}" data-jqyoui-options="{containment: '#draggableArea'}"></div>
</div>
<div>
<span ng-model="rectangleOne">{{rectangleOne.leftOffset}}</span>
</div>
</div>
And my controller is:
var App = angular.module('drag-and-drop', ['ngDragDrop', 'ui.bootstrap']);
App.controller('onDragController', function($scope) {
$scope.rectangleOne = {};
$scope.rectangleOne.leftOffset = 'ASDF';
$scope.dragCallback = function (event, ui) {
$scope.rectangleOne = {leftOffset: '12345'};
};
});
If I toss an alert in my callback function then I am seeing that the leftOffSet is updated, but on my HTML page the {{rectangleOne.leftOffset}} is staying the same.
What am I missing here...?
Use $apply in your dragCallback as follows:
$scope.dragCallback = function(event, ui) {
$scope.$apply(function() {
$scope.rectangleOne = {
leftOffset: '12345'
};
});
};
This will update your leftOffset in the scope. Here is a Plunker.
It might be helpful to better understand how Angular does two-way data binding. This blog post on how apply works, and why one would be motivated to use it, is pretty good.
In summary, for your changes to $scope.rectangleOne:
You can call $scope.$digest() every time you make a simple change like this. You need to do this so Angular knows to check if your bound data got updated.
Alternatively, you can use $scope.$apply, make the changes to $scope.rectangleOne inside a callback to $apply. It's doing the same thing, $apply ends up calling $digest() indirectly.
In code:
$scope.$apply(function() {
$scope.rectangleOne.leftOffset = '12345';
});
Hope that helps solve your problem, and add a bit of understanding to what's happening behind the scenes!
I want to validate max number of checked checkboxes.
I have this function and it works.
$('.Item:input').each( function (i, element) {
$(element).click(function() {
if ($('.Item:checked').length > 10) {
alert('error');
$(element).attr('checked', false)
}
})
});
Now i don't know how and where to call it in angular. I suppose to call it on ng-click event, but it works (obviously) only on the second click (first one only call the function)
Anyone can help me?
I solved it using data-ng-init instead of ng-click.
The code you have is for jquery, for angular approach is completly diffrent.
I have put an example.
http://jsfiddle.net/neolivz/8x15o1t4/
You can set up a model
<input type="checkbox" ng-model="test.value" ng-change ="updateCount(test)">
and do what you want in the updateCount api
$scope.updateCount = function(test){
if(test.value){
$scope.count++;
}else{
$scope.count--;
}
if($scope.count==10){
test.value = false;
$scope.count--;
return;
}
}
$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.
I think this might solve your problem, or at least take you nearer to the solution.