feat(kit): KitLoadingController + inferred presentModal typing - #9
Conversation
Wraps Ionic's LoadingController so at most one loading indicator is on screen across concurrent async work: presentLoading increments a counter and presents on the 0 → 1 transition, dismissLoading decrements and dismisses on the N → 0 transition. All operations are serialized through an internal promise chain, so a dismiss that arrives mid-presentation runs after present() settles and tears the element down instead of orphaning it. A failed create/present rolls back its reference so the counter cannot stay elevated and wedge a later cycle into a stuck spinner.
presentModal now infers componentProps from the component's input() fields (the single source of truth, so props can never drift from a hand-written declaration): required inputs become required props and the compiler rejects a call that omits them; default-less input<T>() fields are optional. Because Angular's types cannot distinguish input.required<T>() from a defaulted input<T>(default), a defaulted input is treated as required. Components with no signal inputs fall back to loose, untyped props. The return type is inferred from an optional `declare static modalReturn` phantom type; a component without one resolves to `void`, so the modal is treated as returning no dismiss data and reading the result does not compile. BREAKING CHANGE: the single-type-argument form presentModal<ReturnType>(...) no longer sets the return type. Return data is inferred from a static modalReturn on the component; declare it on modals that resolve with data.
✅ Deploy Preview for rdlabo-ionic-angular-library ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Co-Authored-By: rdlabo <sakakibara@rdlabo.jp>
There was a problem hiding this comment.
🚩 クラスレベルJSDoc例が旧APIスタイルのまま残存
クラスレベルのJSDoc例(kit-overlay.controller.ts:170)は presentModal<EditResult>(EditPage, { id: 1 }) という旧ジェネリック構文を使用しています。新しいオーバーロードでは型パラメータ C はコンポーネント型として解釈されるため、この例は型エラーになります。メソッドレベルの例は更新済みですが、クラスレベルの例とREADME(README.md:130, README.md:152, README.md:187)は旧APIのままです。diff の変更範囲外のため bug として報告していませんが、ドキュメントの一貫性のため更新が推奨されます。
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 1c4d8ec — updated the class-level JSDoc example and all README examples (projects/kit/README.md lines 130, 152, 187) to use the new inferred-typing API.
| * ``` | ||
| */ | ||
| async presentModal<O = unknown>( | ||
| presentModal<C extends ModalOptions['component']>(component: C, ...args: ModalPresentArgs<C>): Promise<ModalReturnOf<C> | undefined>; |
There was a problem hiding this comment.
🚩 presentModalの破壊的API変更 — 既存の明示的ジェネリック呼び出しが非互換に
旧シグネチャ presentModal<O = unknown>(...) では呼び出し元が presentModal<ReturnType>(...) のように戻り値の型を明示指定できましたが、新オーバーロード presentModal<C extends ModalOptions['component']>(...) ではジェネリックパラメータがコンポーネント型になり、戻り値型の明示指定ができなくなります。既存の利用箇所は declare static modalReturn パターンへの移行が必要です。これは意図的な破壊的変更ですが、ライブラリの利用者に影響があるためマイグレーションガイドやCHANGELOGでの明記が望ましいです。
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
This is an intentional breaking change documented in the PR description. The migration path is clear:
- Remove explicit type arg
<ReturnType> - Add
declare static modalReturn: Tto modals that return data - For components without signal
input()fields, the API falls back to loose untyped props — so migration can be incremental.
The CHANGELOG/docs aspect has been addressed in 1c4d8ec (updated README and JSDoc examples to the new API).
Co-Authored-By: rdlabo <sakakibara@rdlabo.jp>
Summary
Two additions to the kit overlay module, both fully covered by tests.
1.
KitLoadingController(new)Reference-counted wrapper around Ionic's
LoadingControllerthat keeps at most one loading indicator on screen across concurrent async work.presentLoadingpresents on the0 → 1transition;dismissLoadingdismisses on theN → 0transition.present()settles and tears the element down instead of orphaning it.create/presentrolls back its reference, so the counter can't stay elevated and wedge a later cycle into a stuck spinner.Pair every
presentLoading()with exactly onedismissLoading()(atry/finallyis the safest shape).2.
presentModal— props and return type inferred from the componentProps are inferred from the component's
input()fields, makinginput()the single source of truth (no hand-written declaration to drift out of sync):input.required<T>()→ required prop; the compiler rejects a call that omits it.input<T>()→ optional prop.input.required<T>()from a defaultedinput<T>(default), so a defaulted input is (safely) treated as required. Declare inputs you want to omit at the call site as default-lessinput<T>().@Input()-decorator components).The return type is inferred from an optional
declare static modalReturnphantom type (zero runtime cost). A component withoutmodalReturnresolves tovoid— reading the result does not compile — so modals that resolve with data are required to declare their shape.Breaking change
The single-type-argument form
presentModal<ReturnType>(...)no longer sets the return type. Return data is now inferred from a staticmodalReturn; declare it on modals that resolve with data.Test plan
ng test kit --watch=false→ 139 passed (includes@ts-expect-errortype-level assertions for required-prop enforcement and thevoidreturn contract).ng build kit→ succeeds.