Reputation: 1080
I am newer to AngularJS and having an issue that I hope someone can point me in the right direction to figuring out. I have created a directive called sizeWatcher
which is placed as an attribute into the HTML which essentially just gets the height of the element it's placed on and echos that height into a scope variable named style
that I set onto another element through the use of ng-style="style"
.
I'm finding that whenever I open my accordion, the $watch
fires on the directive but it's firing multiple times. I have a console.log
in my $watch
and am seeing 3 log entries, the first 2 are the same (guessing this happens on click before the accordion opens, and then the accordion opens and the 3rd log entry is the final height after the accordion is opened). The main issue is that the style
variable is only getting set to the smaller heights before the accordion is expanded even though the log is registering the greater height as the last time the directive is hit -- How can I ignore the first $watch
event firings and only act accordingly on the last and final run-through of the directive? Any insight into this would be greatly appreciated. Relevant code attached below:
<div class="content-wrap" id="height-block" ng-style="style">
<!-- Other HTML etc... -->
<uib-accordion size-watcher close-others="oneAtATime">
<!-- Accordion Directive HTML.. -->
</uib-accordion>
</div>
JavaScript:
.directive("sizeWatcher", function () { //add size-watcher attribute to element on the page to have it echo its' height to the {{style}} scope
function link(scope, element, attrs) {
scope.$watch(function () { //watch element for changes
var height = element[0].offsetHeight;
console.log(height);
if (height > 150) {
scope.style = {
height: height + 'px'
};
}
});
}
return {
restrict: "AE", //attribute & element declarations
link: link
};
})
Upvotes: 1
Views: 1812
Reputation: 1001
This is happening because you are not using $watch correct way,
So in your case it would be something like this
scope.$watch(
function () {
return element[0].offsetHeight;
},
function () { //watch element for changes
var height = element[0].offsetHeight;
console.log(height);
if (height > 150) {
scope.style = {
height: height + 'px'
};
}
}
)
Please notice the first function, so whenever the value it is returning changes, the second callback will execute
Hope this helps you
Upvotes: 0
Reputation: 2596
Obviously an answer to this needs a link to the Angular-Documentation for $watch
;)
it states the following:
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 ofwatchExpression
didn't change. To detect this scenario within thelistener
fn, you can compare thenewVal
andoldVal
. If these two values are identical (===
) then the listener was called due to initialization.
which probably explains your first call.
I'm guessing the second call happens because the accordion is rerendered after initialization (with a title/ or label or anything) which triggers the $digest
and thus the $watch
expression on the height.
Finally the third call happens when you open the accordion and the height actually changes.
To fix this you can compare the newValue and oldValue of the watched expression like Maxim Shoustin said in his answer. Here is an example (again taken from the Angular-docs)
scope.$watch(
// This function returns the value being watched. It is called for each turn of the $digest loop
function() { return food; },
// This is the change listener, called when the value returned from the above function changes
function(newValue, oldValue) {
if ( newValue !== oldValue ) {
// Only increment the counter if the value changed
scope.foodCounter = scope.foodCounter + 1;
}
}
);
However if you actually want to change the style of the element you might want to take a look into ng-class
instead of manually registering any watchers!
Upvotes: 0
Reputation: 77910
How can I ignore the first $watch event firings and only act accordingly on the last and final run-through of the directive?
You can ignore watcher when new or old values are undefined
and not equal to each other:
$scope.$watch(function () {
return element.height(); // or something else
},
function (newVal, oldVal) {
if (newVal !== undefined && oldVal !== undefined && newVal !== oldVal) {
// your stuff
if (newVal > 150) {
scope.style = {
height: newVal + 'px'
};
}
}
});
Anyways you can play with if statement regards to your needs
FYI, to improve performance $watch
returns cancel callback so you can stop watcher whenever you want:
var cancelWatch = $scope.$watch(function () {
return element.height();
},
function (newVal, oldVal) {
if (<some condition>) {
cancelWatch();
}
});
Upvotes: 3