fix(angular): remove form control side effects (#28359)

Issue number: resolves #28358

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->


28f2ec9c62
exposed a (possible) `ng-packagr` bug where the form control components
were being re-assigned, which breaks treeshaking. These components were
considered side effects and were always being pulled into the bundle.

This resulted in a higher than expected bundle size. This issue appears
to be caused by using 2 decorators **and** referring to the class in
`useExisting` (for providers). Doing just one of these does not
reproduce the issue.

The compiled output looks something like this:

```typescript
let IonToggle = IonToggle_1 = /*@__PURE__*/ class IonToggle extends ValueAccessor {
    constructor(c, r, z, injector) {
        super(injector, r);
        this.z = z;
        c.detach();
        this.el = r.nativeElement;
        proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
    }
    writeValue(value) {
        this.elementRef.nativeElement.checked = this.lastValue = value;
        setIonicClasses(this.elementRef);
    }
    handleIonChange(el) {
        this.handleValueChange(el, el.checked);
    }
};
/** @nocollapse */ IonToggle.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonToggle, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ IonToggle.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: IonToggle, isStandalone: true, selector: "ion-toggle", inputs: { checked: "checked", color: "color", disabled: "disabled", enableOnOffLabels: "enableOnOffLabels", justify: "justify", labelPlacement: "labelPlacement", legacy: "legacy", mode: "mode", name: "name", value: "value" }, host: { listeners: { "ionChange": "handleIonChange($event.target)" } }, providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: IonToggle_1,
            multi: true,
        },
    ], usesInheritance: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
IonToggle = IonToggle_1 = __decorate([
    ProxyCmp({
        defineCustomElementFn: defineCustomElement$1i,
        inputs: TOGGLE_INPUTS,
    })
], IonToggle);
```

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Removed the `ProxyCmp` usage in favor of manually calling proxyInputs
and proxyMethods.
-  Also saw that select was missing a form control test, so I added one

The compiled code now looks something like this:

```typescript
class IonToggle extends ValueAccessor {
    constructor(c, r, z, injector) {
        super(injector, r);
        this.z = z;
        defineCustomElement$1i();
        proxyInputs(IonToggle, TOGGLE_INPUTS);
        c.detach();
        this.el = r.nativeElement;
        proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur']);
    }
    writeValue(value) {
        this.elementRef.nativeElement.checked = this.lastValue = value;
        setIonicClasses(this.elementRef);
    }
    handleIonChange(el) {
        this.handleValueChange(el, el.checked);
    }
}
/** @nocollapse */ IonToggle.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: IonToggle, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ IonToggle.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: IonToggle, isStandalone: true, selector: "ion-toggle", inputs: { checked: "checked", color: "color", disabled: "disabled", enableOnOffLabels: "enableOnOffLabels", justify: "justify", labelPlacement: "labelPlacement", legacy: "legacy", mode: "mode", name: "name", value: "value" }, host: { listeners: { "ionChange": "handleIonChange($event.target)" } }, providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: IonToggle,
            multi: true,
        },
    ], usesInheritance: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
```

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Ryan provided some context on a related Stencil bug where doing
reassignments broke treeshaking in Webpack. While the source of this bug
is not Stencil, understanding the Stencil bug helped me better
understand this issue:

https://github.com/ionic-team/stencil/issues/3191
https://github.com/ionic-team/stencil/pull/3248
https://github.com/ionic-team/stencil/pull/4188 (fixes an issue
introduced in the above stencil PR)

Dev build: `7.5.1-dev.11697480817.10fa2601`
This commit is contained in:
Liam DeBeasi
2023-10-18 15:03:20 -04:00
committed by GitHub
parent 416bb736a1
commit 82d6309ef1
24 changed files with 407 additions and 80 deletions

View File

@ -37,6 +37,7 @@ export const routes: Routes = [
{ path: 'range', loadComponent: () => import('../value-accessors/range/range.component').then(c => c.RangeComponent) },
{ path: 'searchbar', loadComponent: () => import('../value-accessors/searchbar/searchbar.component').then(c => c.SearchbarComponent) },
{ path: 'segment', loadComponent: () => import('../value-accessors/segment/segment.component').then(c => c.SegmentComponent) },
{ path: 'select', loadComponent: () => import('../value-accessors/select/select.component').then(c => c.SelectComponent) },
{ path: 'textarea', loadComponent: () => import('../value-accessors/textarea/textarea.component').then(c => c.TextareaComponent) },
{ path: 'toggle', loadComponent: () => import('../value-accessors/toggle/toggle.component').then(c => c.ToggleComponent) },
{ path: '**', redirectTo: 'checkbox' }

View File

@ -6,6 +6,6 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-checkbox formControlName="checkbox"></ion-checkbox>
<ion-checkbox color="danger" formControlName="checkbox"></ion-checkbox>
</app-value-accessor-test>
</div>

View File

@ -6,6 +6,6 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-datetime formControlName="datetime"></ion-datetime>
<ion-datetime color="danger" formControlName="datetime"></ion-datetime>
</app-value-accessor-test>
</div>

View File

@ -5,7 +5,7 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-input label="String" formControlName="inputString"></ion-input>
<ion-input color="danger" label="String" formControlName="inputString"></ion-input>
<ion-input label="Number" type="number" formControlName="inputNumber"></ion-input>
</app-value-accessor-test>
</div>

View File

@ -7,7 +7,7 @@
<app-value-accessor-test [formGroup]="form">
<ion-radio-group formControlName="radioGroup">
<ion-radio value="1">One</ion-radio>
<ion-radio color="danger" value="1">One</ion-radio>
<ion-radio value="2">Two</ion-radio>
</ion-radio-group>
</app-value-accessor-test>

View File

@ -4,6 +4,6 @@
This test checks the form integrations with ion-range to make sure values are correctly assigned to the form group.
</p>
<app-value-accessor-test [formGroup]="form">
<ion-range formControlName="range"></ion-range>
<ion-range color="danger" formControlName="range"></ion-range>
</app-value-accessor-test>
</div>

View File

@ -5,6 +5,6 @@
group.
</p>
<app-value-accessor-test [formGroup]="form">
<ion-searchbar label="String" formControlName="searchbar"></ion-searchbar>
<ion-searchbar color="danger" label="String" formControlName="searchbar"></ion-searchbar>
</app-value-accessor-test>
</div>

View File

@ -6,7 +6,7 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-segment formControlName="segment">
<ion-segment color="danger" formControlName="segment">
<ion-segment-button value="Paid">
<ion-label>Paid</ion-label>
</ion-segment-button>

View File

@ -0,0 +1,14 @@
<div>
<h1>IonSelect Value Accessors</h1>
<p>
This test checks the form integrations with ion-select to make sure values are correctly assigned to the form
group.
</p>
<app-value-accessor-test [formGroup]="form">
<ion-select color="danger" label="Favorite Fruit" formControlName="select" interface="popover">
<ion-select-option value="apples">Apples</ion-select-option>
<ion-select-option value="bananas">Bananas</ion-select-option>
</ion-select>
</app-value-accessor-test>
</div>

View File

@ -0,0 +1,26 @@
import { Component } from "@angular/core";
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
import { IonSelect, IonSelectOption } from "@ionic/angular/standalone";
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
@Component({
selector: 'app-select',
templateUrl: 'select.component.html',
standalone: true,
imports: [
IonSelect,
IonSelectOption,
ReactiveFormsModule,
FormsModule,
ValueAccessorTestComponent
]
})
export class SelectComponent {
form = this.fb.group({
select: ['bananas', Validators.required],
});
constructor(private fb: FormBuilder) { }
}

View File

@ -6,6 +6,6 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-textarea label="String" formControlName="textarea"></ion-textarea>
<ion-textarea color="danger" label="String" formControlName="textarea"></ion-textarea>
</app-value-accessor-test>
</div>

View File

@ -5,6 +5,6 @@
</p>
<app-value-accessor-test [formGroup]="form">
<ion-toggle formControlName="toggle"></ion-toggle>
<ion-toggle color="danger" formControlName="toggle"></ion-toggle>
</app-value-accessor-test>
</div>