ngxsmk-datepicker
Version:
<!-- SEO Keywords: Angular DatePicker, Angular Date Range Picker, Lightweight Calendar Component, Angular Signals DatePicker, SSR Ready DatePicker, Zoneless Angular, A11y DatePicker, Mobile-Friendly DatePicker, Ionic DatePicker Meta Description: The
427 lines (335 loc) • 11.3 kB
Markdown
# Server-Side Rendering (SSR) Example
**Last updated:** March 21, 2026 · **Current stable:** v2.2.8
Complete example demonstrating ngxsmk-datepicker with Angular Universal (SSR).
## Overview
ngxsmk-datepicker is fully compatible with Angular Universal and server-side rendering. All browser APIs are properly guarded with platform checks, ensuring the component works seamlessly in both server and browser environments.
> **Note**: For setup instructions and best practices, see the [SSR Guide](./ssr.md).
## Basic SSR Setup
### 1. Install Angular Universal
```bash
ng add /express-engine
```
### 2. Configure App for SSR
```typescript
// app.config.ts
import { ApplicationConfig, PLATFORM_ID } from '@angular/core';
import { provideRouter } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
import { provideDatepickerConfig } from 'ngxsmk-datepicker';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideDatepickerConfig({
locale: 'en-US', // Explicitly set locale for SSR consistency
weekStart: 1,
minuteInterval: 15
})
]
};
```
### 3. Component Example
```typescript
// datepicker-demo.component.ts
import { Component, PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
export class DatepickerDemoComponent {
private platformId = inject(PLATFORM_ID);
selectedDate: Date | null = null;
// Use explicit locale for SSR consistency
locale = isPlatformBrowser(this.platformId)
? navigator.language || 'en-US'
: 'en-US';
onDateChange(date: Date | null): void {
this.selectedDate = date;
}
}
```
## SSR-Specific Considerations
### 1. Locale Detection
Always provide an explicit locale for SSR consistency:
```typescript
// ❌ Bad - relies on browser API
locale = navigator.language;
// ✅ Good - platform-checked
locale = isPlatformBrowser(this.platformId)
? navigator.language || 'en-US'
: 'en-US';
// ✅ Better - use service or config
locale = this.localeService.getLocale(); // Returns 'en-US' on server
```
### 2. Date Initialization
Dates work the same on server and client:
```typescript
// ✅ Safe - Date works on both server and client
const today = new Date();
const minDate = new Date(2025, 0, 1); // January 1, 2025
```
### 3. Value Hydration
The component handles value hydration automatically:
```typescript
export class MyComponent {
// Value from server API
serverDate: Date | null = null;
ngOnInit(): void {
// Fetch from API
this.apiService.getDate().subscribe(date => {
this.serverDate = new Date(date); // Works on both server and client
});
}
}
```
## Complete SSR Example
### Server-Side Component
```typescript
// server.component.ts
import { Component, OnInit, PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
import { DateService } from './date.service';
export class ServerDemoComponent implements OnInit {
private platformId = inject(PLATFORM_ID);
private dateService = inject(DateService);
locale = 'en-US';
initialDate: Date | null = null;
initialRange: { start: Date; end: Date } | null = null;
singleDate: Date | null = null;
dateRange: { start: Date; end: Date } | null = null;
ngOnInit(): void {
// Set locale based on platform
if (isPlatformBrowser(this.platformId)) {
this.locale = navigator.language || 'en-US';
}
// Load initial data (works on both server and client)
this.loadInitialData();
}
private loadInitialData(): void {
// Simulate server-side data loading
this.dateService.getInitialDate().subscribe(date => {
this.initialDate = date ? new Date(date) : null;
});
this.dateService.getInitialRange().subscribe(range => {
if (range) {
this.initialRange = {
start: new Date(range.start),
end: new Date(range.end)
};
}
});
}
onSingleDateChange(date: Date | null): void {
this.singleDate = date;
// Save to server
if (date) {
this.dateService.saveDate(date).subscribe();
}
}
onRangeChange(range: { start: Date; end: Date } | null): void {
this.dateRange = range;
// Save to server
if (range) {
this.dateService.saveRange(range).subscribe();
}
}
}
```
### Service Example
```typescript
// date.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export class DateService {
constructor(private http: HttpClient) {}
getInitialDate(): Observable<string | null> {
// Works on both server and client
return this.http.get<string | null>('/api/initial-date');
}
getInitialRange(): Observable<{ start: string; end: string } | null> {
return this.http.get<{ start: string; end: string } | null>('/api/initial-range');
}
saveDate(date: Date): Observable<void> {
return this.http.post<void>('/api/date', { date: date.toISOString() });
}
saveRange(range: { start: Date; end: Date }): Observable<void> {
return this.http.post<void>('/api/range', {
start: range.start.toISOString(),
end: range.end.toISOString()
});
}
}
```
## Hydration Notes
### Automatic Hydration
The component automatically handles hydration when values are set:
```typescript
// Value set on server
component.value = new Date('2025-01-15');
// Component automatically:
// 1. Initializes the selected date
// 2. Generates the calendar
// 3. Updates the display value
// 4. Works seamlessly after hydration
```
### Manual Hydration
If you need to manually trigger hydration:
```typescript
export class MyComponent implements AfterViewInit {
datepicker!: NgxsmkDatepickerComponent;
serverDate: Date | null = null;
ngAfterViewInit(): void {
// After view init, ensure component is hydrated
if (this.serverDate) {
// Component handles this automatically, but you can force update if needed
this.datepicker.ngOnChanges({
value: new SimpleChange(null, this.serverDate, true)
});
}
}
}
```
## Common SSR Issues & Solutions
### Issue 1: Locale Not Set
**Problem**: Component uses browser locale on server, causing inconsistencies.
**Solution**: Always provide explicit locale:
```typescript
// ✅ Correct
<ngxsmk-datepicker [locale]="'en-US'"></ngxsmk-datepicker>
// Or use platform check
locale = isPlatformBrowser(this.platformId)
? navigator.language
: 'en-US';
```
### Issue 2: Date Formatting Differences
**Problem**: Date formatting may differ between server and client.
**Solution**: Use consistent locale and timezone:
```typescript
// ✅ Correct
provideDatepickerConfig({
locale: 'en-US',
timezone: 'UTC' // Use UTC for consistency
})
```
### Issue 3: Window/Document Access
**Problem**: Component tries to access window/document on server.
**Solution**: Component already handles this with platform checks. No action needed.
## Testing SSR
### Unit Tests
```typescript
// datepicker-ssr.spec.ts
import { TestBed } from '@angular/core/testing';
import { PLATFORM_ID } from '@angular/core';
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
describe('SSR Compatibility', () => {
it('should work on server platform', () => {
TestBed.configureTestingModule({
imports: [NgxsmkDatepickerComponent],
providers: [
{ provide: PLATFORM_ID, useValue: 'server' }
]
});
const fixture = TestBed.createComponent(NgxsmkDatepickerComponent);
expect(() => fixture.detectChanges()).not.toThrow();
});
});
```
### E2E Tests
```typescript
// e2e/ssr.spec.ts
import { test, expect } from '@playwright/test';
test('datepicker works after SSR hydration', async ({ page }) => {
await page.goto('/');
// Wait for hydration
await page.waitForSelector('ngxsmk-datepicker');
// Click datepicker
await page.click('.ngxsmk-input-group');
// Select a date
await page.click('.ngxsmk-day-cell:has-text("15")');
// Verify value is set
const value = await page.inputValue('.ngxsmk-display-input');
expect(value).toBeTruthy();
});
```
## Best Practices
1. **Always Set Locale**: Provide explicit locale for SSR consistency
2. **Use Platform Checks**: Check `isPlatformBrowser` for browser-only code
3. **Test Both Platforms**: Test on both server and client
4. **Use UTC for Consistency**: Consider using UTC timezone for server/client consistency
5. **Handle Hydration**: Let the component handle hydration automatically
## Additional Resources
- [Angular Universal Guide](https://angular.io/guide/universal)
- [SSR Best Practices](https://angular.io/guide/universal#best-practices)
- [Platform Detection](https://angular.io/api/common/isPlatformBrowser)