mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
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`
132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
import {
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
ElementRef,
|
|
EventEmitter,
|
|
HostListener,
|
|
Injector,
|
|
NgZone,
|
|
} from '@angular/core';
|
|
import type { OnInit } from '@angular/core';
|
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
import { ValueAccessor } from '@ionic/angular/common';
|
|
import type {
|
|
RangeChangeEventDetail,
|
|
RangeKnobMoveStartEventDetail,
|
|
RangeKnobMoveEndEventDetail,
|
|
Components,
|
|
} from '@ionic/core/components';
|
|
import { defineCustomElement } from '@ionic/core/components/ion-range.js';
|
|
|
|
/**
|
|
* Value accessor components should not use ProxyCmp
|
|
* and should call defineCustomElement and proxyInputs
|
|
* manually instead. Using both the @ProxyCmp and @Component
|
|
* decorators and useExisting (where useExisting refers to the
|
|
* class) causes ng-packagr to output multiple component variables
|
|
* which breaks treeshaking.
|
|
* For example, the following would be generated:
|
|
* let IonRange = IonRange_1 = class IonRange extends ValueAccessor {
|
|
* Instead, we want only want the class generated:
|
|
* class IonRange extends ValueAccessor {
|
|
*/
|
|
import { proxyInputs, proxyOutputs } from './angular-component-lib/utils';
|
|
|
|
const RANGE_INPUTS = [
|
|
'activeBarStart',
|
|
'color',
|
|
'debounce',
|
|
'disabled',
|
|
'dualKnobs',
|
|
'label',
|
|
'labelPlacement',
|
|
'legacy',
|
|
'max',
|
|
'min',
|
|
'mode',
|
|
'name',
|
|
'pin',
|
|
'pinFormatter',
|
|
'snaps',
|
|
'step',
|
|
'ticks',
|
|
'value',
|
|
];
|
|
|
|
@Component({
|
|
selector: 'ion-range',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: '<ng-content></ng-content>',
|
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
inputs: RANGE_INPUTS,
|
|
providers: [
|
|
{
|
|
provide: NG_VALUE_ACCESSOR,
|
|
useExisting: IonRange,
|
|
multi: true,
|
|
},
|
|
],
|
|
standalone: true,
|
|
})
|
|
export class IonRange extends ValueAccessor implements OnInit {
|
|
protected el: HTMLElement;
|
|
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
|
|
super(injector, r);
|
|
defineCustomElement();
|
|
c.detach();
|
|
this.el = r.nativeElement;
|
|
proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']);
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
/**
|
|
* Data-bound input properties are set
|
|
* by Angular after the constructor, so
|
|
* we need to run the proxy in ngOnInit.
|
|
*/
|
|
proxyInputs(IonRange, RANGE_INPUTS);
|
|
}
|
|
|
|
@HostListener('ionChange', ['$event.target'])
|
|
handleIonChange(el: HTMLIonRangeElement): void {
|
|
this.handleValueChange(el, el.value);
|
|
}
|
|
}
|
|
|
|
export declare interface IonRange extends Components.IonRange {
|
|
/**
|
|
* The `ionChange` event is fired for `<ion-range>` elements when the user
|
|
modifies the element's value:
|
|
- When the user releases the knob after dragging;
|
|
- When the user moves the knob with keyboard arrows
|
|
|
|
`ionChange` is not fired when the value is changed programmatically.
|
|
*/
|
|
ionChange: EventEmitter<CustomEvent<RangeChangeEventDetail>>;
|
|
/**
|
|
* The `ionInput` event is fired for `<ion-range>` elements when the value
|
|
is modified. Unlike `ionChange`, `ionInput` is fired continuously
|
|
while the user is dragging the knob.
|
|
*/
|
|
ionInput: EventEmitter<CustomEvent<RangeChangeEventDetail>>;
|
|
/**
|
|
* Emitted when the range has focus.
|
|
*/
|
|
ionFocus: EventEmitter<CustomEvent<void>>;
|
|
/**
|
|
* Emitted when the range loses focus.
|
|
*/
|
|
ionBlur: EventEmitter<CustomEvent<void>>;
|
|
/**
|
|
* Emitted when the user starts moving the range knob, whether through
|
|
mouse drag, touch gesture, or keyboard interaction.
|
|
*/
|
|
ionKnobMoveStart: EventEmitter<CustomEvent<RangeKnobMoveStartEventDetail>>;
|
|
/**
|
|
* Emitted when the user finishes moving the range knob, whether through
|
|
mouse drag, touch gesture, or keyboard interaction.
|
|
*/
|
|
ionKnobMoveEnd: EventEmitter<CustomEvent<RangeKnobMoveEndEventDetail>>;
|
|
}
|