mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(angular): standalone form controls can participate in forms (#28125)
Issue number: N/A --------- <!-- 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. --> Ionic standalone form controls cannot participate in Angular forms. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Ionic form controls can participate in Angular forms by importing the standalone component - Applies to: `ion-input`, `ion-textarea`, `ion-searchbar`, `ion-toggle`, `ion-checkbox`, `ion-segment`, `ion-radio`, `ion-radio-group`, `ion-datetime` and `ion-range`. - Refactors `ValueAccessor` from `@ionic/angular` to `@ionic/angular/common` - Refactors `raf` utility from `@ionic/angular` to `@ionic/angular/common` ## 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. --> ---------
This commit is contained in:
@@ -26,6 +26,21 @@ export const routes: Routes = [
|
||||
{ path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'value-accessors',
|
||||
children: [
|
||||
{ path: 'checkbox', loadComponent: () => import('../value-accessors/checkbox/checkbox.component').then(c => c.CheckboxComponent) },
|
||||
{ path: 'datetime', loadComponent: () => import('../value-accessors/datetime/datetime.component').then(c => c.DatetimeComponent) },
|
||||
{ path: 'input', loadComponent: () => import('../value-accessors/input/input.component').then(c => c.InputComponent) },
|
||||
{ path: 'radio-group', loadComponent: () => import('../value-accessors/radio-group/radio-group.component').then(c => c.RadioGroupComponent) },
|
||||
{ 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: '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' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<h1>IonCheckbox Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-checkbox to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-checkbox formControlName="checkbox"></ion-checkbox>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonCheckbox } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-checkbox',
|
||||
templateUrl: 'checkbox.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonCheckbox,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class CheckboxComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
checkbox: [false, Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<h1>IonDatetime Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-datetime to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-datetime formControlName="datetime"></ion-datetime>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonDatetime } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-datetime',
|
||||
templateUrl: 'datetime.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonDatetime,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class DatetimeComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
datetime: ['2023-05-10T04:00:00', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<h1>IonInput Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-input to make sure values are correctly assigned to the form group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-input label="String" formControlName="inputString"></ion-input>
|
||||
<ion-input label="Number" type="number" formControlName="inputNumber"></ion-input>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonInput } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-input',
|
||||
templateUrl: 'input.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonInput,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent,
|
||||
]
|
||||
})
|
||||
export class InputComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
inputString: ['', Validators.required],
|
||||
inputNumber: ['', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<div>
|
||||
<h1>IonRadioGroup Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-radio-group to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-radio-group formControlName="radioGroup">
|
||||
<ion-radio value="1">One</ion-radio>
|
||||
<ion-radio value="2">Two</ion-radio>
|
||||
</ion-radio-group>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonRadioGroup, IonRadio } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-radio-group',
|
||||
templateUrl: 'radio-group.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonRadioGroup,
|
||||
IonRadio,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class RadioGroupComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
radioGroup: ['1', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<h1>IonRange Value Accessors</h1>
|
||||
<p>
|
||||
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>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonRange } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-range',
|
||||
templateUrl: 'range.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonRange,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class RangeComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
range: [0, Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div>
|
||||
<h1>IonSearchbar Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-searchbar to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-searchbar label="String" formControlName="searchbar"></ion-searchbar>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonSearchbar } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-searchbar',
|
||||
templateUrl: 'searchbar.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonSearchbar,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class SearchbarComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
searchbar: ['', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<div>
|
||||
<h1>IonSegment Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-segment to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-segment formControlName="segment">
|
||||
<ion-segment-button value="Paid">
|
||||
<ion-label>Paid</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="Free">
|
||||
<ion-label>Free</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonSegment, IonSegmentButton, IonLabel } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-segment',
|
||||
templateUrl: 'segment.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonSegment,
|
||||
IonSegmentButton,
|
||||
IonLabel,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class SegmentComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
segment: ['Paid', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div>
|
||||
<h1>IonTextarea Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-textarea to make sure values are correctly assigned to the form
|
||||
group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-textarea label="String" formControlName="textarea"></ion-textarea>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonTextarea } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-textarea',
|
||||
templateUrl: 'textarea.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonTextarea,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class TextareaComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
textarea: ['', Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div>
|
||||
<h1>IonToggle Value Accessors</h1>
|
||||
<p>
|
||||
This test checks the form integrations with ion-toggle to make sure values are correctly assigned to the form group.
|
||||
</p>
|
||||
|
||||
<app-value-accessor-test [formGroup]="form">
|
||||
<ion-toggle formControlName="toggle"></ion-toggle>
|
||||
</app-value-accessor-test>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { IonToggle } from "@ionic/angular/standalone";
|
||||
import { ValueAccessorTestComponent } from "../value-accessor-test/value-accessor-test.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-toggle',
|
||||
templateUrl: 'toggle.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
IonToggle,
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
ValueAccessorTestComponent
|
||||
]
|
||||
})
|
||||
export class ToggleComponent {
|
||||
|
||||
form = this.fb.group({
|
||||
toggle: [false, Validators.required],
|
||||
});
|
||||
|
||||
constructor(private fb: FormBuilder) { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<form [formGroup]="formGroup">
|
||||
<div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Submit" [disabled]="!formGroup.valid" />
|
||||
|
||||
<h3>Form Value</h3>
|
||||
<pre id="formValue">{{ formGroup.value | json }}</pre>
|
||||
|
||||
<h3>Form Validity</h3>
|
||||
<ul id="formValidity">
|
||||
<li>Form valid: {{ formGroup.valid }}</li>
|
||||
<li *ngFor="let control of formGroup.controls | keyvalue">{{ control.key }} valid: {{ control.value.valid }}</li>
|
||||
</ul>
|
||||
</form>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CommonModule, JsonPipe, KeyValuePipe } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: 'app-value-accessor-test',
|
||||
templateUrl: 'value-accessor-test.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
JsonPipe,
|
||||
KeyValuePipe,
|
||||
/**
|
||||
* NgFor directive is not available until Angular 15.
|
||||
* We import the CommonModule for now.
|
||||
*
|
||||
* TODO: FW-5197 Replace with NgFor when dropping Angular 14 support.
|
||||
*/
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class ValueAccessorTestComponent {
|
||||
|
||||
@Input() formGroup!: FormGroup;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user