Files
Liam DeBeasi 3b6e6318bf fix(angular): inputs on standalone form controls are reactive (#28434)
Issue number: resolves #28431

---------

<!-- 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. -->

My previous attempt at fixing
https://github.com/ionic-team/ionic-framework/issues/28358 caused inputs
to no longer be correctly proxied to the underlying components. This was
an attempt to work around an underlying ng-packagr bug (see linked
thread for more info).

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

I decided it would be best to continue using `ProxyCmp` (since we know
that works) and find an alternative to working around the ng-packagr
bug. I spoke with the Angular team, and they recommended pulling the
provider into its own object. `forwardRef` is now required since we are
referencing the component before it is declared.

- Revert
82d6309ef1
- Moves provider to an object to avoid ng-packagr issue
- I reverted the proxy e2e tests. These are no longer needed since we
are not ejecting from the typical `ProxyCmp` usage anymore.

## 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. -->
Dev build: `7.5.3-dev.11698699090.1151d73f`
Verified that the issue is fixed with the repro provided in
https://github.com/ionic-team/ionic-framework/issues/28431

Also verified that this does not regress the issue described in
https://github.com/ionic-team/ionic-framework/issues/28358.
2023-10-31 18:12:57 +00:00

155 lines
4.4 KiB
TypeScript

import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostListener,
Injector,
NgZone,
forwardRef,
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from '@ionic/angular/common';
import type {
InputInputEventDetail as IIonInputInputInputEventDetail,
InputChangeEventDetail as IIonInputInputChangeEventDetail,
Components,
} from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-input.js';
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
const INPUT_INPUTS = [
'accept',
'autocapitalize',
'autocomplete',
'autocorrect',
'autofocus',
'clearInput',
'clearOnEdit',
'color',
'counter',
'counterFormatter',
'debounce',
'disabled',
'enterkeyhint',
'errorText',
'fill',
'helperText',
'inputmode',
'label',
'labelPlacement',
'legacy',
'max',
'maxlength',
'min',
'minlength',
'mode',
'multiple',
'name',
'pattern',
'placeholder',
'readonly',
'required',
'shape',
'size',
'spellcheck',
'step',
'type',
'value',
];
/**
* Pulling the provider into an object and using PURE works
* around an ng-packagr issue that causes
* components with multiple decorators and
* a provider to be re-assigned. This re-assignment
* is not supported by Webpack and causes treeshaking
* to not work on these kinds of components.
*/
const accessorProvider = {
provide: NG_VALUE_ACCESSOR,
useExisting: /*@__PURE__*/ forwardRef(() => IonInput),
multi: true,
};
@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: INPUT_INPUTS,
methods: ['setFocus', 'getInputElement'],
})
@Component({
selector: 'ion-input',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: INPUT_INPUTS,
providers: [accessorProvider],
standalone: true,
})
export class IonInput extends ValueAccessor {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
super(injector, r);
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']);
}
@HostListener('ionInput', ['$event.target'])
handleIonInput(el: HTMLIonInputElement): void {
this.handleValueChange(el, el.value);
}
registerOnChange(fn: (_: any) => void): void {
super.registerOnChange((value: string) => {
if (this.type === 'number') {
/**
* If the input type is `number`, we need to convert the value to a number
* when the value is not empty. If the value is empty, we want to treat
* the value as null.
*/
fn(value === '' ? null : parseFloat(value));
} else {
fn(value);
}
});
}
}
export declare interface IonInput extends Components.IonInput {
/**
* The `ionInput` event is fired each time the user modifies the input's value.
Unlike the `ionChange` event, the `ionInput` event is fired for each alteration
to the input's value. This typically happens for each keystroke as the user types.
For elements that accept text input (`type=text`, `type=tel`, etc.), the interface
is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others,
the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If
the input is cleared on edit, the type is `null`.
*/
ionInput: EventEmitter<CustomEvent<IIonInputInputInputEventDetail>>;
/**
* The `ionChange` event is fired when the user modifies the input's value.
Unlike the `ionInput` event, the `ionChange` event is only fired when changes
are committed, not as the user types.
Depending on the way the users interacts with the element, the `ionChange`
event fires at a different moment:
- When the user commits the change explicitly (e.g. by selecting a date
from a date picker for `<ion-input type="date">`, pressing the "Enter" key, etc.).
- When the element loses focus after its value has changed: for elements
where the user's interaction is typing.
*/
ionChange: EventEmitter<CustomEvent<IIonInputInputChangeEventDetail>>;
/**
* Emitted when the input loses focus.
*/
ionBlur: EventEmitter<CustomEvent<FocusEvent>>;
/**
* Emitted when the input has focus.
*/
ionFocus: EventEmitter<CustomEvent<FocusEvent>>;
}