taiga-ui icon indicating copy to clipboard operation
taiga-ui copied to clipboard

🐞 - inside html tag <tui-dropdown></tui-dropdown> cannot be set positions in Server side rendering

Open sarvarkhuja opened this issue 1 year ago • 5 comments

Playground Link

No response

Description

In angular 17 started with SSR, followed instructions in https://taiga-ui.dev/ssr everything is working fine but dropdown cannot be opened image

Angular version

17

Taiga UI version

3.64.0

Which browsers have you used?

  • [X] Chrome
  • [X] Firefox
  • [ ] Safari
  • [ ] Edge

Which operating systems have you used?

  • [ ] macOS
  • [X] Windows
  • [ ] Linux
  • [ ] iOS
  • [ ] Android

sarvarkhuja avatar Feb 08 '24 11:02 sarvarkhuja

I'm not sure what issue you're describing. Sure you cannot open the dropdown in server side as it's impossible to calculate its position. Or do you mean that it is not opening after the client side kicks in? Could you provide a small reproduction?

waterplea avatar Feb 18 '24 07:02 waterplea

I've encountered the same issue where the dropdown doesn't function as expected after the client-side rendering kicks in.

Here's a minimal reproduction path to replicate the issue:

  • Create a new Angular app (v17) with Server Side Rendering (SSR).
  • Add Taiga UI to your project using the command ng add taiga-ui.
  • Add @ng-web-apis/universal to your project and follow the instructions provided here.
  • In your app component, add a select component as shown below:
<tui-root>

<main class="main">
  <div class="content">
    <form class="b-form">
      <tui-select tuiTextfieldSize="s" [formControl]="testValue">
        Character
        <input placeholder="Choose your hero" tuiTextfield />
        <tui-data-list-wrapper
          *tuiDataList
          [items]="items"
        ></tui-data-list-wrapper>
      </tui-select>
      <tui-select [formControl]="testValue">
        Character
        <select tuiSelect [items]="items"></select>
      </tui-select>
    </form>
  </div>
</main>

<router-outlet />

</tui-root>
import { NgDompurifySanitizer } from '@tinkoff/ng-dompurify';
import {
  TuiRootModule,
  TuiDialogModule,
  TuiAlertModule,
  TUI_SANITIZER,
  TuiDataListModule,
} from '@taiga-ui/core';
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TuiDataListWrapperModule, TuiSelectModule } from '@taiga-ui/kit';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RouterOutlet,
    TuiRootModule,
    TuiDialogModule,
    TuiAlertModule,
    FormsModule,
    ReactiveFormsModule,
    TuiSelectModule,
    TuiDataListModule,
    TuiDataListWrapperModule,
  ],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss',
  providers: [{ provide: TUI_SANITIZER, useClass: NgDompurifySanitizer }],
})
export class AppComponent {
  items = [
    'Luke Skywalker',
    'Leia Organa Solo',
    'Darth Vader',
    'Han Solo',
    'Obi-Wan Kenobi',
    'Yoda',
  ];

  testValue = new FormControl();
}

As a result, the dropdown doesn't work as expected. In a standard app, the tui-dropdown gets the correct attributes : Capture d’écran 2024-02-21 à 18 04 09

However, with SSR, the tui-dropdown doesn't have any attributes except for width and is set to visibility: hidden because it's missing top attribute.

Capture d’écran 2024-02-21 à 18 05 29

zerbusdetroy avatar Feb 21 '24 17:02 zerbusdetroy

taiga.zip here is source file to reproduce

sarvarkhuja avatar Feb 23 '24 00:02 sarvarkhuja

I've recently begun working with Taiga UI and ng-web-apis, and while I'm still familiarizing myself with these libraries, but I've noticed something strange.

In @ng-web-apis/universal, the UNIVERSAL_ANIMATION_FRAME of UNIVERSAL_PROVIDERS seems to mock ANIMATION_FRAME with NEVER.

import {ValueProvider} from '@angular/core';
import {ANIMATION_FRAME} from '@ng-web-apis/common';
import {NEVER} from 'rxjs';

export const UNIVERSAL_ANIMATION_FRAME: ValueProvider = {
    provide: ANIMATION_FRAME,
    useValue: NEVER,
};

This works fine for server-side rendering, but it doesn't seem to update when Angular hydrates the components. Shouldn't that be the case?

As a temporary solution, I've created a workaround using isPlatformBrowser:

import { Component, PLATFORM_ID } from '@angular/core';
import { UNIVERSAL_PROVIDERS } from '@ng-web-apis/universal';
import { NEVER, Observable } from 'rxjs';
...

export const NEW_ANIMATION_FRAME: Observable<DOMHighResTimeStamp> =
  new Observable<DOMHighResTimeStamp>((subscriber) => {
    let id = NaN;
    const callback = (timestamp: DOMHighResTimeStamp): void => {
      subscriber.next(timestamp);
      id = requestAnimationFrame(callback);
    };

    id = requestAnimationFrame(callback);

    return () => {
      cancelAnimationFrame(id);
    };
  });
  
  @Component({
  ...
  providers: [
    { provide: TUI_SANITIZER, useClass: NgDompurifySanitizer },
    UNIVERSAL_PROVIDERS,
    {
      provide: ANIMATION_FRAME,
      useFactory: (platformId: Object) =>
        isPlatformBrowser(platformId) ? NEW_ANIMATION_FRAME : NEVER,
      deps: [PLATFORM_ID],
    }
]

While this workaround allows me to see the dropdowns, I'm still encountering a height issue. But I'm hopeful that this information will aid in finding a more robust solution.

I'm unsure if this is the best place to report this issue. Should I also post it on the ng-web-apis repo?

zerbusdetroy avatar Feb 23 '24 14:02 zerbusdetroy

Thank you for investigation. It should change to the real animation frame after CSR kicks in. Seems like something has changed in more recent Angular versions with hydration. We will take a look. No need to post in other repo as it would still be us to dig in and fix it :)

waterplea avatar Feb 23 '24 17:02 waterplea

Thank you.

FYI, I've encountered issues with several other components and directives when using Angular 17 in conjunction with SSR. I suspect these issues might be related, which is why I've chosen not to create separate issue threads for each one.

Here's a list of the elements that are not functioning as expected:

  • Tabs: The underline feature is not working properly
  • tuiTextfieldCleaner: Clicking on the cross icon does not produce any effect
  • TuiLanguageSwitcher: I'm unable to inject it, which seems to be related to an issue with localStorage

Please inform me if it would be helpful for me to update this list with new elements as I discover them.

zerbusdetroy avatar Feb 26 '24 09:02 zerbusdetroy

I think we should first address the issue with the tokens not properly switching to the client side, it will most likely fix everything.

waterplea avatar Feb 26 '24 15:02 waterplea

I just realized that there is actually no bug. I was using the UNIVERSAL_PROVIDERS in both the server side and client side configurations because I changed it in the app.config.ts file instead of app.config.server.ts. Of course, it should only be placed on the server side! @sarvarkhuja did the same mistake in his code.

zerbusdetroy avatar Feb 29 '24 09:02 zerbusdetroy

Good to know, thanks :)

waterplea avatar Feb 29 '24 10:02 waterplea