Skip to content

Certain @let values can mimic internal data structure, breaking basic runtime logic #59504

Closed

Description

@csutorasr

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

core

Is this a regression?

Yes

Description

Before hydration and using signals my code worked. I refactored it to use signals and incremental hydration and it broke. The error message was not really meaningful: Cannot add property i18nNodes, object is not extensible

I use apollo-angular which freezes every object in its state and results. The state cannot be modified.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

Cannot add property i18nNodes, object is not extensible

The line where the error has happened is https://github.com/angular/angular/blob/4491704fbaec02db8eec5845ffbb200dbf2a0bf6/packages/core/src/hydration/i18n.ts#L660

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

NX 20.3.0
Angular 19.0.5
Angular SSR 19.0.6

Anything else?

Workaround:

Unfreeze the object using some hacky js code: const unfrozenObj = JSON.parse(JSON.stringify(obj));

Possible solutions:
Check before trying to set the values if the object is frozen using: Object.isExtensible(object).

  1. Throw an error that the state is not extensible and provide some information on the current component which has the error.
  2. Show a warning during development and provide some information on the current component which has the error. In production mode ignore it.
  3. Ignore setting the values without any warning.

I am very happy to code and make a PR on any of the solutions if the Angular team has decided.

Activity

  1. added
    needs reproductionThis issue needs a reproduction in order for the team to investigate further
    on Jan 13, 2025
  2. JeanMeche commented on Jan 13, 2025

    @JeanMeche
    Member

    Hi, could you please provide a stackblitz reproduction or a minimal project on a github repo. Thank you.

  3. added
    area: i18nIssues related to localization and internationalization
    on Jan 13, 2025
  4. added this to the Backlog milestone on Jan 13, 2025
  5. csutorasr commented on Jan 13, 2025

    @csutorasr
    Author

    I was not able to reproduce it, but in my lView list I can see my array that is frozen. I don't know how to put data there, I've been trying for more than an hour now. I have tried @let @if @for.

    In my original code I use a state key to store the data. That is not frozen. Then the library updates it with the frozen data. Then the hydration runs.

    Image

    EDIT: Here is the code that I made for reproduce the error which was not successfully reproduced: https://stackblitz.com/edit/stackblitz-starters-v39odskh?file=src%2Fapp%2Fapp.component.ts

    My HTML in the original code looks like this:

    @let list = elements();
    @if (unrelatedHttpCallWithOwnTiming()) {
      ...
      @if (anotherUnrelatedHttpCallWithOwnTiming()) {
        ...
        @if (relatedIfToList() && ...) {
          <div>
            <!-- If I move the let here the error goes away. -->
            @for (element of list; track element.id) {
              <subcomponent [element]="element" />
            }
          </div>
        }
        @if (!relatedIfToList() && ...) {
          <div>
            <!-- If I move the let here the error goes away. -->
            @for (element of list; track element.id) {
              <subcomponent [element]="element" />
            }
          </div>
        }
      }
    } 
    

    If I move the @lets next to the usage, the error goes away. So being deeply nested puts the array into the lView array.

  6. csutorasr commented on Jan 14, 2025

    @csutorasr
    Author

    Probably this optimization hides my let declaration in the stackblitz example.

  7. csutorasr commented on Jan 14, 2025

    @csutorasr
    Author

    I have found the bug. If the data in the @let is an array, or an object with a 6 key, then it finds it as a hydration data in this line:

    const hydrationInfo = lView[HYDRATION];

  8. csutorasr commented on Jan 14, 2025

    @csutorasr
    Author

    The cleanup is referenced here:

    cleanupI18nHydrationData(lView);

    So this check has the error:

    } else if (isLView(lView[i])) {

    The isLView function has the error:

    export function isLView(value: RNode | LView | LContainer | {} | null): value is LView {

    Every array that is stored in a LetDeclaration is considered as an LView.
    If the array has a 6th element, then the error happens.

    Possible solution:
    Mark LViews or LetDeclarations to be able to distinguish the two arrays.

    • Marking every LView would be a lot of memory.
    • Marking every LetDeclaration can be a solution, however it would make the code very unreadable.
    • Making the LetDeclaration data in the LView to appear as an object can be a solution that provides readable code.
  9. JeanMeche commented on Jan 14, 2025

    @JeanMeche
    Member

    A minimal repro :

    @Component({
      selector: 'app-root',
      template: `
        @let list = [0, {}, 0, 0, 0, 0, 1];
        <!-- index 6 is important for repro-->
        @if (true) { {{ list }}}
      `,
    })
    export class AppComponent {}
    

    For the Let Declaration to be mistaken for a LView, index:1 must be an object.

  10. added
    area: coreIssues related to the framework runtime
    and removed
    area: i18nIssues related to localization and internationalization
    needs reproductionThis issue needs a reproduction in order for the team to investigate further
    on Jan 14, 2025
  11. modified the milestones: Backlog, needsTriage on Jan 14, 2025
  12. 9 remaining items

  13. added
    P2The issue is important to a large percentage of users, with a workaround
    and removed
    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
    on Jan 16, 2025
  14. changed the title [-]Cannot add property i18nNodes, object is not extensible[/-] [+]Certain `@let` values can mimic internal data structure, breaking basic runtime logic[/+] on Jan 16, 2025
  15. AndrewKushnir commented on Jan 16, 2025

    @AndrewKushnir
    Contributor

    @alxhub also brought up an idea to use T data structures (TNodes) to identify the type of value stored in a given LView slot. This would require some refactoring around existing places where isLView and isLContainer are used to bring T data structure where needed.

  16. csutorasr commented on Jan 16, 2025

    @csutorasr
    Author

    @AndrewKushnir I agree on the first option, as it fits into the current code and has the less impact.

    Adding a new condition to every isLView check would impact the performance a lot. These checks happen during initial rendering.

    If you agree on the solution I am happy to create the PR. I have found a bug and I would love to fix Angular. 😍

  17. AndrewKushnir commented on Jan 17, 2025

    @AndrewKushnir
    Contributor

    If you agree on the solution I am happy to create the PR. I have found a bug and I would love to fix Angular. 😍

    @csutorasr thanks for your interest in contributing to Angular. We still need to figure out the best path forward and some of the options (using T data structures) might require complex refactoring. We'll discuss possible solutions with the team and will keep this thread updated.

  18. added a commit that references this issue on Jan 24, 2025
    41780c1
  19. griest024 commented on Oct 3, 2025

    @griest024

    In lieu of opening a new issue this is likely a related case when having a @let inside a content projection slot produces the error Error: NG0502: During serialization, Angular was unable to find an element in the DOM:

    <comp>
      @let var = val;
    </comp>

    Moving the let outside the slot fixes the issue.

  20. csutorasr commented on Oct 27, 2025

    @csutorasr
    Author

    @griest024 Is this all the code that you have? Or are there any other code in the projected html? If there's none, then no components can use the var because there's nothing in the scope of that element.

    I don't think it is related and I close this issue as it was resolved by the PR.

  21. csutorasr commented on Oct 27, 2025

    @csutorasr
    Author

    Oh, sorry it was not resolved by the PR that is mentioned, but further changes to the code had resolved it.

  22. angular-automatic-lock-bot commented on Nov 27, 2025

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  23. locked and limited conversation to collaborators on Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2The issue is important to a large percentage of users, with a workaroundarea: coreIssues related to the framework runtimebugstate: has PR

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions