taiga-ui
taiga-ui copied to clipboard
🐞 - inside html tag <tui-dropdown></tui-dropdown> cannot be set positions in Server side rendering
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
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
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?
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 :
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.
taiga.zip here is source file to reproduce
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?
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 :)
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.
I think we should first address the issue with the tokens not properly switching to the client side, it will most likely fix everything.
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.
Good to know, thanks :)