bug: datetime ignores a value set right after it is attached when zone.js is loaded, and shows the current time · Issue #31565 · ionic-team/ionic-framework · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: datetime ignores a value set right after it is attached when zone.js is loaded, and shows the current time #31565
I have searched for existing issues that already report this problem, without success.
Ionic Framework Version
v8.x, v9.x
Current Behavior
With the custom elements build (@ionic/core/components, which @ionic/angular/standalone and the standalone @ionic/angular 9 use), an ion-datetime that gets its value one microtask after it is attached ignores that value: the wheels open on the current time. The value property does hold the new value, but valueChanged never runs, so the working parts are never updated.
This only happens when zone.js is loaded. That is the default for Angular, and <ion-datetime [(ngModel)]="value"> hits it every time, because NgModel writes the value in a resolved-promise callback after the element has been created and connected.
Pressing confirm without scrolling still keeps the bound value. But the user sees, and scrolls from, the current time.
Expected Behavior
The wheels show the value that was set, as they do without zone.js, with the lazy-loaded build, or when the value is set synchronously before the element is attached.
Steps to Reproduce
Load zone.js and define ion-datetime from @ionic/core/components.
Inside a zone: create <ion-datetime presentation="time">, attach it, then set value to a time that isn't now in Promise.resolve().then(...).
The wheels show the current time; dt.value is the value you set.
Repeat without zone.js (?zone=0 in the reproduction) or set the value synchronously: the wheels show the value.
A single HTML file with no build step. It loads @ionic/core@9.0.7 and zone.js@0.15.1 from jsDelivr. Open it as is to see the bug, or with ?zone=0 to see the expected behaviour.
index.html
<!doctype html><html><head><metacharset="utf-8" /><title>ion-datetime ignores a value set one microtask late under zone.js</title><linkrel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9.0.7/css/ionic.bundle.css" /><script>if(newURLSearchParams(location.search).get('zone')!=='0'){document.write('<script src="https://cdn.jsdelivr.net/npm/zone.js@0.15.1/bundles/zone.umd.js"><\/script>');}</script></head><body><p>Bound value: <b>12:00</b>. Current time: <bid="now"></b>. Wheels show: <bid="shown">…</b></p><divid="host"></div><scripttype="module">import{defineCustomElement}from'https://cdn.jsdelivr.net/npm/@ionic/core@9.0.7/components/ion-datetime.js/+esm';defineCustomElement();awaitcustomElements.whenDefined('ion-datetime');constvalue=newDate().toISOString().slice(0,10)+'T12:00:00';constrender=()=>{// What Angular does for <ion-datetime presentation="time" [(ngModel)]="value">:// inputs are set synchronously, the element is attached, and NgModel writes the value one microtask later.constdt=document.createElement('ion-datetime');dt.presentation='time';document.getElementById('host').appendChild(dt);Promise.resolve().then(()=>{dt.value=value;});window.dt=dt;};if(window.Zone)Zone.current.fork({name: 'angular'}).run(render);elserender();document.getElementById('now').textContent=newDate().toTimeString().slice(0,5);setTimeout(()=>{constcols=[...dt.shadowRoot.querySelectorAll('ion-picker-column')].map(c=>c.value);document.getElementById('shown').textContent=JSON.stringify(cols)+' (dt.value = '+dt.value+')';},1500);</script></body></html>
Output in headless Chromium at 23:16:
Wheels
dt.value
with zone.js
[23, 16, "pm"] (now)
…T12:00:00
?zone=0
[12, 0, "pm"]
…T12:00:00
Ionic Info
Not an Ionic CLI project (Nx monorepo). Relevant versions:
@ionic/core / @ionic/angular 9.0.7, also reproduced with 8.8.9 (@ionic/core/components)
zone.js 0.15.1
Angular 21 (provideZoneChangeDetection)
Chromium (headless shell 1243); first seen in a Capacitor app
Additional Information
What we traced. We wrapped the component lifecycle hooks; times are ms after the element is created:
+2componentWillLoad runs with value = null. processValue(null) sets the working parts to now.
+5..8NgModel writes value. valueChanged is not called.
+14componentDidLoad runs, and the wheels render now.
+17 the customElements.whenDefined('ion-datetime').then(...) callback runs.
The Stencil runtime in @ionic/core/components only calls @Watch handlers after the host's "watch ready" flag is set. That flag is set in customElements.whenDefined(tagName).then(() => …) inside initializeComponent. With zone.js loaded, that native-promise continuation runs after the zone-scheduled microtask that sets value. So a value assigned between componentWillLoad and that point is stored but never processed. ion-datetime reads value only in componentWillLoad and valueChanged, so it's lost.
Setting value after load works, because valueChanged fires. So does setting it synchronously, in the same task that attaches the element.
Why we only hit it now. With the lazy build (IonicModule in Ionic 8), componentWillLoad waits for the component module's dynamic import. By then NgModel has already written the value. Moving to standalone components or to Ionic 9 removes that delay.
Workaround we shipped: bind the value as an input instead of through ngModel, so it is set during the first change detection, before componentWillLoad:
Possibly related: the same timing affects any property Angular binds after the element has connected. For example, <ion-picker [mode]="'md'"> inside an ios modal resolves its mode stylesheet at connect from the modal's mode, before the [mode] binding applies, so it renders with the ios styles. Happy to file that separately if useful.
Suggestions: in ion-datetime, re-read value in componentDidLoad, or compare it with the value seen in componentWillLoad. Or, in Stencil, don't drop watcher calls between componentWillLoad and the watch-ready flag.
Activity