Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
OS-jacobbell
left a comment
There was a problem hiding this comment.
Everything looks good for the auto-generated component wrappers!
Some components are excluded from auto generation, e.g. standalone ion-checkbox. See core/stencil.config.ts for the full list of excluded components wrappers, and packages/angular/src for their manual implementations. The nullableBooleanAttribute will need to be applied to their inputs directly.
ShaneK
left a comment
There was a problem hiding this comment.
Looks really solid overall, and the sweep across both the generated and hand-written wrappers is thorough.
The main thing is that I think this is a breaking change. These inputs had no type at all before, so narrowing them means bindings like [disabled]="items.length" stop compiling under strictTemplates, and it's the same on Angular 18 through 22. The test cases expect ngAcceptInputType to come out unknown, which would have been harmless, but what actually ships is boolean | string | null | undefined.
The rest are smaller. A couple of boolean props are missing from the hand-written arrays so they only work in the lazy build, and I've left a question on whether the editor warning from #30822 is actually silenced, since your ticket note had that as an open item.
| directivesArrayFile: '../packages/angular/src/lazy/directives/proxies-list.ts', | ||
| excludeComponents, | ||
| outputType: 'component', | ||
| booleanAttributes: true, |
There was a problem hiding this comment.
The <ion-modal [handle]="42"> failure in the description has a flip side I don't think we've accounted for. These inputs had no type at all before, so anything that compiled and isn't boolean, any or nullable now fails too. Something like [disabled]="items.length" is the common one, and it's the same on Angular 18 through 22.
What gets emitted is ngAcceptInputType_<prop>: boolean | string | null | undefined. Upstream chose the narrow type on purpose, the helper's own comment says widening it to unknown would let any expression through, so this is intended rather than a slip. It does mean every boolean input in the library gets stricter in a minor though.
Could we check the breaking change box, or scope the claim in the description? It probably wants a changelog note either way.
There was a problem hiding this comment.
Updated the PR description and BREAKING.md: b8c795c
| outputType: 'standalone', | ||
| // Emit each component in a separate file rather than putting them all in one large file. | ||
| esModules: true, | ||
| booleanAttributes: true, |
There was a problem hiding this comment.
Is there an ionic-docs PR for this one? I couldn't find one linked. The part consumers will get wrong is that null and undefined pass through instead of coercing to false, since that's the opposite of what Angular's own booleanAttribute does.
The "Standalone Directive" section of docs/component-guide.md could use a line too. It's where someone goes to add a hand-written wrapper and it still points at ion-checkbox and ion-toggle for boolean inputs without mentioning the transform, so the next one added won't match its neighbors.
There was a problem hiding this comment.
| */ | ||
|
|
||
| /** | ||
| * Transforms a value to a boolean so that boolean properties can be set by attribute presence, |
There was a problem hiding this comment.
Did this end up confirmed? The original report was an editor warning rather than a build failure, and I'm not sure the new option silences it on its own.
From the compiler side it doesn't. Writing <ion-item button detail> compiles clean with strictTemplates on against both this branch and the base, in both entry points, on Angular 18 through 22. So whether #30822 is actually fixed comes down entirely to the language service. If it isn't silenced there, that's the second Stencil option and probably its own call.
There was a problem hiding this comment.
Confirmed, you were right. The language service doesn't flag it either, on 9.0.4 or on this branch. I thought I had watched the squiggles go away while testing, but that was a stale editor server.
Dropped the resolves, reframed the description around the type checking, and corrected the BREAKING.md line that described a compile error that never existed.
| * reach inputs routinely from the `async` pipe before its first emission and from form control | ||
| * values: | ||
| * | ||
| * ```tsx |
There was a problem hiding this comment.
This is the only one of the three copies anyone will actually edit, and it's the shortest. It drops the notes the generated copy carries about why Angular's booleanAttribute isn't imported, and about the parameter type being what ngAcceptInputType derives from, so widening it to unknown would quietly loosen template checking everywhere. That second one especially is the kind of thing someone would undo without realizing.
Meanwhile the tsx example and the async pipe paragraph read more like PR description material. I think trading one for the other would leave this better off, but up to you!
| if (value === null || value === undefined) { | ||
| return value; | ||
| } | ||
| return typeof value === 'boolean' ? value : value !== 'false'; |
There was a problem hiding this comment.
Anything that isn't a boolean, null or undefined falls into the string branch here, so 0 and NaN come back true. Before this, Angular wrote the raw value straight through and Stencil coerced 0 to false, so [disabled]="items.length" on an empty list flips from enabled to disabled. With strictTemplates on you'd get a compile error instead, without it it's silent. Empty string, null, undefined and objects don't change, so the focusTrap === false cases in the ticket are all fine.
Coercing only strings would keep the feature and the old behavior. The catch is that the two generated copies of this helper are byte-identical to what @stencil/angular-output-target 1.5.0 ships, and the whole angular-component-lib directory gets recopied on every core build, so changing it here alone would leave the hand-written wrappers behaving differently from the generated ones. It'd have to go upstream again.
Mostly flagging it so it's a deliberate call rather than something we notice later, since changing it afterwards is its own behavior change.
There was a problem hiding this comment.
| 'labelPlacement', | ||
| 'mode', | ||
| 'name', | ||
| 'value', |
There was a problem hiding this comment.
| 'value', | |
| { name: 'required', transform: nullableBooleanAttribute }, | |
| 'value', |
The required prop is a public boolean on ion-checkbox and the regenerated lazy proxies picked it up, but this array doesn't list it at all, so it binds in the lazy build and does nothing in standalone. Both ion-toggle and ion-select have the same gap. It predates this PR, but since this one is a sweep over exactly these arrays it seems worth catching here. I added all three and they compile fine with the inputs typed correctly.
The hand-written wrappers are also the part the upstream tests can't reach, so this is where drift shows up. I threw together a script modelled on verify-change-detection.js that checks each one's boolean props against core's types, and it finds exactly these three plus one on popover and nothing else. Happy to share it.
| 'value', | ||
| ]; | ||
|
|
||
| /* ProxyCmp only needs the names, and runs at runtime rather than through the Angular compiler. */ |
There was a problem hiding this comment.
This line and the comment above it are byte-identical in 16 files. The NG1010 thing in the PR body is real but it only applies to the @Component/@Directive inputs array, and ProxyCmp is a plain runtime decorator whose inputs never reaches ngtsc, so this could be a shared helper instead. Most of the wrappers already import from @ionic/angular/common or utils/proxy, so 15 of the 16 sites are a one-token change to an existing import. Only the icon wrapper would pick up a new dependency and it can keep its local .map.
One thing to watch if you do take it though. It compiles fine, but changing the line count near these classes makes TypeScript splice unrelated comments into the synthesized ngAcceptInputType members in the emitted .d.ts. Still valid TypeScript and purely cosmetic, and there's none of it on this branch today, but worth a glance at dist afterwards.
| 'reference', | ||
| 'size', | ||
| 'side', | ||
| ]; |
There was a problem hiding this comment.
| ]; | |
| { name: 'keyboardEvents', transform: nullableBooleanAttribute }, | |
| ]; |
The keyboardEvents prop is a public documented boolean on ion-popover and it's missing from this array entirely, so it's the one popover boolean this PR doesn't reach. Checked that adding it compiles and wires up properly. Pre-existing, same class of thing as the required gap over on checkbox.
While you're in here, the modal array has the mirror problem, it lists translucent and event and neither of those exists on ion-modal. I think the standalone input wrapper has a couple in the same state too.
There was a problem hiding this comment.
keyboardEvents is in, along with the same gap on checkbox, toggle and select: 70c7557.
Confirmed the mirror problem too. event and translucent aren't props on ion-modal, and ion-input has one, accept. I'd rather take those in a follow-up (FW-7774), since removing an input breaks anyone binding it and wants its own breaking change note.
Co-authored-by: Shane <ShaneK@users.noreply.github.com>
ShaneK
left a comment
There was a problem hiding this comment.
Looks good to me! Everything from the last round is addressed, and I checked that the generated proxies reproduce byte-exact from a clean build. A few suggestions inline, all minor apart from the BREAKING.md wording, which describes the wrong outcome.
| @@ -0,0 +1,43 @@ | |||
| /* eslint-disable */ | |||
There was a problem hiding this comment.
The angular package's .prettierignore lists **/*/angular-component-lib/utils.ts because the output target emits it, but not this file, so npm run lint owns its formatting. It's a no-op today, though lint and regeneration will fight each other the moment the generator's output drifts. Adding **/*/angular-component-lib/boolean-attribute.ts next to the existing entry covers both the lazy and standalone copies. Just a nit, up to you!
| 'labelPlacement', | ||
| 'mode', | ||
| 'name', | ||
| { name: 'required', transform: nullableBooleanAttribute }, |
There was a problem hiding this comment.
Following on from the required thread, nothing stops this drifting again. It's still live for the non-boolean props, theme is in all 164 generated proxies and in none of the hand-written wrappers, and there are a few others like alignment here and on checkbox. I did end up writing that verify-wrapper-inputs.js, on the same model as validate.change-detection, and it catches the required case when I revert it. Say the word and I'll open a PR. The non-boolean gaps could go along with FW-7774.
There was a problem hiding this comment.
That would be great! Thank you! Please add me as a reviewer.
| * That keeps it independently type-checkable without pulling `rxjs` in for `proxyOutputs`. | ||
| */ | ||
| export function nullableBooleanAttribute(value: boolean | string | null | undefined): boolean | null | undefined { | ||
| if (value === null || value === undefined) { |
There was a problem hiding this comment.
The undefined passthrough is the reason this exists instead of Angular's booleanAttribute, and nothing pins it right now. One caveat if you do add a test: assertions for attribute presence, 'false' and an absent attribute all still pass with this stubbed out to a no-op, because Stencil's own coercion already covers those, so they'd pass on next too. The undefined case is the one that discriminates, swapping in booleanAttribute semantics breaks that and nothing else. So one test rather than a suite. The number coercion can't be covered this way at all, since the test app has strictTemplates on and [button]="someNumber" doesn't compile.
Co-authored-by: Shane <shane.king@outsystems.com>
Co-authored-by: Shane <shane.king@outsystems.com>
Issue number: internal
What is the current behavior?
Angular does not type-check boolean inputs on the Ionic wrappers. The generated wrappers declare no typed members for their inputs, so the compiler has nothing to check a binding against and accepts any expression:
Attribute presence already works at runtime, because Stencil coerces boolean attributes on the element, but it is incidental rather than declared.
What is the new behavior?
Boolean props declare an Angular input transform, which makes attribute presence explicit and turns on type checking for those inputs.
@stencil/angular-output-targetto 1.5.0 and enables its newbooleanAttributesoption on both Angular output targets, which covers every boolean prop in the generated wrappers.<ion-checkbox checked>and<ion-modal handle>.nullandundefinedare passed through rather than coerced tofalse, unlike Angular's ownbooleanAttribute. Props that treat them as a third state keep working:ion-item detail,ion-modal handleandion-content forceOverscrollall resolveundefinedto a computed default.0andNaNwerefalseand are nowtrue, matching Angular's ownbooleanAttribute. Everything else coerces identically.core's build rather than hand-edited.Does this introduce a breaking change?
These inputs previously had no declared type, so Angular did not check bindings to them and any expression compiled. Declaring a transform emits
ngAcceptInputType_<prop>: boolean | string | null | undefined, which turns checking on for every boolean input in the library. Bindings that pass anything outside that union now fail to compile.The common pattern this catches is a truthiness binding:
This affects Angular 18 through 22 equally and is compile time only. A binding that already passed a boolean, a string,
nullorundefinedis unaffected.The narrow accepted type is deliberate upstream. Widening it to
unknownwould accept any expression again and give up the checking this adds.Only boolean inputs end up checked. Every other input stays unchecked, for the same reason they all were before, and closing that gap needs the output target's
inlinePropertiesoption, which is a separate decision.Other information
Associated docs: ionic-team/ionic-docs#4744
The version bump is the fix. Generated proxies are overwritten on every
corebuild, so this could not be solved here. The change was made upstream in stenciljs/output-targets#842 and released in 1.5.0.This started as a fix for #30822, but that report is not reproducible. Neither the compiler nor the Angular language service flags
<ion-item button detail>on 9.0.4 or on this branch, and the wrappers have had the same untyped shape since v6.