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

Broken hydration after v17 -> v18 migration if i18n blocks are used #56074

Closed
zip-fa opened this issue May 24, 2024 · 7 comments
Closed

Broken hydration after v17 -> v18 migration if i18n blocks are used #56074

zip-fa opened this issue May 24, 2024 · 7 comments
Labels
area: core Issues related to the framework runtime area: i18n core: hydration P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent state: confirmed state: has PR
Milestone

Comments

@zip-fa
Copy link

zip-fa commented May 24, 2024

Which @angular/* package(s) are the source of the bug?

core

Is this a regression?

Yes

Description

Imagine this code:

<div>
  @if (currentBreakpoint() === 'mobile') {
    <div class="mobile" i18n="@@test">
      Mobile content
    </div>
  } @else {
    <div class="desktop" i18n="@@test2">
      Desktop content
    </div>
  }
</div>

currentBreakpoint signal can be desktop, mobile or tablet. On server it's always set to 'desktop'.
When i add this little line to my component, hydration breaks and 'desktop' div never cleans up on mobile devices after hydration:

  private readonly vcr = inject(ViewContainerRef);

Video:

2024-05-24.21.24.22.mov

Please provide a link to a minimal reproduction of the bug

https://github.com/zip-fa/ng18-broken-hydration

Please provide the exception or error you saw

No response

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 18.0.1
Node: 21.7.3 (Unsupported)
Package Manager: npm 10.5.0
OS: darwin arm64

Angular: 18.0.0
... animations, cdk, common, compiler, compiler-cli, core, forms
... localize, platform-browser, platform-browser-dynamic
... platform-server, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1800.1
@angular-devkit/build-angular   18.0.1
@angular-devkit/core            18.0.1
@angular-devkit/schematics      18.0.1
@angular/cli                    18.0.1
@angular/ssr                    18.0.1
@schematics/angular             18.0.1
rxjs                            7.8.1
typescript                      5.4.5
zone.js                         0.14.6
    
Warning: The current version of Node (21.7.3) is not supported by Angular.

Anything else?

No response

@JeanMeche
Copy link
Member

You repro doesn't exibit the issue you are describing. Can you double check ?

Did you forgot the withI18nSupport() initially ? (without it hydration break).

@JeanMeche JeanMeche added the needs reproduction This issue needs a reproduction in order for the team to investigate further label May 24, 2024
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented May 24, 2024

@zip-fa thanks for providing a repro! As @JeanMeche mentioned, the problem doesn't seem to exist in the code as is, but when I remove withI18nSupport(), I see the following error:

Error: ASSERTION ERROR: Unable to retrieve hydration info from the
TransferState. [Expected=> null != undefined <=Actual]

Ideally, when i18n blocks are present, but there is no withI18nSupport(), hydration should work as it used to work before: components with i18n blocks should skip hydration. We should also consider producing a console warning in a browser for such cases and suggest adding withI18nSupport() call.

Looking at the HTML output, this is likely a culprit:

<app-test1 _nghost-ng-c1108691119="" ngskiphydration="" ngh="null|0">

i.e. the ngh attribute value contains null, which is likely unexpected and we should look at the serialization code, which appends the ngh attribute. I think the fix would be to avoid appending ngh for those cases (i.e. opt a component out of hydration as it happens prior to v18).

cc @devknoll

@AndrewKushnir AndrewKushnir added area: i18n state: confirmed P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent core: hydration and removed needs reproduction This issue needs a reproduction in order for the team to investigate further labels May 24, 2024
@ngbot ngbot bot added this to the Backlog milestone May 24, 2024
@AndrewKushnir AndrewKushnir added the area: core Issues related to the framework runtime label May 24, 2024
@AndrewKushnir AndrewKushnir changed the title Broken hydration after v17 -> v18 migration Broken hydration after v17 -> v18 migration if i18n blocks are used May 24, 2024
@AndrewKushnir
Copy link
Contributor

AndrewKushnir commented May 24, 2024

More info after some additional investigation: here is a unit test (can be added to the packages/platform-server/test/hydration_spec.ts file) that reproduces the issue:

        it('should append skip hydration flag if component uses i18n blocks and no `withI18nSupport()` call present', async () => {
          @Component({
            standalone: true,
            selector: 'app',
            template: '<div i18n>Hi!</div>',
          })
          class SimpleComponent {
            vcr = inject(ViewContainerRef);  // <-- this line triggers the issue 
          }

          const hydrationFeatures = [] as unknown as HydrationFeature<any>[];
          const html = await ssr(SimpleComponent, {hydrationFeatures});
          const ssrContents = getAppContents(html);
          expect(ssrContents).toContain('<app ngh');
        });

The fix would likely be somewhere in the annotateLContainerForHydration function (code), which would need to account for a case when i18n blocks are present, but withI18nSupport() function is not invoked (we have similar checks here).

@zip-fa
Copy link
Author

zip-fa commented May 25, 2024

image image image

I am 100% sure that 'desktop' block is not removed on my setup - it stays in the DOM tree after initial page render (on first load) while it must be removed.
Tested on safari and chrome on mac os.

@zip-fa
Copy link
Author

zip-fa commented May 25, 2024

Added slower video. When screen blinks, i just refresh the page (cmd+r):

2024-05-25.11.03.12.mov

@if (currentBreakpoint() === 'mobile') successfully becomes true on 'mobile screens', but @else block is not removed from DOM tree only after initial page render when hydration active

@JeanMeche
Copy link
Member

This is due to the error thrown during hydration

AndrewKushnir added a commit to AndrewKushnir/angular that referenced this issue May 30, 2024
…t use i18n blocks

This commit updates hydration serialization logic to handle a case when the `withI18nSupport()` call is not present for an application that has a component that uses i18n blocks. Note: the issue is only reproducible for components that also inject `ViewContainerRef`, since it triggers a special serialization code path.

Resolves angular#56074.
@AndrewKushnir
Copy link
Contributor

@zip-fa FYI, I've created PR #56175 with the fix for the issue described in #56074 (comment). The PR will go through the regular review and testing phases and will be included into one of the next 18.0.x releases. Thank you.

thePunderWoman pushed a commit that referenced this issue May 30, 2024
…t use i18n blocks (#56175)

This commit updates hydration serialization logic to handle a case when the `withI18nSupport()` call is not present for an application that has a component that uses i18n blocks. Note: the issue is only reproducible for components that also inject `ViewContainerRef`, since it triggers a special serialization code path.

Resolves #56074.

PR Close #56175
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the framework runtime area: i18n core: hydration P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent state: confirmed state: has PR
Projects
None yet
4 participants
@JeanMeche @AndrewKushnir @zip-fa and others