Reputation: 891
I have made a new directive that is basically a new carousel that extends the angular UI carousel bootstrap. This new carousel will display multiple divs in one frame. My new directive accepts any data in an array and a custom html template for each data.
However, if I use my carousel with a directive, there is a weird behaviour that I am seeing with watch inside the directive. Everything works fine, but the watch inside my directive is always getting the same value for newVal
and oldVal
. What I mean is this, here's my carousel code:
<slide ng-repeat="frame in ctrl.frames">
<div ng-repeat="divs in frame.divs">
<custom-directive data="divs"></custom-directive>
</div>
</slide>
and inside my customDirective
controller, I watch the change of data like this:
$scope.$watch("ctrl.data", function(newVal, oldVal){
if (newVal !== oldVal) {
// data is updated, redraw the directive in my case
// however newVal is always the same as oldVal
}
})
newVal and oldVal is always the same.. I expected the initial state to be oldVal = undefined
and newVal
will be my new data. However, this is never the case. Data is passed as a two-way binding to carousel and to custom directive (using '=' operator inside the scope of each directive).
Why is this happening? I have investigated this for long and here's my findings:
ng-repeat
inside my carousel, this will work. oldVal
will be undefined
and newVal
will be my data during the initial state. But why is ng-repeat causing this? I have read lots of article regarding golden rule of prototypical inheritance, that says ng-repeat will create new childScope that hides/shadows the parent, but that only happens to primitive object and I am passing an array to my data.I need to use ng-repeat in my carousel directive.. so I need to know why ng-repeat is causing this.. any suggestions?
UPDATE: Reproduced the problem in Plunkr here. As you can see, oldValue is always the same as newValue (I expected the oldValue to be undefined in the beginning)
Upvotes: 1
Views: 157
Reputation: 19193
When you register the $watch
in your link
function, Angular has already processed the bindings during the preLink
phase, hence you will never see undefined
the first time your watcher is executed (that initialiation call is the only moment on which oldVal and newVal are potentially the same. If the watcher was registered before the bindings resolution, the oldValue would be undefined
)
If you really want to see it, you could override the compile
phase and add a custom preLink
method (the default link
being the postLink
).
But I really doubt that you want to do that. Why is it a problem to not have undefined the first time? You should try to explain the real problem you are facing.
Also, note that if divs
that you pass to your directive is an array, you should use scope.$watchColleciton
instead of scope.$watch
in order to detect changes in the array elements instead of change of the whole array pointer.
Upvotes: 1
Reputation: 486
I think the problem you are having is just a misunderstanding of how $watch
works.
$watch
is expected to initialize with equal values. See the documentation here. Specifically:
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
So in other words, your check for if they are equal is so you don't detect the initial call
In your provided Plunker, if you need to do some initialization code, you can do two things:
$watch
function, and if they are then that is the initial call with their initial valueslink
function, the values are their initial values there (since the link
function is equivalent to post-link
, which means scope
values have already been linked) so you can put your code thereForked your Plunker here. Notice I moved the alert
outside of the $watch
, and the value is still valid
EDIT:
The reason you see a difference when it is not in the ng-repeat
and it is set up like your commented out code in the Plunkr is due to you adding data in a $timeout
. When the page initially loads, below are what the two types render as:
<a1 prop="data[0]"></a1>
data=[]
. directive element exists, calling link
with data[0]=undefined
. $watch
called with prop=undefined
<!-- ngRepeat: element in data track by $index -->
data
to be populated. No directive element exists, which means link
is not calledWhen you add items to data
after the timeout, they look like this:
<a1 prop="data[0]"></a1>
data[0]
is now defined so prop
is defined<div ng-repeat="element in data track by $index" class="ng-scope">
<a1 prop="element" class="ng-isolate-scope"></a1>
</div>
(x3)
link
function on each with data
now filled. $watch
called with prop
values linkedUpvotes: 1