Skip to content

Commit 714c1a1

Browse files
committed
ensure user watchers are triggered after directive updates
1 parent 8d545da commit 714c1a1

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

src/api/data.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ exports.$watch = function (exp, cb, deep, immediate) {
7878
if (!watcher) {
7979
watcher = vm._userWatchers[key] =
8080
new Watcher(vm, exp, wrappedCb, {
81-
deep: deep
81+
deep: deep,
82+
user: true
8283
})
8384
} else {
8485
watcher.addCb(wrappedCb)

src/batcher.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,24 @@ function Batcher () {
1111

1212
var p = Batcher.prototype
1313

14+
/**
15+
* Reset the batcher's state.
16+
*/
17+
18+
p.reset = function () {
19+
this.has = {}
20+
// we have two separate queues: one for directive updates
21+
// and one for user watcher registered via $watch().
22+
// we want to guarantee directive updates to be called
23+
// before user watchers so that when user watchers are
24+
// triggered, the DOM would have already been in updated
25+
// state.
26+
this.queue = []
27+
this.userQueue = []
28+
this.waiting = false
29+
this.flushing = false
30+
}
31+
1432
/**
1533
* Push a job into the job queue.
1634
* Jobs with duplicate IDs will be skipped unless it's
@@ -24,7 +42,18 @@ var p = Batcher.prototype
2442

2543
p.push = function (job) {
2644
if (!job.id || !this.has[job.id] || this.flushing) {
27-
this.queue.push(job)
45+
// A user watcher callback could trigger another
46+
// directive update during the flushing; at that time
47+
// the directive queue would already have been run, so
48+
// we call that update immediately as it is pushed.
49+
if (this.flushing && !job.user) {
50+
job.run()
51+
return
52+
}
53+
var queue = job.user
54+
? this.userQueue
55+
: this.queue
56+
queue.push(job)
2857
this.has[job.id] = job
2958
if (!this.waiting) {
3059
this.waiting = true
@@ -34,32 +63,31 @@ p.push = function (job) {
3463
}
3564

3665
/**
37-
* Flush the queue and run the jobs.
38-
* Will call a preFlush hook if has one.
66+
* Flush both queues and run the jobs.
3967
*/
4068

4169
p.flush = function () {
4270
this.flushing = true
43-
// do not cache length because more jobs might be pushed
44-
// as we run existing jobs
45-
for (var i = 0; i < this.queue.length; i++) {
46-
var job = this.queue[i]
47-
if (!job.cancelled) {
48-
job.run()
49-
}
50-
}
71+
this.run(this.queue)
72+
this.run(this.userQueue)
5173
this.reset()
5274
}
5375

5476
/**
55-
* Reset the batcher's state.
77+
* Run the jobs in a single queue.
78+
*
79+
* @param {Array} queue
5680
*/
5781

58-
p.reset = function () {
59-
this.has = {}
60-
this.queue = []
61-
this.waiting = false
62-
this.flushing = false
82+
p.run = function (queue) {
83+
// do not cache length because more jobs might be pushed
84+
// as we run existing jobs
85+
for (var i = 0; i < queue.length; i++) {
86+
var job = queue[i]
87+
if (!job.cancelled) {
88+
job.run()
89+
}
90+
}
6391
}
6492

6593
module.exports = Batcher

test/unit/specs/batcher_spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,33 @@ describe('Batcher', function () {
5252
})
5353
})
5454

55+
it('calls user watchers after directive updates', function (done) {
56+
var vals = []
57+
function run () {
58+
vals.push(this.id)
59+
}
60+
batcher.push({
61+
id: 2,
62+
user: true,
63+
run: function () {
64+
run.call(this)
65+
// user watcher triggering another directive update!
66+
batcher.push({
67+
id: 3,
68+
run: run
69+
})
70+
}
71+
})
72+
batcher.push({
73+
id: 1,
74+
run: run
75+
})
76+
nextTick(function () {
77+
expect(vals[0]).toBe(1)
78+
expect(vals[1]).toBe(2)
79+
expect(vals[2]).toBe(3)
80+
done()
81+
})
82+
})
83+
5584
})

0 commit comments

Comments
 (0)