Skip to content

formField directive broken when using oxc-angular plugin #229

@nickbelling

Description

@nickbelling

When using @oxc-angular/vite to compile an Angular 21 application that uses the Signal Forms API (@angular/forms/signals), the [formField] directive binding appears to be not wired up correctly. The FormField directive is instantiated (its selector [formField] is matched), but its field input never receives the bound value, resulting in the form input silently failing to bind.

No error output is generated to the JS console (that I can see), but the control is simply not bound to the [formField] directive - it wasn't until I looked at the Angular DevTools that I noticed the FormFieldDirective's field signal was in an errored state:

Image

Minimal reproduction

// app.component.ts
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { form, FormField, required } from '@angular/forms/signals';

@Component({
  selector: 'app-root',
  imports: [FormField],
  template: `
    <input type="text" [formField]="myForm.firstName" />
    <pre>{{ myFormModel().firstName }}</pre>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
  protected readonly myFormModel = signal({
    firstName: 'Foo',
    email: 'foo@bar.com',
  });

  protected readonly myForm = form(this.myFormModel, path => {
    required(path.firstName);
  });
}

Expected behavior

The <input> displays "Foo", and typing updates myFormModel().firstName via two-way signal binding.

Actual behavior

The input is empty. Typing does not update the model. Angular DevTools shows the FormField directive's field input in an error state (NG0950). The FieldState signals (name, value, etc.) are never populated.

If I swap the @oxc-angular/vite plugin with the @analogjs/vite-plugin-angular plugin, everything works as expected.

I did that like this:

// vite.config.ts
import analogAngular from '@analogjs/vite-plugin-angular';
import { angular as oxcAngular } from '@oxc-angular/vite';
import { defineConfig } from 'vite-plus';

const USE_OXC_ANGULAR = true;

export default defineConfig({
  plugins: USE_OXC_ANGULAR ? [oxcAngular()] : [analogAngular()],
  server: {
    host: '0.0.0.0',
    port: 4200,
  },
});

Bundle size differences

Additionally, when building this small reproduction, the build output when using the OXC Angular plugin vs the AnalogJS Angular plugin is twice the size.

When using @analogjs/vite-plugin-angular (USE_OXC_ANGULAR = false):

$ vp build
VITE+ - The Unified Toolchain for the Web

vite v8.0.5 building client environment for production...
✓ 259 modules transformed.
computing gzip size...
dist/index.html                  0.37 kB │ gzip:  0.26 kB
dist/assets/index-D5gCGqZR.js  165.45 kB │ gzip: 51.07 kB

✓ built in 2.03s

When using @oxc-angular/vite (USE_OXC_ANGULAR = true):

$ vp build
VITE+ - The Unified Toolchain for the Web

vite v8.0.5 building client environment for production...
✓ 259 modules transformed.
computing gzip size...
dist/index.html                  0.37 kB │ gzip:  0.25 kB
dist/assets/index-DBjttgyu.js  309.79 kB │ gzip: 91.37 kB

✓ built in 195ms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions