ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
50.64k stars 13.52k forks source link

bug: virtual prop mode does not account if assigned dynamically for all components #29167

Open DwieDima opened 3 months ago

DwieDima commented 3 months ago

Prerequisites

Ionic Framework Version

v7.x

Current Behavior

If a global mode is defined using

IonicModule.forRoot({ mode: 'ios' })

Mode on component level won't assign the defined mode, instead global mode is used.

public mode = isPlatform('ios') ? 'ios' : 'md'
<!-- works only if app is initialized with this component first -->
<ion-button [mode]="mode"><ion-button>

But if you use the mode value directly like

<!-- works always -->
<ion-button mode="md"><ion-button>

it works, even if global mode is set to ios.

https://github.com/ionic-team/ionic-framework/assets/26873275/d7041deb-6a44-4afe-b9fa-e72a7d30336c

In this example both tabs have assigned mode="md". Some of them directly (fixed mode), some with value binding (dynamic mode). As you can see here only the first initialized tab is working as expected with both fixed and dynamic mode as md. If you now switch the tab, the md mode is gone and ios mode is visible.

Expected Behavior

mode on component level should also work with value binding, even if global mode is set to ios

Steps to Reproduce

  1. open provided stackblitz
  2. "Listen Now" Tab is Visible, style for ion-button and ion-refresher is md ✅
  3. switch tab to "Radio"
  4. dynamic pull-to-refresh is iOS (should be md), dynamic button is iOS (should be md) ❌
  5. refresh the application while "Radio" Tab is active
  6. now both buttons are correct ✅
  7. switch tab to "Listen Now"
  8. fixed pull-to-refresh is md (correct), dynamic button is iOS (should be md) ❌

Code Reproduction URL

https://stackblitz.com/edit/angular-od4spf?file=src%2Fapp%2Fhome%2Fhome-page.component.html

Ionic Info

9.x

Additional Information

I think this bug only occurs in angular where the mode is defined in proxies.ts, since all components use changeDetection: ChangeDetectionStrategy.OnPush.

Since global mode is set using IonicModule.forRoot({ mode: 'ios' }), reassigning mode on component level doesn't trigger changeDetectionRef.detectChanges(), so the components keep the global defined mode.

related: #29137

amandaejohnston commented 3 months ago

Thank you for the issue. I can reproduce this, but need to dig into why exactly it's happening. If the issue is indeed with the change detection, we'll need to figure out the best path forward to resolving that.

sean-perkins commented 2 months ago

Hello @DwieDima this behavior is working as intended in Ionic Framework.

Virtual properties do not behave the same as our @Prop decorated properties. They aren't reactive and are not intended to be.

With Angular we have an additional limitation that the property value is not set immediately when the element is inserted into the DOM with the binding syntax ([mode]="foo"). It is initially undefined and 1-2 frames later is set to the value you provide. Since these properties are not reactive, they fallback to the detected mode instead of the specified mode when using the binding syntax.

Supporting reactive properties is something the team has discussed in the past, but has a lot of edge cases where reactive config options can cause issues in a users application. As a result we decided against supporting it.

Do you have a use case from an application where a dynamic mode is required that we could explore further?

In the past when faced with this type of behavior, I would render different instances of an element with the mode set statically to achieve the behavior around the limitations that Ionic Framework + Angular have.

DwieDima commented 1 month ago

Hi @sean-perkins, thanks a lot for the detailed explanation about how virtual properties work in Ionic.

I appreciate the suggestion to display components statically based on conditions to address the issues with reactive properties. This approach has resolved the problem.

I wasn't aware that virtual properties cannot handle dynamic values. Is this documented anywhere in Ionic's official documentation?