refactor(angular): move to packages directory (#27719)

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

The `angular` directory sits at the root of the project instead of in
`packages` with all the other JS Framework integrations. This does not
cause any functional issues with Ionic, but it is confusing since
integrations are not in a consistent place.

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

- Moves the `angular` directory to `packages/angular`

Note: Most files should remain unchanged. The only files I changed are
the files that had direct paths to the old `angular` directory:

1. Removes the `angular` path in `lerna.json`. This is now covered by
`packages/*`
2. Updated the angular file path in `.gitignore`
3. Updates the path to the angular package in `stencil.config.ts` for
the Angular Output Targets
4. Updates some of Angular's sync scripts to correctly get the core
stylesheets as well as the core package.
5. Updates the test app sync script to correctly sync core and
angular-server

~I'm not entirely sure why GitHub thinks
https://github.com/ionic-team/ionic-framework/pull/27719/files#diff-f5bba7e7c7c75426e2b9c89868310cb03890493b4efe0252adf8d12cc8398962
is a new file since it exists in `main` here:
1f06be4a31/angular/test/base/scripts/build-ionic.sh~
Fixed in
6e7fc49827

## 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.1.2-dev.11688052109.13454f5c`
This commit is contained in:
Liam DeBeasi
2023-07-05 13:52:35 -04:00
committed by GitHub
parent e5ab6d8804
commit 32bc33ed28
268 changed files with 29 additions and 30 deletions

View File

@@ -0,0 +1,30 @@
import { Directive, HostListener, ElementRef, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor, setIonicClasses } from './value-accessor';
@Directive({
selector: 'ion-checkbox,ion-toggle',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: BooleanValueAccessorDirective,
multi: true,
},
],
})
export class BooleanValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
writeValue(value: boolean): void {
this.el.nativeElement.checked = this.lastValue = value;
setIonicClasses(this.el);
}
@HostListener('ionChange', ['$event.target'])
_handleIonChange(el: HTMLIonCheckboxElement | HTMLIonToggleElement): void {
this.handleValueChange(el, el.checked);
}
}

View File

@@ -0,0 +1,5 @@
export * from './boolean-value-accessor';
export * from './numeric-value-accessor';
export * from './radio-value-accessor';
export * from './select-value-accessor';
export * from './text-value-accessor';

View File

@@ -0,0 +1,31 @@
import { Directive, HostListener, ElementRef, Injector } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@Directive({
selector: 'ion-input[type=number]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: NumericValueAccessorDirective,
multi: true,
},
],
})
export class NumericValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionInput', ['$event.target'])
handleInputEvent(el: HTMLIonInputElement): void {
this.handleValueChange(el, el.value);
}
registerOnChange(fn: (_: number | null) => void): void {
super.registerOnChange((value: string) => {
fn(value === '' ? null : parseFloat(value));
});
}
}

View File

@@ -0,0 +1,27 @@
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-radio',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: RadioValueAccessorDirective,
multi: true,
},
],
})
export class RadioValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
// TODO(FW-2827): type (HTMLIonRadioElement and HTMLElement are both missing `checked`)
@HostListener('ionSelect', ['$event.target'])
_handleIonSelect(el: any): void {
this.handleValueChange(el, el.checked);
}
}

View File

@@ -0,0 +1,33 @@
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@Directive({
/* tslint:disable-next-line:directive-selector */
selector: 'ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SelectValueAccessorDirective,
multi: true,
},
],
})
export class SelectValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionChange', ['$event.target'])
_handleChangeEvent(
el:
| HTMLIonRangeElement
| HTMLIonSelectElement
| HTMLIonRadioGroupElement
| HTMLIonSegmentElement
| HTMLIonDatetimeElement
): void {
this.handleValueChange(el, el.value);
}
}

View File

@@ -0,0 +1,25 @@
import { ElementRef, Injector, Directive, HostListener } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { ValueAccessor } from './value-accessor';
@Directive({
selector: 'ion-input:not([type=number]),ion-textarea,ion-searchbar',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: TextValueAccessorDirective,
multi: true,
},
],
})
export class TextValueAccessorDirective extends ValueAccessor {
constructor(injector: Injector, el: ElementRef) {
super(injector, el);
}
@HostListener('ionInput', ['$event.target'])
_handleInputEvent(el: HTMLIonInputElement | HTMLIonTextareaElement | HTMLIonSearchbarElement): void {
this.handleValueChange(el, el.value);
}
}

View File

@@ -0,0 +1,150 @@
import { AfterViewInit, ElementRef, Injector, OnDestroy, Directive, HostListener } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import { raf } from '../../util/util';
// TODO(FW-2827): types
@Directive()
export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDestroy {
private onChange: (value: any) => void = () => {
/**/
};
private onTouched: () => void = () => {
/**/
};
protected lastValue: any;
private statusChanges?: Subscription;
constructor(protected injector: Injector, protected el: ElementRef) {}
writeValue(value: any): void {
this.el.nativeElement.value = this.lastValue = value;
setIonicClasses(this.el);
}
/**
* Notifies the ControlValueAccessor of a change in the value of the control.
*
* This is called by each of the ValueAccessor directives when we want to update
* the status and validity of the form control. For example with text components this
* is called when the ionInput event is fired. For select components this is called
* when the ionChange event is fired.
*
* This also updates the Ionic form status classes on the element.
*
* @param el The component element.
* @param value The new value of the control.
*/
handleValueChange(el: HTMLElement, value: any): void {
if (el === this.el.nativeElement) {
if (value !== this.lastValue) {
this.lastValue = value;
this.onChange(value);
}
setIonicClasses(this.el);
}
}
@HostListener('ionBlur', ['$event.target'])
_handleBlurEvent(el: any): void {
if (el === this.el.nativeElement) {
this.onTouched();
setIonicClasses(this.el);
}
}
registerOnChange(fn: (value: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.el.nativeElement.disabled = isDisabled;
}
ngOnDestroy(): void {
if (this.statusChanges) {
this.statusChanges.unsubscribe();
}
}
ngAfterViewInit(): void {
let ngControl;
try {
ngControl = this.injector.get<NgControl>(NgControl);
} catch {
/* No FormControl or ngModel binding */
}
if (!ngControl) {
return;
}
// Listen for changes in validity, disabled, or pending states
if (ngControl.statusChanges) {
this.statusChanges = ngControl.statusChanges.subscribe(() => setIonicClasses(this.el));
}
/**
* TODO FW-2787: Remove this in favor of https://github.com/angular/angular/issues/10887
* whenever it is implemented.
*/
const formControl = ngControl.control as any;
if (formControl) {
const methodsToPatch = ['markAsTouched', 'markAllAsTouched', 'markAsUntouched', 'markAsDirty', 'markAsPristine'];
methodsToPatch.forEach((method) => {
if (typeof formControl[method] !== 'undefined') {
const oldFn = formControl[method].bind(formControl);
formControl[method] = (...params: any[]) => {
oldFn(...params);
setIonicClasses(this.el);
};
}
});
}
}
}
export const setIonicClasses = (element: ElementRef): void => {
raf(() => {
const input = element.nativeElement as HTMLInputElement;
const hasValue = input.value != null && input.value.toString().length > 0;
const classes = getClasses(input);
setClasses(input, classes);
const item = input.closest('ion-item');
if (item) {
if (hasValue) {
setClasses(item, [...classes, 'item-has-value']);
} else {
setClasses(item, classes);
}
}
});
};
const getClasses = (element: HTMLElement) => {
const classList = element.classList;
const classes = [];
for (let i = 0; i < classList.length; i++) {
const item = classList.item(i);
if (item !== null && startsWith(item, 'ng-')) {
classes.push(`ion-${item.substring(3)}`);
}
}
return classes;
};
const setClasses = (element: HTMLElement, classes: string[]) => {
const classList = element.classList;
classList.remove('ion-valid', 'ion-invalid', 'ion-touched', 'ion-untouched', 'ion-dirty', 'ion-pristine');
classList.add(...classes);
};
const startsWith = (input: string, search: string): boolean => {
return input.substring(0, search.length) === search;
};