1

I have a project that is using Angular 17, application builder, standalone components, with NgModule imports. In Development, it builds and runs just fine, but when building in Production, the application crashes with the following error:

core.mjs:6373 ERROR TypeError: st is not a function
    at HTMLElement.addEventListener (zone.js:1907:21)
    at n.addEventListener (platform-browser.mjs:785:15)
    at n.addEventListener (platform-browser.mjs:212:21)
    at Dd.listen (platform-browser.mjs:665:30)
    at sf.listen (browser.mjs:4309:26)
    at G0 (core.mjs:24973:34)
    at _t (core.mjs:26820:3)
    at Kle (createAccount.component.html:31:53)
    at o0 (core.mjs:10988:5)
    at LA (core.mjs:12150:7)

createAccount.component.html:31:53 looks like this, with line 31 being [(value)]="model.Username"

<my-textbox [label]="ResourceKey.Portals_EditProfile_Username"
    [(value)]="model.Username"
    [readonly]="model.IsSSO" 
    [required]="true">
</my-textbox>

If I comment this textbox out, then the same error on the next textbox's [(value)] line.

From what I can tell poking around in the stack, st seems to be mapped to prepareEventNames in zone.js:

if (!symbolEventNames) {
    prepareEventNames(eventName, eventNameToString);
    symbolEventNames = zoneSymbolEventNames[eventName];
}

attempting to step into prepareEventNames() results in the error.

So what does this mean? Did the optimized build shake one too many things out? How do I fix/find the issue?

Environment:

Angular CLI: 17.3.8
Node: 20.16.0
Package Manager: npm 10.8.1
OS: win32 x64

Angular: 17.3.12
... animations, common, compiler, compiler-cli, core, forms
... localize, platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1703.8
@angular-devkit/build-angular   17.3.8
@angular-devkit/core            17.3.8
@angular-devkit/schematics      17.3.8
@angular/cdk                    17.3.10
@angular/cli                    17.3.8
@angular/flex-layout            15.0.0-beta.42
@angular/material               17.3.10
@schematics/angular             17.3.8
ng-packagr                      17.3.0
rxjs                            7.8.1
typescript                      5.3.3
zone.js                         0.14.4

Application configuration:

export const appConfig: ApplicationConfig = {
    providers: [
        { provide: LocalizationSource, useClass: MinIDLocalizationSource },
        provideRouter(routes),
        importProvidersFrom(BrowserAnimationsModule),
        provideHttpClient(
            withInterceptors([
                DateInterceptorFn
            ])
        )
    ]
};

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

Update: If I build with optimization turned off, the application runs fine. ng build {project} --configuration=production --optimization=false

3
  • And what happens in prepareEventNames()? Can you share the code from this file? Commented Aug 29 at 5:16
  • @SwissCodeMen prepareEventNames is in zone.js. Github link github.com/angular/angular/blob/…
    – ZrSiO4
    Commented Aug 29 at 17:03
  • If I build with optimization turned off, the application runs fine ng build {project} --configuration=production --optimization=false
    – ZrSiO4
    Commented Aug 30 at 23:50

2 Answers 2

0

Based on your code, as I understand it, you have a custom component called my-textbox and this one has (for the looks of it) an input called value, independently if you are using input as signals or @Input(), you can't expect to have double way binding for value by just using [()]. You should be getting an error, you wouldn't be able to compile it.

Since you didn't post the relevant code for my-textbox, if you want to send and modify model.Username, try adding an output for listening to your custom event

// using input/output annotations
@Componet({...})
export class MyTextBox {
  @Input() value!: string;
  @Output() valueChanged = new EventEmitter<string>();

  onChange(val: string): void {
    this.valueChanged.emit(val);
  }
}

// using input/output signals
@Componet({...})
export class MyTextBox {
  value = input<string>();
  valueChanged = output<string>();

  onChange(val: string): void {
    this.valueChanged.emit(val);
  }
}

// a parent component using my-texbox
@Component({
  template: `<my-textbox 
               [value]="model.Username" 
               (valueChanged)="onUsername($event)">
             </my-textbox>`,
  ...
})
export class ParentComponent {
   model!: any; // I don't know the type, that's why I use any

   onUsername(name: string): void {
     this.model.Username = name;
   }
}

This way, my-texbox will react to the changes made by the custom event valueChanged.

Another way for handling input/output from the level component is by using the model signal which replaces @Input & @Output but it doesn't behave exactly as ngModel:

@Componet({...})
export class MyTextBox {
  value = model<string>();

  onChange(val: string): void {
    this.value.set(val);
  }
}

Then, in your parent component, you will need to attach a signal to your [(value)] as the following:

@Component({
  template: `<my-textbox [(value)]="username"> </my-textbox>
             <!-- without parenthesis -->
  `,             
  ...
})
export class ParentComponent {
  person = { username: 'Bob' }
  username = signal(this.person.username);
}

UPDATED DEMO

5
  • The textbox component does have a "value" Input and a "valueChange" Output, so the two way binding should work. And in fact, it does, in development build. It's just the production build that is broken.
    – ZrSiO4
    Commented Aug 29 at 16:50
  • I have updated my answer, are you using the model signal operator? If so, are you attaching a signal to your [(value)]?. Let me know if this help with your issue.
    – Andres2142
    Commented Aug 29 at 21:52
  • I am using @ Input("value") and @ Output("valueChange") in the textbox component. The model is a simple json object { Username: string }. Again, this isn't an issue with syntax or a question about how to do two way binding. There's an issue with production build maybe tree shaking out core Angular/zone code.
    – ZrSiO4
    Commented Aug 30 at 0:24
  • Well, I don't know exactly what the issue is from the terminal output, but at least you can try refactor your component to use the model signal rather than @input @output since you are using Angular v17. I am not sure how is able to work on dev but not in prod
    – Andres2142
    Commented Aug 30 at 2:11
  • Additionally, you could also try the old fashion way, handling separately the (valueChange) and [value] while still using @Input & @Output and see if in prod works. I have to say, the way you are handling your own double way binding, I personally haven't try it myself, I actually wasn't even aware of it. I learned something today, thanks for that
    – Andres2142
    Commented Aug 30 at 2:37
0

Searching for any relevant info regarding this issue, I ran into this one: Enabling optimization breaks Angular site for no apparent reason

Lo and behold, that was it. Now my production build works fine.

To recap the issue and the fix:

  • It seemed production builds were broken. Functions within zone.js being undefined or set to scalar values, resulting in TypeErrors and a broken application
  • The SO post referenced above talks about different javascript files clobbering eachother's functions and variables
  • The fix was to add type="module" to the script references. <script src="main.js"></script> to <script src="main.js" type="module"></script>

Definitely one to remember for the future

Not the answer you're looking for? Browse other questions tagged or ask your own question.