diff --git a/lib/mixins/element-mixin.html b/lib/mixins/element-mixin.html index a3bb9c9eec..aae9c992e9 100644 --- a/lib/mixins/element-mixin.html +++ b/lib/mixins/element-mixin.html @@ -656,6 +656,10 @@ hostStack.endHosting(this); this.$ = this.root.$; } + // The super.ready here makes this element able to observe data changes. + // We must wait to do this until after client dom is created/attached + // so that any notifications fired during this process are not handled + // before all clients are ready. super.ready(); } @@ -669,10 +673,10 @@ * @override */ _readyClients() { - super._readyClients(); if (this._template) { this.root = this._attachDom(this.root); } + super._readyClients(); } diff --git a/lib/mixins/property-effects.html b/lib/mixins/property-effects.html index 2321826b7e..9cc1d0adcc 100644 --- a/lib/mixins/property-effects.html +++ b/lib/mixins/property-effects.html @@ -227,8 +227,11 @@ } } // Flush host if we actually notified and host was batching + // And the host has already initialized clients; this prevents + // an issue with a host observing data changes before clients are ready. let host; - if (notified && (host = inst.__dataHost) && host._flushProperties) { + if (notified && (host = inst.__dataHost) && host._flushProperties + && host.__dataClientsInitialized) { host._flushProperties(); } } @@ -1509,6 +1512,11 @@ if (!this.__dataClientsInitialized) { this._readyClients(); } + // Before ready, client notifications do not trigger _flushProperties. + // Therefore a flush is necessary here if data has been set. + if (this.__dataPending) { + this._flushProperties(); + } } /** diff --git a/test/unit/property-effects-elements.html b/test/unit/property-effects-elements.html index 7005ec55b3..aeb74816c1 100644 --- a/test/unit/property-effects-elements.html +++ b/test/unit/property-effects-elements.html @@ -184,6 +184,9 @@ }; this.titleChanged = sinon.spy(); }, + ready: function() { + this.isReady = true; + }, clearObserverCounts: function() { for (var i in this.observerCounts) { this.observerCounts[i] = 0; @@ -344,7 +347,8 @@ 'boundcomputedvalueChanged(boundcomputedvalue)', 'boundcomputednotifyingvalueChanged(boundcomputednotifyingvalue)', 'boundreadonlyvalueChanged(boundreadonlyvalue)', - 'boundCustomNotifyingValueChanged(boundCustomNotifyingValue)' + 'boundCustomNotifyingValueChanged(boundCustomNotifyingValue)', + 'boundnotifyingvalueWithDefaultChanged(boundnotifyingvalueWithDefault)' ], properties: { a: { @@ -367,7 +371,8 @@ boundcomputedvalueChanged: 0, boundcomputednotifyingvalueChanged: 0, boundreadonlyvalueChanged: 0, - boundCustomNotifyingValueChanged: 0 + boundCustomNotifyingValueChanged: 0, + boundnotifyingvalueWithDefault: 0 }; }, computeComputedValue: function(a, b) { @@ -378,23 +383,39 @@ this.observerCounts[i] = 0; } }, + assertClientsReady: function() { + assert.isTrue(this.$.basic1.isReady, 'basic1 not `ready` by observer time'); + assert.isTrue(this.$.basic2.isReady, 'basic2 element not `ready` by observer time'); + assert.isTrue(this.$.basic3.isReady, 'basic3 element not `ready` by observer time'); + assert.isTrue(this.$.basic4.isReady, 'basic4 element not `ready` by observer time'); + }, boundvalueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundvalueChanged++; }, boundnotifyingvalueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundnotifyingvalueChanged++; }, boundcomputedvalueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundcomputedvalueChanged++; }, boundcomputednotifyingvalueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundcomputednotifyingvalueChanged++; }, boundreadonlyvalueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundreadonlyvalueChanged++; }, boundCustomNotifyingValueChanged: function() { + this.assertClientsReady(); this.observerCounts.boundCustomNotifyingValueChanged++; + }, + boundnotifyingvalueWithDefaultChanged: function() { + this.assertClientsReady(); + this.observerCounts.boundnotifyingvalueWithDefault++; } });