Repository navigation
Incorrect emit for parameter properties when using standard class fields #55132
Description
Activity
That's probably OK since parameter properties aren't standard.
I’d argue that it’s not OK because you’re potentially changing the initialization behavior of a bunch of unrelated properties just because the class constructor has some parameter props, which feels icky.
If I enable
useDefineForClassFieldsthen generally the reason I’ve done so is because I want fields that behave according to the standard. If that behavior doesn’t compose with some older TS feature, I’d rather know about it rather than have the compiler try to be clever and make it work by fudging things.We've done a similar transformation in the past, such as we do for
accessorfields. I don't think this transform would have any major issues, as the only side effects you might observe from this emit would be bad practice to depend on to begin with.Reacted by Nathan Shively-SandersI appreciate this fix will align the newer mode with the original intended behaviour.
What are the chances this will be a breaking change for users of
"useDefineForClassFields"?sandersn commented
on Jul 28, 2023 MemberAuthorMore actionsRob Palmer (@robpalme) As far as I can see, the pre-5.2 state is: Typescript doesn't error, but you may crash at runtime, depending on order of class fields vs parameter properties. Post-5.2: Typescript errors. So fixing this bug would avoid crashes or compile errors; that doesn't sound like a breaking change except in the very strictest of meanings where "I expected a crash but the program terminated successfully".
Reacted by Rob Palmer and Nicolas BreitwieserNathan Shively-Sanders (@sandersn) wrote:
Currently the compiler issues an error on bug = this.facade.create() to avoid the bad emit, but it might be better to change the emit when parameter properties are used (...)
Totally Agree! This bug (until fixed) forces us to stay with
target: "es2021".Bruce Pascoe (@fatcerberus) wrote:
If I enable useDefineForClassFields then generally the reason I’ve done so is because I want fields that behave according to the standard.
Exactly. As I understand it, the
useDefineForClassFieldsoption was introduced to bring us [[Define]] semantics over [[Set]] semantics. The proposed solution doesn't change this.In general: regarding whether this bugfix maybe could be a breaking change or not, this is how I see it:
- either people had used target < es2022 until now, then they never have had proper ES class fields so far. They will only be starting using them when switching to es2022 output. So this cannot be a breaking change for them (in contrast: the bugfix prevents them from suffering a severe breaking change when using parameter properties to initialize class fields)
- or people are using plain vanilla JS, maybe relying heavily on latest ES standards (i.e. class fields as in the spec) and didn't use TypeScript so far. When switching to TypeScript it will also be not a breaking change for them, because they could not have been using parameter properties before (as it is a TypeScript feature).
- or people had used TypeScript all the time and switched to
target: es2022"as soon as it was out" (i.e. TypeScript 4.6, released on February 28, 2022). If they sticked with this target, they likely didn't use parameter properties, or at least not used the parameter properties to initialize class fields. Otherwise they would have stumbled into this bug as well. So maybe the proposed solution can be extended by "not pulling things up into constructor", when the available parameter properties aren't used to initialize class fields.
Final Note:
The current behavior of "issuing an error" not always works, e.g. think of this NgRx effect, where the "callback" given to thecreateEffect()function is called immediately, resulting incannot read property 'pipe' of 'undefined'(becausethis.actions$isundefinedat this moment as we're still in the middle of instantiation)(Example taken from https://ngrx.io/guide/effects)
export class MoviesEffects { loadMovies$ = createEffect(() => this.actions$.pipe( ofType('[Movies Page] Load Movies'), exhaustMap(() => this.moviesService.getAll() .pipe( map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })), catchError(() => EMPTY) )) ) ); constructor( private actions$: Actions, private moviesService: MoviesService ) {} }
Any news on this? It's 2024 now but because of this issue we're still forced to stick with
target: "es2021".Side note: this issue duplicates #45995
CC: Nathan Shively-Sanders (@sandersn) Ron Buckton (@rbuckton) Alan Agius (@alan-agius4)
I think this issue really should be resolved rather sooner than later!
I just found out that Angular has had to force-patch their users' tsconfig.json inside
@angular-devkit/build-angular(see esbuild tooling + webpack-tooling) because of this bug! Effectively this is leaving us withtarget: es2022anduseDefineForClassFields=falsewhich is the opposite of what I intended to have (my tsconfig.json statestarget: es2021anduseDefineForClassFields=true). Alan Agius (@alan-agius4) are you aware that you're forcing all Angular users to the old semantics of class fields unless they explicitly setuseDefineForClassFields=true(this was your intention by looking at the source code (links see above), but you've implemented it incorrectly by checking it only against the locally createdcompilerOptions, which never has auseDefineForClassFieldsset, thus it's always forced to befalse(see webpack-tooling). Because of this incorrect implementation, all users using webpack-tooling right now don't even have a choice here.I can easily imagine this not ending well ... as soon as TypeScript will drop support for
target: es2022(or newer) +useDefineForClassFields=falsea lot of projects will be broken for sure.Reacted by Ryan Cavanaugh, Robin, Leonardo de Albuquerque Gouveia and XhstormR- added a commit that references this issue
on Jul 24, 2024 - addedRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Jul 26, 2024 ping
- addedDomain: JS EmitThe issue relates to the emission of JavaScriptThe issue relates to the emission of JavaScript
on Oct 16, 2025 - added a commit that references this issue
on Jun 17, 2026
Pointed out by Ron Buckton (@rbuckton) on #50971, based on the repro from that bug. When targetting ES2022 or higher, and leaving useDefineWithClassFields: true (the default) for that target:
Produces
Currently the compiler issues an error on
bug = this.facade.create()to avoid the bad emit, but it might be better to change the emit when parameter properties are used:Unfortunately, this isn't standard initialisation order for class fields...so it's correct, but not standard. That's probably OK since parameter properties aren't standard.