Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions packages/angular/standalone/src/directives/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ import {
Injector,
NgZone,
} from '@angular/core';
import type { OnInit } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor, setIonicClasses } from '@ionic/angular/common';
import type { CheckboxChangeEventDetail, Components } from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-checkbox.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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 IonCheckbox = IonCheckbox_1 = class IonCheckbox extends ValueAccessor {
* Instead, we want only want the class generated:
* class IonCheckbox extends ValueAccessor {
*/
import { proxyInputs, proxyOutputs } from './angular-component-lib/utils';

const CHECKBOX_INPUTS = [
'checked',
Expand All @@ -28,10 +41,6 @@ const CHECKBOX_INPUTS = [
'value',
];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: CHECKBOX_INPUTS,
})
@Component({
selector: 'ion-checkbox',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -47,15 +56,25 @@ const CHECKBOX_INPUTS = [
],
standalone: true,
})
export class IonCheckbox extends ValueAccessor {
export class IonCheckbox 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', 'ionFocus', 'ionBlur']);
}

ngOnInit(): void {
/**
* Data-bound input properties are set
* by Angular after the constructor, so
* we need to run the proxy in ngOnInit.
*/
proxyInputs(IonCheckbox, CHECKBOX_INPUTS);
}

writeValue(value: boolean): void {
this.elementRef.nativeElement.checked = this.lastValue = value;
setIonicClasses(this.elementRef);
Expand Down
35 changes: 28 additions & 7 deletions packages/angular/standalone/src/directives/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ import {
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 { DatetimeChangeEventDetail, Components } from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-datetime.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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 IonDatetime = IonDatetime_1 = class IonDatetime extends ValueAccessor {
* Instead, we want only want the class generated:
* class IonDatetime extends ValueAccessor {
*/
import { proxyInputs, proxyMethods, proxyOutputs } from './angular-component-lib/utils';

const DATETIME_INPUTS = [
'cancelText',
Expand Down Expand Up @@ -48,11 +61,8 @@ const DATETIME_INPUTS = [
'yearValues',
];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: DATETIME_INPUTS,
methods: ['confirm', 'reset', 'cancel'],
})
const DATETIME_METHODS = ['confirm', 'reset', 'cancel'];

@Component({
selector: 'ion-datetime',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -68,15 +78,26 @@ const DATETIME_INPUTS = [
],
standalone: true,
})
export class IonDatetime extends ValueAccessor {
export class IonDatetime 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, ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur']);
}

ngOnInit(): void {
/**
* Data-bound input properties are set
* by Angular after the constructor, so
* we need to run the proxy in ngOnInit.
*/
proxyInputs(IonDatetime, DATETIME_INPUTS);
proxyMethods(IonDatetime, DATETIME_METHODS);
}

@HostListener('ionChange', ['$event.target'])
handleIonChange(el: HTMLIonDatetimeElement): void {
this.handleValueChange(el, el.value);
Expand Down
35 changes: 28 additions & 7 deletions packages/angular/standalone/src/directives/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
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 {
Expand All @@ -17,7 +18,19 @@ import type {
} from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-input.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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 IonInput = IonInput_1 = class IonInput extends ValueAccessor {
* Instead, we want only want the class generated:
* class IonInput extends ValueAccessor {
*/
import { proxyInputs, proxyMethods, proxyOutputs } from './angular-component-lib/utils';

const INPUT_INPUTS = [
'accept',
Expand Down Expand Up @@ -59,11 +72,8 @@ const INPUT_INPUTS = [
'value',
];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: INPUT_INPUTS,
methods: ['setFocus', 'getInputElement'],
})
const INPUT_METHODS = ['setFocus', 'getInputElement'];

@Component({
selector: 'ion-input',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -79,15 +89,26 @@ const INPUT_INPUTS = [
],
standalone: true,
})
export class IonInput extends ValueAccessor {
export class IonInput 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, ['ionInput', 'ionChange', 'ionBlur', 'ionFocus']);
}

ngOnInit(): void {
/**
* Data-bound input properties are set
* by Angular after the constructor, so
* we need to run the proxy in ngOnInit.
*/
proxyInputs(IonInput, INPUT_INPUTS);
proxyMethods(IonInput, INPUT_METHODS);
}

@HostListener('ionInput', ['$event.target'])
handleIonInput(el: HTMLIonInputElement): void {
this.handleValueChange(el, el.value);
Expand Down
31 changes: 25 additions & 6 deletions packages/angular/standalone/src/directives/radio-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@ import {
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 { RadioGroupChangeEventDetail, Components } from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-radio-group.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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 IonRadioGroup = IonRadioGroup_1 = class IonRadioGroup extends ValueAccessor {
* Instead, we want only want the class generated:
* class IonRadioGroup extends ValueAccessor {
*/
import { proxyInputs, proxyOutputs } from './angular-component-lib/utils';

const RADIO_GROUP_INPUTS = ['allowEmptySelection', 'name', 'value'];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: RADIO_GROUP_INPUTS,
})
@Component({
selector: 'ion-radio-group',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -36,15 +45,25 @@ const RADIO_GROUP_INPUTS = ['allowEmptySelection', 'name', 'value'];
],
standalone: true,
})
export class IonRadioGroup extends ValueAccessor {
export class IonRadioGroup 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']);
}

ngOnInit(): void {
/**
* Data-bound input properties are set
* by Angular after the constructor, so
* we need to run the proxy in ngOnInit.
*/
proxyInputs(IonRadioGroup, RADIO_GROUP_INPUTS);
}

@HostListener('ionChange', ['$event.target'])
handleIonChange(el: HTMLIonRadioGroupElement): void {
this.handleValueChange(el, el.value);
Expand Down
31 changes: 25 additions & 6 deletions packages/angular/standalone/src/directives/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,28 @@ import {
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 { Components } from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-radio.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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 IonRadio = IonRadio_1 = class IonRadio extends ValueAccessor {
* Instead, we want only want the class generated:
* class IonRadio extends ValueAccessor {
*/
import { proxyInputs, proxyOutputs } from './angular-component-lib/utils';

const RADIO_INPUTS = ['color', 'disabled', 'justify', 'labelPlacement', 'legacy', 'mode', 'name', 'value'];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: RADIO_INPUTS,
})
@Component({
selector: 'ion-radio',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -36,15 +45,25 @@ const RADIO_INPUTS = ['color', 'disabled', 'justify', 'labelPlacement', 'legacy'
],
standalone: true,
})
export class IonRadio extends ValueAccessor {
export class IonRadio 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, ['ionFocus', 'ionBlur']);
}

ngOnInit(): void {
/**
* Data-bound input properties are set
* by Angular after the constructor, so
* we need to run the proxy in ngOnInit.
*/
proxyInputs(IonRadio, RADIO_INPUTS);
}

@HostListener('ionSelect', ['$event.target'])
handleIonSelect(el: any): void {
/**
Expand Down
31 changes: 25 additions & 6 deletions packages/angular/standalone/src/directives/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
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 {
Expand All @@ -18,7 +19,19 @@ import type {
} from '@ionic/core/components';
import { defineCustomElement } from '@ionic/core/components/ion-range.js';

import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
/**
* 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',
Expand All @@ -41,10 +54,6 @@ const RANGE_INPUTS = [
'value',
];

@ProxyCmp({
defineCustomElementFn: defineCustomElement,
inputs: RANGE_INPUTS,
})
@Component({
selector: 'ion-range',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -60,15 +69,25 @@ const RANGE_INPUTS = [
],
standalone: true,
})
export class IonRange extends ValueAccessor {
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);
Expand Down
Loading
Loading