From 17ce4c332a1a599ecb8b3fa23bbed1692d63fc91 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 10:47:57 -0500 Subject: [PATCH 01/12] fix(angular): setting props on a signal works --- packages/angular/common/src/providers/angular-delegate.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index c1524e8b72c..e4b5dd13c02 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -164,7 +164,13 @@ export const attachView = ( ); } - Object.assign(instance, params); + if (componentRef.setInput !== undefined) { + for (const key in params) { + componentRef.setInput(key, params[key]); + } + } else { + Object.assign(instance, params); + } } if (cssClasses) { for (const cssClass of cssClasses) { From f5c4f7ade3699c6112c78b473d8438ff54941687 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 10:49:15 -0500 Subject: [PATCH 02/12] add comments --- .../angular/common/src/providers/angular-delegate.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index e4b5dd13c02..667a4ee976e 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -164,7 +164,16 @@ export const attachView = ( ); } + /** + * Angular 14.1 added support for setInput + * so we need to fall back to Object.assign + * for Angular 14.0. + */ if (componentRef.setInput !== undefined) { + /** + * Any key/value pairs set in componentProps + * must be set as inputs on the component instance. + */ for (const key in params) { componentRef.setInput(key, params[key]); } From 3dcd77288ad26fa2514a37aeacf1a68ddf8c6ec4 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 11:01:34 -0500 Subject: [PATCH 03/12] use fallbacks --- .../common/src/providers/angular-delegate.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index 667a4ee976e..b09ff912999 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -170,12 +170,28 @@ export const attachView = ( * for Angular 14.0. */ if (componentRef.setInput !== undefined) { + const { modal, popover, otherParams } = params; /** * Any key/value pairs set in componentProps * must be set as inputs on the component instance. */ - for (const key in params) { - componentRef.setInput(key, params[key]); + for (const key in otherParams) { + componentRef.setInput(key, otherParams[key]); + } + + /** + * Using setInput will cause an error when + * setting modal/popover on a component that + * does not define them as an input. For backwards + * compatibility purposes we fall back to using + * Object.assign for these properties. + */ + if (modal !== undefined) { + Object.assign(instance, { modal }); + } + + if (popover !== undefined) { + Object.assign(instance, { popover }); } } else { Object.assign(instance, params); From 1ea025d264475b22dc85d1115c2b529ecee6df87 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 11:12:22 -0500 Subject: [PATCH 04/12] typo --- packages/angular/common/src/providers/angular-delegate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index b09ff912999..c9efeb83319 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -170,7 +170,7 @@ export const attachView = ( * for Angular 14.0. */ if (componentRef.setInput !== undefined) { - const { modal, popover, otherParams } = params; + const { modal, popover, ...otherParams } = params; /** * Any key/value pairs set in componentProps * must be set as inputs on the component instance. From 729529f40e8d22c479a3fb93930bed39d2d1fb2c Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 12:51:57 -0500 Subject: [PATCH 05/12] refactor: remove NavParams --- BREAKING.md | 78 +++++++++++++++++++ .../src/directives/navigation/nav-params.ts | 43 ---------- packages/angular/common/src/index.ts | 2 - .../common/src/providers/angular-delegate.ts | 42 +--------- packages/angular/src/index.ts | 1 - packages/angular/standalone/src/index.ts | 1 - 6 files changed, 79 insertions(+), 88 deletions(-) delete mode 100644 packages/angular/common/src/directives/navigation/nav-params.ts diff --git a/BREAKING.md b/BREAKING.md index 22482f89240..3896ee79da4 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -22,6 +22,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Datetime](#version-8x-datetime) - [Nav](#version-8x-nav) - [Picker](#version-8x-picker) +- [Angular](#version-8x-angular)

Browser and Platform Support

@@ -143,3 +144,80 @@ This allows components to inherit the color properly when used outside of Ionic - `ion-picker` and `ion-picker-column` have been renamed to `ion-picker-legacy` and `ion-picker-legacy-column`, respectively. This change was made to accommodate the new inline picker component while allowing developers to continue to use the legacy picker during this migration period. - Only the component names have been changed. Usages such as `ion-picker` or `IonPicker` should be changed to `ion-picker-legacy` and `IonPickerLegacy`, respectively. - Non-component usages such as `pickerController` or `useIonPicker` remain unchanged. The new picker displays inline with your page content and does not have equivalents for these non-component usages. + +

Angular

+ +- NavParams has been removed in favor of using Angular's Input API. Components that accessed parameters should ensure that an input is created for each parameter: + +**Old** +```js +import { NavParams } from '@ionic/angular'; + +console.log(this.navParams.get('myProp')); +``` + +**New** +```js +import { Input } from '@angular/core'; + +... + +@Input() myProp: string; + +ngOnInit() { + console.log(this.myProp) +} +``` + +Developers who were using `NavParams` as a pass through to an encapsulated IonNav component can use a single `Input` to achieve the same behavior. + +**Old** +```js +// home.page.ts +const modal = await modalCtrl.create({ + component: NavComponent, + componentProps: { + foo: 'hello', + bar: 'goodbye' + } +}); +``` + +```js +// nav.component.ts +public rootParams = {}; + +constructor(private navParams: NavParams) { + this.rootParams = { + ...navParams.data + } +} +``` +```html + + +``` + +**New** + +```js +// home.page.ts +const modal = await modalCtrl.create({ + component: NavComponent, + componentProps: { + rootParams: { + foo: 'hello', + bar: 'goodbye' + } + } +}); +``` + +```js +// nav.component.ts +@Input() rootParams = {}; +``` +```html + + +``` \ No newline at end of file diff --git a/packages/angular/common/src/directives/navigation/nav-params.ts b/packages/angular/common/src/directives/navigation/nav-params.ts deleted file mode 100644 index a5af4b9d631..00000000000 --- a/packages/angular/common/src/directives/navigation/nav-params.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @description - * NavParams are an object that exists on a page and can contain data for that particular view. - * Similar to how data was pass to a view in V1 with `$stateParams`, NavParams offer a much more flexible - * option with a simple `get` method. - * - * @usage - * ```ts - * import { NavParams } from '@ionic/angular'; - * - * export class MyClass{ - * - * constructor(navParams: NavParams){ - * // userParams is an object we have in our nav-parameters - * navParams.get('userParams'); - * } - * - * } - * ``` - */ -export class NavParams { - constructor(public data: { [key: string]: any } = {}) {} - - /** - * Get the value of a nav-parameter for the current view - * - * ```ts - * import { NavParams } from 'ionic-angular'; - * - * export class MyClass{ - * constructor(public navParams: NavParams){ - * // userParams is an object we have in our nav-parameters - * this.navParams.get('userParams'); - * } - * } - * ``` - * - * @param param Which param you want to look up - */ - get(param: string): T { - return this.data[param]; - } -} diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 6e5c909acf3..7c8a8bceb79 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -10,8 +10,6 @@ export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delega export type { IonicWindow } from './types/interfaces'; export type { ViewWillEnter, ViewWillLeave, ViewDidEnter, ViewDidLeave } from './types/ionic-lifecycle-hooks'; -export { NavParams } from './directives/navigation/nav-params'; - export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index c9efeb83319..0e51a197b81 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -6,7 +6,6 @@ import { EnvironmentInjector, inject, createComponent, - InjectionToken, ComponentRef, } from '@angular/core'; import { @@ -18,8 +17,6 @@ import { LIFECYCLE_WILL_UNLOAD, } from '@ionic/core/components'; -import { NavParams } from '../directives/navigation/nav-params'; - // TODO(FW-2827): types @Injectable() @@ -123,26 +120,9 @@ export const attachView = ( cssClasses: string[] | undefined, elementReferenceKey: string | undefined ): any => { - /** - * Wraps the injector with a custom injector that - * provides NavParams to the component. - * - * NavParams is a legacy feature from Ionic v3 that allows - * Angular developers to provide data to a component - * and access it by providing NavParams as a dependency - * in the constructor. - * - * The modern approach is to access the data directly - * from the component's class instance. - */ - const childInjector = Injector.create({ - providers: getProviders(params), - parent: injector, - }); - const componentRef = createComponent(component, { environmentInjector, - elementInjector: childInjector, + elementInjector: injector, }); const instance = componentRef.instance; @@ -230,23 +210,3 @@ export const bindLifecycleEvents = (zone: NgZone, instance: any, element: HTMLEl return () => unregisters.forEach((fn) => fn()); }); }; - -const NavParamsToken = new InjectionToken('NavParamsToken'); - -const getProviders = (params: { [key: string]: any }) => { - return [ - { - provide: NavParamsToken, - useValue: params, - }, - { - provide: NavParams, - useFactory: provideNavParamsInjectable, - deps: [NavParamsToken], - }, - ]; -}; - -const provideNavParamsInjectable = (params: { [key: string]: any }) => { - return new NavParams(params); -}; diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index da4a6c549c0..8096030ef63 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -24,7 +24,6 @@ export { Config, Platform, AngularDelegate, - NavParams, IonicRouteStrategy, ViewWillEnter, ViewWillLeave, diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index 741fef2831f..a47da34dc10 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -20,7 +20,6 @@ export { NavController, Config, Platform, - NavParams, IonicRouteStrategy, ViewWillEnter, ViewDidEnter, From 96a5433c96139c286237c0fa669fde737989b57a Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 12:05:26 -0500 Subject: [PATCH 06/12] chore: update test app --- .../test/base/src/app/lazy/alert/alert.component.ts | 1 - .../lazy/modal-example/modal-example.component.html | 2 +- .../lazy/modal-example/modal-example.component.ts | 11 ++++------- .../test/base/src/app/lazy/nav/nav.component.ts | 13 +++++++------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/angular/test/base/src/app/lazy/alert/alert.component.ts b/packages/angular/test/base/src/app/lazy/alert/alert.component.ts index 4a5b9e10703..d92f9339d2d 100644 --- a/packages/angular/test/base/src/app/lazy/alert/alert.component.ts +++ b/packages/angular/test/base/src/app/lazy/alert/alert.component.ts @@ -1,6 +1,5 @@ import { Component, NgZone } from '@angular/core'; import { AlertController } from '@ionic/angular'; -import { NavComponent } from '../nav/nav.component'; @Component({ selector: 'app-alert', diff --git a/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html index 4f2bd52a24e..942801614f9 100644 --- a/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html +++ b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.html @@ -11,7 +11,7 @@

Value

{{value}}

-

{{valueFromParams}}

+

{{prop}}

modal is defined: {{ !!modal }}

ngOnInit: {{onInit}}

ionViewWillEnter: {{willEnter}}

diff --git a/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts index d26fff7bd33..495ae1412c9 100644 --- a/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts +++ b/packages/angular/test/base/src/app/lazy/modal-example/modal-example.component.ts @@ -1,6 +1,6 @@ import { Component, Input, NgZone, OnInit, Optional } from '@angular/core'; import { UntypedFormControl, UntypedFormGroup } from '@angular/forms'; -import { ModalController, NavParams, IonNav, ViewWillLeave, ViewDidEnter, ViewDidLeave } from '@ionic/angular'; +import { ModalController, IonNav, ViewWillLeave, ViewDidEnter, ViewDidLeave } from '@ionic/angular'; @Component({ selector: 'app-modal-example', @@ -9,12 +9,12 @@ import { ModalController, NavParams, IonNav, ViewWillLeave, ViewDidEnter, ViewDi export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnter, ViewWillLeave, ViewDidLeave { @Input() value?: string; + @Input() prop?: string; form = new UntypedFormGroup({ select: new UntypedFormControl([]) }); - valueFromParams: string; onInit = 0; willEnter = 0; didEnter = 0; @@ -25,11 +25,8 @@ export class ModalExampleComponent implements OnInit, ViewWillLeave, ViewDidEnte constructor( private modalCtrl: ModalController, - @Optional() public nav: IonNav, - navParams: NavParams - ) { - this.valueFromParams = navParams.get('prop'); - } + @Optional() public nav: IonNav + ) {} ngOnInit() { NgZone.assertInAngularZone(); diff --git a/packages/angular/test/base/src/app/lazy/nav/nav.component.ts b/packages/angular/test/base/src/app/lazy/nav/nav.component.ts index 735f4f4b250..7c5fec01f5a 100644 --- a/packages/angular/test/base/src/app/lazy/nav/nav.component.ts +++ b/packages/angular/test/base/src/app/lazy/nav/nav.component.ts @@ -1,6 +1,5 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { ModalExampleComponent } from '../modal-example/modal-example.component'; -import { NavParams } from '@ionic/angular'; @Component({ selector: 'app-nav', @@ -10,11 +9,13 @@ export class NavComponent { rootPage = ModalExampleComponent; rootParams: any; - constructor( - params: NavParams - ) { + @Input() value?: string; + @Input() prop?: string; + + ngOnInit() { this.rootParams = { - ...params.data + value: this.value, + prop: this.prop }; } } From 375c52844708a26b06b17c8301d1f5b38983be99 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 25 Jan 2024 13:09:08 -0500 Subject: [PATCH 07/12] chore: update test apps --- .../lazy/version-test/modal-nav-params/nav-root.component.ts | 4 ++-- .../lazy/version-test/modal-nav-params/nav-root.component.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts b/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts index ae545093dc7..765a594950c 100644 --- a/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts +++ b/packages/angular/test/apps/ng16/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts @@ -1,5 +1,5 @@ import { JsonPipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { IonicModule } from "@ionic/angular"; @@ -23,7 +23,7 @@ let rootParamsException = false; }) export class NavRootComponent { - params: any; + @Input() params: any = {}; ngOnInit() { if (this.params === undefined) { diff --git a/packages/angular/test/apps/ng17/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts b/packages/angular/test/apps/ng17/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts index ae545093dc7..13bca637f3e 100644 --- a/packages/angular/test/apps/ng17/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts +++ b/packages/angular/test/apps/ng17/src/app/lazy/version-test/modal-nav-params/nav-root.component.ts @@ -1,5 +1,5 @@ import { JsonPipe } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { IonicModule } from "@ionic/angular"; @@ -23,7 +23,7 @@ let rootParamsException = false; }) export class NavRootComponent { - params: any; + @Input() params: any; ngOnInit() { if (this.params === undefined) { From 3e6df87fa55caf4136ae1c2676e879569d9eb87b Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 15 Feb 2024 09:19:07 -0500 Subject: [PATCH 08/12] Revert "refactor: remove NavParams" This reverts commit 729529f40e8d22c479a3fb93930bed39d2d1fb2c. --- BREAKING.md | 78 ------------------- .../src/directives/navigation/nav-params.ts | 43 ++++++++++ packages/angular/common/src/index.ts | 2 + .../common/src/providers/angular-delegate.ts | 42 +++++++++- packages/angular/src/index.ts | 1 + packages/angular/standalone/src/index.ts | 1 + 6 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 packages/angular/common/src/directives/navigation/nav-params.ts diff --git a/BREAKING.md b/BREAKING.md index 3896ee79da4..22482f89240 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -22,7 +22,6 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Datetime](#version-8x-datetime) - [Nav](#version-8x-nav) - [Picker](#version-8x-picker) -- [Angular](#version-8x-angular)

Browser and Platform Support

@@ -144,80 +143,3 @@ This allows components to inherit the color properly when used outside of Ionic - `ion-picker` and `ion-picker-column` have been renamed to `ion-picker-legacy` and `ion-picker-legacy-column`, respectively. This change was made to accommodate the new inline picker component while allowing developers to continue to use the legacy picker during this migration period. - Only the component names have been changed. Usages such as `ion-picker` or `IonPicker` should be changed to `ion-picker-legacy` and `IonPickerLegacy`, respectively. - Non-component usages such as `pickerController` or `useIonPicker` remain unchanged. The new picker displays inline with your page content and does not have equivalents for these non-component usages. - -

Angular

- -- NavParams has been removed in favor of using Angular's Input API. Components that accessed parameters should ensure that an input is created for each parameter: - -**Old** -```js -import { NavParams } from '@ionic/angular'; - -console.log(this.navParams.get('myProp')); -``` - -**New** -```js -import { Input } from '@angular/core'; - -... - -@Input() myProp: string; - -ngOnInit() { - console.log(this.myProp) -} -``` - -Developers who were using `NavParams` as a pass through to an encapsulated IonNav component can use a single `Input` to achieve the same behavior. - -**Old** -```js -// home.page.ts -const modal = await modalCtrl.create({ - component: NavComponent, - componentProps: { - foo: 'hello', - bar: 'goodbye' - } -}); -``` - -```js -// nav.component.ts -public rootParams = {}; - -constructor(private navParams: NavParams) { - this.rootParams = { - ...navParams.data - } -} -``` -```html - - -``` - -**New** - -```js -// home.page.ts -const modal = await modalCtrl.create({ - component: NavComponent, - componentProps: { - rootParams: { - foo: 'hello', - bar: 'goodbye' - } - } -}); -``` - -```js -// nav.component.ts -@Input() rootParams = {}; -``` -```html - - -``` \ No newline at end of file diff --git a/packages/angular/common/src/directives/navigation/nav-params.ts b/packages/angular/common/src/directives/navigation/nav-params.ts new file mode 100644 index 00000000000..a5af4b9d631 --- /dev/null +++ b/packages/angular/common/src/directives/navigation/nav-params.ts @@ -0,0 +1,43 @@ +/** + * @description + * NavParams are an object that exists on a page and can contain data for that particular view. + * Similar to how data was pass to a view in V1 with `$stateParams`, NavParams offer a much more flexible + * option with a simple `get` method. + * + * @usage + * ```ts + * import { NavParams } from '@ionic/angular'; + * + * export class MyClass{ + * + * constructor(navParams: NavParams){ + * // userParams is an object we have in our nav-parameters + * navParams.get('userParams'); + * } + * + * } + * ``` + */ +export class NavParams { + constructor(public data: { [key: string]: any } = {}) {} + + /** + * Get the value of a nav-parameter for the current view + * + * ```ts + * import { NavParams } from 'ionic-angular'; + * + * export class MyClass{ + * constructor(public navParams: NavParams){ + * // userParams is an object we have in our nav-parameters + * this.navParams.get('userParams'); + * } + * } + * ``` + * + * @param param Which param you want to look up + */ + get(param: string): T { + return this.data[param]; + } +} diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 7c8a8bceb79..6e5c909acf3 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -10,6 +10,8 @@ export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delega export type { IonicWindow } from './types/interfaces'; export type { ViewWillEnter, ViewWillLeave, ViewDidEnter, ViewDidLeave } from './types/ionic-lifecycle-hooks'; +export { NavParams } from './directives/navigation/nav-params'; + export { IonPopover } from './overlays/popover'; export { IonModal } from './overlays/modal'; diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index 0e51a197b81..c9efeb83319 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -6,6 +6,7 @@ import { EnvironmentInjector, inject, createComponent, + InjectionToken, ComponentRef, } from '@angular/core'; import { @@ -17,6 +18,8 @@ import { LIFECYCLE_WILL_UNLOAD, } from '@ionic/core/components'; +import { NavParams } from '../directives/navigation/nav-params'; + // TODO(FW-2827): types @Injectable() @@ -120,9 +123,26 @@ export const attachView = ( cssClasses: string[] | undefined, elementReferenceKey: string | undefined ): any => { + /** + * Wraps the injector with a custom injector that + * provides NavParams to the component. + * + * NavParams is a legacy feature from Ionic v3 that allows + * Angular developers to provide data to a component + * and access it by providing NavParams as a dependency + * in the constructor. + * + * The modern approach is to access the data directly + * from the component's class instance. + */ + const childInjector = Injector.create({ + providers: getProviders(params), + parent: injector, + }); + const componentRef = createComponent(component, { environmentInjector, - elementInjector: injector, + elementInjector: childInjector, }); const instance = componentRef.instance; @@ -210,3 +230,23 @@ export const bindLifecycleEvents = (zone: NgZone, instance: any, element: HTMLEl return () => unregisters.forEach((fn) => fn()); }); }; + +const NavParamsToken = new InjectionToken('NavParamsToken'); + +const getProviders = (params: { [key: string]: any }) => { + return [ + { + provide: NavParamsToken, + useValue: params, + }, + { + provide: NavParams, + useFactory: provideNavParamsInjectable, + deps: [NavParamsToken], + }, + ]; +}; + +const provideNavParamsInjectable = (params: { [key: string]: any }) => { + return new NavParams(params); +}; diff --git a/packages/angular/src/index.ts b/packages/angular/src/index.ts index 8096030ef63..da4a6c549c0 100644 --- a/packages/angular/src/index.ts +++ b/packages/angular/src/index.ts @@ -24,6 +24,7 @@ export { Config, Platform, AngularDelegate, + NavParams, IonicRouteStrategy, ViewWillEnter, ViewWillLeave, diff --git a/packages/angular/standalone/src/index.ts b/packages/angular/standalone/src/index.ts index a47da34dc10..741fef2831f 100644 --- a/packages/angular/standalone/src/index.ts +++ b/packages/angular/standalone/src/index.ts @@ -20,6 +20,7 @@ export { NavController, Config, Platform, + NavParams, IonicRouteStrategy, ViewWillEnter, ViewDidEnter, From e414be7aeee6dd66f781245a56dd8741e092a22d Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Thu, 15 Feb 2024 11:30:58 -0500 Subject: [PATCH 09/12] try making this opt-in --- .../src/directives/navigation/nav-params.ts | 6 +++- packages/angular/common/src/index.ts | 2 +- .../common/src/providers/angular-delegate.ts | 32 ++++++++++++++++--- packages/angular/src/ionic-module.ts | 20 +++++++++--- .../standalone/src/providers/ionic-angular.ts | 18 ++++++++--- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/packages/angular/common/src/directives/navigation/nav-params.ts b/packages/angular/common/src/directives/navigation/nav-params.ts index a5af4b9d631..23f4125aff4 100644 --- a/packages/angular/common/src/directives/navigation/nav-params.ts +++ b/packages/angular/common/src/directives/navigation/nav-params.ts @@ -19,7 +19,11 @@ * ``` */ export class NavParams { - constructor(public data: { [key: string]: any } = {}) {} + constructor(public data: { [key: string]: any } = {}) { + console.warn( + `[Ionic Warning]: NavParams has been deprecated in favor of using Angular's input API. Developers should migrate to either the @Input decorator or the Signals-based input API.` + ); + } /** * Get the value of a nav-parameter for the current view diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 6e5c909acf3..1612723810f 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -5,7 +5,7 @@ export { NavController } from './providers/nav-controller'; export { Config, ConfigToken } from './providers/config'; export { Platform } from './providers/platform'; -export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delegate'; +export { bindLifecycleEvents, AngularDelegate, AngularDelegateWithSignalsSupport } from './providers/angular-delegate'; export type { IonicWindow } from './types/interfaces'; export type { ViewWillEnter, ViewWillLeave, ViewDidEnter, ViewDidLeave } from './types/ionic-lifecycle-hooks'; diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index c9efeb83319..3489ee06479 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -42,6 +42,27 @@ export class AngularDelegate { } } +@Injectable() +export class AngularDelegateWithSignalsSupport { + private zone = inject(NgZone); + private applicationRef = inject(ApplicationRef); + + create( + environmentInjector: EnvironmentInjector, + injector: Injector, + elementReferenceKey?: string + ): AngularFrameworkDelegate { + return new AngularFrameworkDelegate( + environmentInjector, + injector, + this.applicationRef, + this.zone, + elementReferenceKey, + true + ); + } +} + export class AngularFrameworkDelegate implements FrameworkDelegate { private elRefMap = new WeakMap>(); private elEventsMap = new WeakMap void>(); @@ -51,7 +72,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate { private injector: Injector, private applicationRef: ApplicationRef, private zone: NgZone, - private elementReferenceKey?: string + private elementReferenceKey?: string, + private enableSignalsSupport?: boolean ) {} attachViewToDom(container: any, component: any, params?: any, cssClasses?: string[]): Promise { @@ -84,7 +106,8 @@ export class AngularFrameworkDelegate implements FrameworkDelegate { component, componentProps, cssClasses, - this.elementReferenceKey + this.elementReferenceKey, + this.enableSignalsSupport ); resolve(el); }); @@ -121,7 +144,8 @@ export const attachView = ( component: any, params: any, cssClasses: string[] | undefined, - elementReferenceKey: string | undefined + elementReferenceKey: string | undefined, + enableSignalsSupport: boolean | undefined ): any => { /** * Wraps the injector with a custom injector that @@ -169,7 +193,7 @@ export const attachView = ( * so we need to fall back to Object.assign * for Angular 14.0. */ - if (componentRef.setInput !== undefined) { + if (enableSignalsSupport === true && componentRef.setInput !== undefined) { const { modal, popover, ...otherParams } = params; /** * Any key/value pairs set in componentProps diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index 13656fc3269..6771823e75e 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -1,6 +1,11 @@ import { CommonModule, DOCUMENT } from '@angular/common'; import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core'; -import { ConfigToken, AngularDelegate, provideComponentInputBinding } from '@ionic/angular/common'; +import { + ConfigToken, + AngularDelegate, + AngularDelegateWithSignalsSupport, + provideComponentInputBinding, +} from '@ionic/angular/common'; import { IonicConfig } from '@ionic/core'; import { appInitialize } from './app-initialize'; @@ -52,20 +57,26 @@ const DECLARATIONS = [ IonMaxValidator, ]; +type OptInAngularFeatures = { + useSetInputAPI: boolean; +}; + @NgModule({ declarations: DECLARATIONS, exports: DECLARATIONS, - providers: [AngularDelegate, ModalController, PopoverController], + providers: [ModalController, PopoverController], imports: [CommonModule], }) export class IonicModule { - static forRoot(config?: IonicConfig): ModuleWithProviders { + static forRoot(config?: IonicConfig & OptInAngularFeatures): ModuleWithProviders { + const { useSetInputAPI, ...rest } = config || {}; + return { ngModule: IonicModule, providers: [ { provide: ConfigToken, - useValue: config, + useValue: rest, }, { provide: APP_INITIALIZER, @@ -73,6 +84,7 @@ export class IonicModule { multi: true, deps: [ConfigToken, DOCUMENT, NgZone], }, + useSetInputAPI ? AngularDelegateWithSignalsSupport : AngularDelegate, provideComponentInputBinding(), ], }; diff --git a/packages/angular/standalone/src/providers/ionic-angular.ts b/packages/angular/standalone/src/providers/ionic-angular.ts index 2b1ad84d552..d2ba9fc3e1f 100644 --- a/packages/angular/standalone/src/providers/ionic-angular.ts +++ b/packages/angular/standalone/src/providers/ionic-angular.ts @@ -1,14 +1,24 @@ import { DOCUMENT } from '@angular/common'; import { APP_INITIALIZER } from '@angular/core'; import type { Provider } from '@angular/core'; -import { AngularDelegate, ConfigToken, provideComponentInputBinding } from '@ionic/angular/common'; +import { + AngularDelegate, + AngularDelegateWithSignalsSupport, + ConfigToken, + provideComponentInputBinding, +} from '@ionic/angular/common'; import { initialize } from '@ionic/core/components'; import type { IonicConfig } from '@ionic/core/components'; import { ModalController } from './modal-controller'; import { PopoverController } from './popover-controller'; -export const provideIonicAngular = (config?: IonicConfig): Provider[] => { +type OptInAngularFeatures = { + useSetInputAPI: boolean; +}; + +export const provideIonicAngular = (config?: IonicConfig & OptInAngularFeatures): Provider[] => { + const { useSetInputAPI, ...rest } = config || {}; /** * TODO FW-4967 * Use makeEnvironmentProviders once Angular 14 support is dropped. @@ -17,7 +27,7 @@ export const provideIonicAngular = (config?: IonicConfig): Provider[] => { return [ { provide: ConfigToken, - useValue: config, + useValue: rest, }, { provide: APP_INITIALIZER, @@ -26,7 +36,7 @@ export const provideIonicAngular = (config?: IonicConfig): Provider[] => { deps: [ConfigToken, DOCUMENT], }, provideComponentInputBinding(), - AngularDelegate, + useSetInputAPI ? AngularDelegateWithSignalsSupport : AngularDelegate, ModalController, PopoverController, ]; From 7d6b2c8784fcfaa1c87e0115aac825c442a7c89d Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Fri, 3 May 2024 13:12:10 -0400 Subject: [PATCH 10/12] fix: opt-in features should be optional --- packages/angular/src/ionic-module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index 6771823e75e..49f9f607345 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -58,7 +58,7 @@ const DECLARATIONS = [ ]; type OptInAngularFeatures = { - useSetInputAPI: boolean; + useSetInputAPI?: boolean; }; @NgModule({ From 820b7bf3971a7ee7a373bf8b45fda0053c69874b Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Fri, 3 May 2024 13:49:19 -0400 Subject: [PATCH 11/12] fix: apply to standalone too --- packages/angular/standalone/src/providers/ionic-angular.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/standalone/src/providers/ionic-angular.ts b/packages/angular/standalone/src/providers/ionic-angular.ts index 9ebd234f451..28d0c42e036 100644 --- a/packages/angular/standalone/src/providers/ionic-angular.ts +++ b/packages/angular/standalone/src/providers/ionic-angular.ts @@ -14,7 +14,7 @@ import { ModalController } from './modal-controller'; import { PopoverController } from './popover-controller'; type OptInAngularFeatures = { - useSetInputAPI: boolean; + useSetInputAPI?: boolean; }; export const provideIonicAngular = (config?: IonicConfig & OptInAngularFeatures): EnvironmentProviders => { From 6675b67f05b2a3a3571b8dc9c29cc6c90a457752 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 6 May 2024 14:58:51 -0400 Subject: [PATCH 12/12] fix: angular provider implementation --- packages/angular/common/src/index.ts | 2 +- .../common/src/providers/angular-delegate.ts | 25 +++---------------- packages/angular/src/ionic-module.ts | 15 +++-------- .../standalone/src/providers/ionic-angular.ts | 19 +++----------- 4 files changed, 13 insertions(+), 48 deletions(-) diff --git a/packages/angular/common/src/index.ts b/packages/angular/common/src/index.ts index 1612723810f..6e5c909acf3 100644 --- a/packages/angular/common/src/index.ts +++ b/packages/angular/common/src/index.ts @@ -5,7 +5,7 @@ export { NavController } from './providers/nav-controller'; export { Config, ConfigToken } from './providers/config'; export { Platform } from './providers/platform'; -export { bindLifecycleEvents, AngularDelegate, AngularDelegateWithSignalsSupport } from './providers/angular-delegate'; +export { bindLifecycleEvents, AngularDelegate } from './providers/angular-delegate'; export type { IonicWindow } from './types/interfaces'; export type { ViewWillEnter, ViewWillLeave, ViewDidEnter, ViewDidLeave } from './types/ionic-lifecycle-hooks'; diff --git a/packages/angular/common/src/providers/angular-delegate.ts b/packages/angular/common/src/providers/angular-delegate.ts index 3489ee06479..fc794c0e34b 100644 --- a/packages/angular/common/src/providers/angular-delegate.ts +++ b/packages/angular/common/src/providers/angular-delegate.ts @@ -20,32 +20,15 @@ import { import { NavParams } from '../directives/navigation/nav-params'; +import { ConfigToken } from './config'; + // TODO(FW-2827): types @Injectable() export class AngularDelegate { private zone = inject(NgZone); private applicationRef = inject(ApplicationRef); - - create( - environmentInjector: EnvironmentInjector, - injector: Injector, - elementReferenceKey?: string - ): AngularFrameworkDelegate { - return new AngularFrameworkDelegate( - environmentInjector, - injector, - this.applicationRef, - this.zone, - elementReferenceKey - ); - } -} - -@Injectable() -export class AngularDelegateWithSignalsSupport { - private zone = inject(NgZone); - private applicationRef = inject(ApplicationRef); + private config = inject(ConfigToken); create( environmentInjector: EnvironmentInjector, @@ -58,7 +41,7 @@ export class AngularDelegateWithSignalsSupport { this.applicationRef, this.zone, elementReferenceKey, - true + this.config.useSetInputAPI ?? false ); } } diff --git a/packages/angular/src/ionic-module.ts b/packages/angular/src/ionic-module.ts index 49f9f607345..acd7745294b 100644 --- a/packages/angular/src/ionic-module.ts +++ b/packages/angular/src/ionic-module.ts @@ -1,11 +1,6 @@ import { CommonModule, DOCUMENT } from '@angular/common'; import { ModuleWithProviders, APP_INITIALIZER, NgModule, NgZone } from '@angular/core'; -import { - ConfigToken, - AngularDelegate, - AngularDelegateWithSignalsSupport, - provideComponentInputBinding, -} from '@ionic/angular/common'; +import { ConfigToken, AngularDelegate, provideComponentInputBinding } from '@ionic/angular/common'; import { IonicConfig } from '@ionic/core'; import { appInitialize } from './app-initialize'; @@ -68,15 +63,13 @@ type OptInAngularFeatures = { imports: [CommonModule], }) export class IonicModule { - static forRoot(config?: IonicConfig & OptInAngularFeatures): ModuleWithProviders { - const { useSetInputAPI, ...rest } = config || {}; - + static forRoot(config: IonicConfig & OptInAngularFeatures = {}): ModuleWithProviders { return { ngModule: IonicModule, providers: [ { provide: ConfigToken, - useValue: rest, + useValue: config, }, { provide: APP_INITIALIZER, @@ -84,7 +77,7 @@ export class IonicModule { multi: true, deps: [ConfigToken, DOCUMENT, NgZone], }, - useSetInputAPI ? AngularDelegateWithSignalsSupport : AngularDelegate, + AngularDelegate, provideComponentInputBinding(), ], }; diff --git a/packages/angular/standalone/src/providers/ionic-angular.ts b/packages/angular/standalone/src/providers/ionic-angular.ts index 28d0c42e036..d810ef4d6b9 100644 --- a/packages/angular/standalone/src/providers/ionic-angular.ts +++ b/packages/angular/standalone/src/providers/ionic-angular.ts @@ -1,12 +1,7 @@ import { DOCUMENT } from '@angular/common'; import { APP_INITIALIZER, makeEnvironmentProviders } from '@angular/core'; import type { EnvironmentProviders } from '@angular/core'; -import { - AngularDelegate, - AngularDelegateWithSignalsSupport, - ConfigToken, - provideComponentInputBinding, -} from '@ionic/angular/common'; +import { AngularDelegate, ConfigToken, provideComponentInputBinding } from '@ionic/angular/common'; import { initialize } from '@ionic/core/components'; import type { IonicConfig } from '@ionic/core/components'; @@ -17,17 +12,11 @@ type OptInAngularFeatures = { useSetInputAPI?: boolean; }; -export const provideIonicAngular = (config?: IonicConfig & OptInAngularFeatures): EnvironmentProviders => { - const { useSetInputAPI, ...rest } = config || {}; - /** - * TODO FW-4967 - * Use makeEnvironmentProviders once Angular 14 support is dropped. - * This prevents provideIonicAngular from being accidentally referenced in an @Component. - */ +export const provideIonicAngular = (config: IonicConfig & OptInAngularFeatures = {}): EnvironmentProviders => { return makeEnvironmentProviders([ { provide: ConfigToken, - useValue: rest, + useValue: config, }, { provide: APP_INITIALIZER, @@ -36,7 +25,7 @@ export const provideIonicAngular = (config?: IonicConfig & OptInAngularFeatures) deps: [ConfigToken, DOCUMENT], }, provideComponentInputBinding(), - useSetInputAPI ? AngularDelegateWithSignalsSupport : AngularDelegate, + AngularDelegate, ModalController, PopoverController, ]);