Angular: ng-show and functions - angularjs

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

Related

how does binding to a service method work?

In this first example, the the view is updated everytime the return value of the method sessionStatus() of userService changes.
but If a change the sessionStatus method like in the second example :
sessionStatus: function(){
return Date.now();
},
it no longer refreshes "live" in the view. I would expect to see the date in milliseconds progressing in the view.
Is it because the second example, the value returned by the sessionStatus method is changing to fast ? Why isn't it updating the view ?
The value is not updating because there is no AngularJS event to cause a digest cycle:
Add a button that has an ng-click and the value will update everytime it is clicked:
<body ng-app="myServiceModule">
<div id="simple" ng-controller="MyController">
<p>I would expect the number bellow to be ticking</p>
<p>Session Status: {{sessionStatus()}}</p>
</div>
<button ng-click="">Update</button>
</body>
By clicking on the Update button, an AngularJS digest cycle gets initiated and the session status updates.
The DEMO on PLNKR.
Or use the $interval service to initiate a repeating digest cycle:
angular.
module('myServiceModule', []).
controller('MyController', function ($scope, $interval, userService) {
$scope.sessionStatus = function(){
return userService.sessionStatus();
};
$interval(null, 250);
})
In this example, the $interval statement starts a digest cycle every 250 milliseconds.
The DEMO on PLNKR

Can a filter be an impure function?

The following works:
<script>
angular.module('myApp', [])
.filter('myFilter', ['$rootScope', function($rootScope) {
return function(v) {
return $rootScope.capitalize ? (v && v.toUpperCase()) : v;
};
}])
.controller('myController', ['$rootScope', '$scope', function($rootScope, $scope) {
$scope.flipCapitalize = function() {
$rootScope.capitalize = !$rootScope.capitalize;
}
}]);
</script>
{{"Hello" | myFilter }}
<div ng-controller="myController">
<button ng-click="flipCapitalize()">flip</button>
</div>
The word "Hello" on the screen flips between mixed case and upper-case when you press the button.
But Angular doesn't "know" it is supposed to re-invoke the filter function. It just does so because the click restarts the digest look and it re-does everything.
My question: is that behavior guaranteed? Can I always assume that the filter will be reinvoked in the digest loop and I can use whatever data in any scope I can find? Or did I just get lucky?
My secondary question: if the filter function is reinvoked on every digest loop, is there some way I can defeat that behavior? Can I tell it, unless the arguments have changed, don't call this function again, you'll just get the same answer? Or do I have to memoize by hand?
According to the angular docs, if you want to guarantee your filter to work you need to mark it as "stateful" using the $stateful property of the filter.
It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by Angular, which often leads to performance issues. Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter.
If you however do need to write a stateful filter, you have to mark the filter as $stateful, which means that it will be executed one or more times during the each $digest cycle.
So you should mark your filter as stateful to guarantee that behavior:
.filter('myFilter', ['$rootScope', function($rootScope) {
var filter = function(v) {
return $rootScope.capitalize ? (v && v.toUpperCase()) : v;
};
filter.$stateful = true;
return filter;
}])

$watch on clientWidth isn't working

Inside of my directive I have this and it works only in the beginning, and whenever I resize later it never fires.
scope.$watch ->
cw: element[0].clientWidth
,(newValue, oldValue)->
if newValue.cw isnt oldValue.cw
console.log "changed"
,true
Your function looks right to me, but it's important to note that it won't fire on resize unless you manually trigger a digest on the resize event.
Without this, angular doesn't realise there's been an event that should trigger a digest, and so none happen.
Try adding this (remember to inject $window):
angular.element($window).bind('resize', ()->
scope.$apply();
)
Just like Jason Goemaat said, its because Watches are only checked on a $digest cycle
Depending on how the way your element is resized maybe this example may help you.
Here is a menu that expands it self when pressed on the arrow.
<div class="menu" ng-class="{'expanded' : main.expanded}">
<div class="menu-handler" ng-click="main.expanded = !main.expanded">
</div>
</div>
The menu is expanded by a ng-click that makes a change on the scope, in this case its a boolean var that acts as a flag. As the opening of the menu is made throught the change in the scope it calls the $digest cycle iterating through all $watch.
scope.$watch(function() {
return element[0].clientWidth;
}, function(oldValue, newValue) {
console.log(oldValue, newValue);
});
You can view the full example in this JSfiddle
https://jsfiddle.net/owenbcal/zf17s0mL/
Hope i was helpfull.
the way I know about watching this kind of values is making a function that is fired once the value changes so then I watch that function :
in the service:
this.getDataOut = function () {
return self.dataOut;
};
in the controller:
$scope.$watch(dataService.getDataOut, function() {
$scope.dataOut = dataService.dataOut;
});

How does AngularJS encode the ngClick calls to a controller?

Here's a brief example here: http://plnkr.co/edit/x1sSw8?p=preview.
This is the HTML file:
<body ng-controller="MainCtrl as main">
<p>Hello {{main.name}}!
My current value for <code>main.value</code> is {{main.value}}.</p>
<button ng-click="main.doSomething()">Click Me</button>
</body>
Here is the app.js file:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function() {
this.name = 'World';
this.value = 0;
this.doSomething = function(){
this.value += 5;
}
this.doSomethingElse = function(){
this.value -= 5;
}
});
When I click the button, I normally expect the counter to go up in increments of 5. When I inspect the button and change the ng-click function to doSomethingElse(), why does it still continue to increment?
I understand I'm not directly changing the event listeners, so I wonder how AngularJS is protecting the controllers from outside tampering (like in the example I described). My guess is that it builds event listeners up after it parses the DOM for the first time (reading ng-click attributes and all).
When I inspect the button and change the ng-click function to doSomethingElse(), why does it still continue to increment?
You should check out the compilation & linking processes in Angular. The short reason is that, angular goes over these elements once they are created, not every time you click it. Since it is scanned and parsed by angular when the element is created, manually changing it by hand does not change anything, because angular does not check out what's written in there.
More info: https://docs.angularjs.org/guide/compiler

ngForm is invalid before model update

In my application I $watch if the form is valid before doing some stuff. The problem is that ngForm won't compile models before I use it.
Exemple : http://plnkr.co/edit/Y7dL67Fn7SaSEkjiFf2q?p=preview
JS
$scope.results = [];
$scope.$watch(function() {
return $scope.testForm.$valid;
},
function( valid ) {
$scope.results.push( valid );
}
)
HTML
<ng-form name="testForm" ng-init="test = 1">
<input ng-model="test" required>
</ng-form>
<p ng-repeat="result in results track by $index" ng-class="{'false': !result, 'true': result}">{{ result }}</p>
Results :
false // Wrong
true
The form shouldn't be invalid at first because $scope.test is set to 1.
Any clue ?
According to the docs:
After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization.
It (almost always) makes sense to ignore this first call using a check:
$scope.$watch('testForm.$valid', function (newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.results.push(newValue);
});
See, also, this short demo.
Not sure if correctly understand, but it could be that the first time AngularJS checks,
$scope.results = [];
is empty, therefore result evaluating to false before you push anything in it.
If you start with an non-empty result, say:
$scope.results = [1];
First evaluation is truey.
I don't think $watch is the proper method. I think your issue has to do with how $watch works, and the digest cycle of Angular.

Resources