ngxsmk-datepicker 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +942 -922
- package/docs/API.md +2423 -2413
- package/docs/COMPATIBILITY.md +471 -471
- package/docs/INTEGRATION.md +703 -703
- package/docs/IONIC_INTEGRATION.md +228 -228
- package/docs/LOCALE-GUIDE.md +300 -300
- package/docs/PLUGIN-ARCHITECTURE.md +930 -930
- package/docs/SSR-EXAMPLE.md +427 -427
- package/docs/THEME-TOKENS.md +324 -324
- package/docs/TIMEZONE.md +307 -307
- package/docs/extension-points.md +419 -419
- package/docs/signal-forms.md +600 -600
- package/docs/signals.md +266 -266
- package/docs/ssr.md +305 -305
- package/package.json +106 -104
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/schema.json +14 -0
- package/schematics/tsconfig.json +17 -0
- package/CHANGELOG.md +0 -1246
- package/LICENSE +0 -21
- package/MIGRATION.md +0 -1794
- package/docs/FEATURE_SCOPING.md +0 -60
- package/docs/IONIC_TESTING.md +0 -148
- package/docs/REFACTOR_PLAN.md +0 -38
- package/docs/SEO.md +0 -214
- package/fesm2022/ngxsmk-datepicker.mjs +0 -14693
- package/types/ngxsmk-datepicker.d.ts +0 -2489
package/docs/SSR-EXAMPLE.md
CHANGED
|
@@ -1,427 +1,427 @@
|
|
|
1
|
-
# Server-Side Rendering (SSR) Example
|
|
2
|
-
|
|
3
|
-
**Last updated:**
|
|
4
|
-
|
|
5
|
-
Complete example demonstrating ngxsmk-datepicker with Angular Universal (SSR).
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
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.
|
|
10
|
-
|
|
11
|
-
> **Note**: For setup instructions and best practices, see the [SSR Guide](./ssr.md).
|
|
12
|
-
|
|
13
|
-
## Basic SSR Setup
|
|
14
|
-
|
|
15
|
-
### 1. Install Angular Universal
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
ng add @nguniversal/express-engine
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### 2. Configure App for SSR
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
// app.config.ts
|
|
25
|
-
import { ApplicationConfig, PLATFORM_ID } from '@angular/core';
|
|
26
|
-
import { provideRouter } from '@angular/router';
|
|
27
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
28
|
-
import { provideDatepickerConfig } from 'ngxsmk-datepicker';
|
|
29
|
-
|
|
30
|
-
export const appConfig: ApplicationConfig = {
|
|
31
|
-
providers: [
|
|
32
|
-
provideRouter(routes),
|
|
33
|
-
provideDatepickerConfig({
|
|
34
|
-
locale: 'en-US', // Explicitly set locale for SSR consistency
|
|
35
|
-
weekStart: 1,
|
|
36
|
-
minuteInterval: 15
|
|
37
|
-
})
|
|
38
|
-
]
|
|
39
|
-
};
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 3. Component Example
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// datepicker-demo.component.ts
|
|
46
|
-
import { Component, PLATFORM_ID, inject } from '@angular/core';
|
|
47
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
48
|
-
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
49
|
-
|
|
50
|
-
@Component({
|
|
51
|
-
selector: 'app-datepicker-demo',
|
|
52
|
-
standalone: true,
|
|
53
|
-
imports: [NgxsmkDatepickerComponent],
|
|
54
|
-
template: `
|
|
55
|
-
<div class="datepicker-container">
|
|
56
|
-
<h2>Date Selection</h2>
|
|
57
|
-
<ngxsmk-datepicker
|
|
58
|
-
mode="single"
|
|
59
|
-
[locale]="locale"
|
|
60
|
-
placeholder="Select a date"
|
|
61
|
-
(valueChange)="onDateChange($event)">
|
|
62
|
-
</ngxsmk-datepicker>
|
|
63
|
-
|
|
64
|
-
@if (selectedDate) {
|
|
65
|
-
<p>Selected: {{ selectedDate | date:'fullDate' }}</p>
|
|
66
|
-
}
|
|
67
|
-
</div>
|
|
68
|
-
`
|
|
69
|
-
})
|
|
70
|
-
export class DatepickerDemoComponent {
|
|
71
|
-
private platformId = inject(PLATFORM_ID);
|
|
72
|
-
|
|
73
|
-
selectedDate: Date | null = null;
|
|
74
|
-
|
|
75
|
-
// Use explicit locale for SSR consistency
|
|
76
|
-
locale = isPlatformBrowser(this.platformId)
|
|
77
|
-
? navigator.language || 'en-US'
|
|
78
|
-
: 'en-US';
|
|
79
|
-
|
|
80
|
-
onDateChange(date: Date | null): void {
|
|
81
|
-
this.selectedDate = date;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## SSR-Specific Considerations
|
|
87
|
-
|
|
88
|
-
### 1. Locale Detection
|
|
89
|
-
|
|
90
|
-
Always provide an explicit locale for SSR consistency:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// ❌ Bad - relies on browser API
|
|
94
|
-
locale = navigator.language;
|
|
95
|
-
|
|
96
|
-
// ✅ Good - platform-checked
|
|
97
|
-
locale = isPlatformBrowser(this.platformId)
|
|
98
|
-
? navigator.language || 'en-US'
|
|
99
|
-
: 'en-US';
|
|
100
|
-
|
|
101
|
-
// ✅ Better - use service or config
|
|
102
|
-
locale = this.localeService.getLocale(); // Returns 'en-US' on server
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### 2. Date Initialization
|
|
106
|
-
|
|
107
|
-
Dates work the same on server and client:
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// ✅ Safe - Date works on both server and client
|
|
111
|
-
const today = new Date();
|
|
112
|
-
const minDate = new Date(2025, 0, 1); // January 1, 2025
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### 3. Value Hydration
|
|
116
|
-
|
|
117
|
-
The component handles value hydration automatically:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
@Component({
|
|
121
|
-
template: `
|
|
122
|
-
<ngxsmk-datepicker
|
|
123
|
-
[value]="serverDate"
|
|
124
|
-
(valueChange)="onDateChange($event)">
|
|
125
|
-
</ngxsmk-datepicker>
|
|
126
|
-
`
|
|
127
|
-
})
|
|
128
|
-
export class MyComponent {
|
|
129
|
-
// Value from server API
|
|
130
|
-
serverDate: Date | null = null;
|
|
131
|
-
|
|
132
|
-
ngOnInit(): void {
|
|
133
|
-
// Fetch from API
|
|
134
|
-
this.apiService.getDate().subscribe(date => {
|
|
135
|
-
this.serverDate = new Date(date); // Works on both server and client
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Complete SSR Example
|
|
142
|
-
|
|
143
|
-
### Server-Side Component
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
// server.component.ts
|
|
147
|
-
import { Component, OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
148
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
149
|
-
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
150
|
-
import { DateService } from './date.service';
|
|
151
|
-
|
|
152
|
-
@Component({
|
|
153
|
-
selector: 'app-server-demo',
|
|
154
|
-
standalone: true,
|
|
155
|
-
imports: [NgxsmkDatepickerComponent],
|
|
156
|
-
template: `
|
|
157
|
-
<div class="server-demo">
|
|
158
|
-
<h1>SSR Datepicker Demo</h1>
|
|
159
|
-
|
|
160
|
-
<section>
|
|
161
|
-
<h2>Single Date Selection</h2>
|
|
162
|
-
<ngxsmk-datepicker
|
|
163
|
-
mode="single"
|
|
164
|
-
[locale]="locale"
|
|
165
|
-
[value]="initialDate"
|
|
166
|
-
placeholder="Select a date"
|
|
167
|
-
(valueChange)="onSingleDateChange($event)">
|
|
168
|
-
</ngxsmk-datepicker>
|
|
169
|
-
<p *ngIf="singleDate">Selected: {{ singleDate | date:'fullDate' }}</p>
|
|
170
|
-
</section>
|
|
171
|
-
|
|
172
|
-
<section>
|
|
173
|
-
<h2>Date Range Selection</h2>
|
|
174
|
-
<ngxsmk-datepicker
|
|
175
|
-
mode="range"
|
|
176
|
-
[locale]="locale"
|
|
177
|
-
[value]="initialRange"
|
|
178
|
-
placeholder="Select date range"
|
|
179
|
-
(valueChange)="onRangeChange($event)">
|
|
180
|
-
</ngxsmk-datepicker>
|
|
181
|
-
<p *ngIf="dateRange">
|
|
182
|
-
Range: {{ dateRange.start | date:'shortDate' }} -
|
|
183
|
-
{{ dateRange.end | date:'shortDate' }}
|
|
184
|
-
</p>
|
|
185
|
-
</section>
|
|
186
|
-
</div>
|
|
187
|
-
`
|
|
188
|
-
})
|
|
189
|
-
export class ServerDemoComponent implements OnInit {
|
|
190
|
-
private platformId = inject(PLATFORM_ID);
|
|
191
|
-
private dateService = inject(DateService);
|
|
192
|
-
|
|
193
|
-
locale = 'en-US';
|
|
194
|
-
initialDate: Date | null = null;
|
|
195
|
-
initialRange: { start: Date; end: Date } | null = null;
|
|
196
|
-
singleDate: Date | null = null;
|
|
197
|
-
dateRange: { start: Date; end: Date } | null = null;
|
|
198
|
-
|
|
199
|
-
ngOnInit(): void {
|
|
200
|
-
// Set locale based on platform
|
|
201
|
-
if (isPlatformBrowser(this.platformId)) {
|
|
202
|
-
this.locale = navigator.language || 'en-US';
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Load initial data (works on both server and client)
|
|
206
|
-
this.loadInitialData();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private loadInitialData(): void {
|
|
210
|
-
// Simulate server-side data loading
|
|
211
|
-
this.dateService.getInitialDate().subscribe(date => {
|
|
212
|
-
this.initialDate = date ? new Date(date) : null;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
this.dateService.getInitialRange().subscribe(range => {
|
|
216
|
-
if (range) {
|
|
217
|
-
this.initialRange = {
|
|
218
|
-
start: new Date(range.start),
|
|
219
|
-
end: new Date(range.end)
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
onSingleDateChange(date: Date | null): void {
|
|
226
|
-
this.singleDate = date;
|
|
227
|
-
// Save to server
|
|
228
|
-
if (date) {
|
|
229
|
-
this.dateService.saveDate(date).subscribe();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
onRangeChange(range: { start: Date; end: Date } | null): void {
|
|
234
|
-
this.dateRange = range;
|
|
235
|
-
// Save to server
|
|
236
|
-
if (range) {
|
|
237
|
-
this.dateService.saveRange(range).subscribe();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Service Example
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
// date.service.ts
|
|
247
|
-
import { Injectable } from '@angular/core';
|
|
248
|
-
import { HttpClient } from '@angular/common/http';
|
|
249
|
-
import { Observable } from 'rxjs';
|
|
250
|
-
|
|
251
|
-
@Injectable({
|
|
252
|
-
providedIn: 'root'
|
|
253
|
-
})
|
|
254
|
-
export class DateService {
|
|
255
|
-
constructor(private http: HttpClient) {}
|
|
256
|
-
|
|
257
|
-
getInitialDate(): Observable<string | null> {
|
|
258
|
-
// Works on both server and client
|
|
259
|
-
return this.http.get<string | null>('/api/initial-date');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
getInitialRange(): Observable<{ start: string; end: string } | null> {
|
|
263
|
-
return this.http.get<{ start: string; end: string } | null>('/api/initial-range');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
saveDate(date: Date): Observable<void> {
|
|
267
|
-
return this.http.post<void>('/api/date', { date: date.toISOString() });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
saveRange(range: { start: Date; end: Date }): Observable<void> {
|
|
271
|
-
return this.http.post<void>('/api/range', {
|
|
272
|
-
start: range.start.toISOString(),
|
|
273
|
-
end: range.end.toISOString()
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Hydration Notes
|
|
280
|
-
|
|
281
|
-
### Automatic Hydration
|
|
282
|
-
|
|
283
|
-
The component automatically handles hydration when values are set:
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
// Value set on server
|
|
287
|
-
component.value = new Date('2025-01-15');
|
|
288
|
-
|
|
289
|
-
// Component automatically:
|
|
290
|
-
// 1. Initializes the selected date
|
|
291
|
-
// 2. Generates the calendar
|
|
292
|
-
// 3. Updates the display value
|
|
293
|
-
// 4. Works seamlessly after hydration
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Manual Hydration
|
|
297
|
-
|
|
298
|
-
If you need to manually trigger hydration:
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
@Component({
|
|
302
|
-
template: `
|
|
303
|
-
<ngxsmk-datepicker
|
|
304
|
-
#datepicker
|
|
305
|
-
[value]="serverDate">
|
|
306
|
-
</ngxsmk-datepicker>
|
|
307
|
-
`
|
|
308
|
-
})
|
|
309
|
-
export class MyComponent implements AfterViewInit {
|
|
310
|
-
@ViewChild('datepicker') datepicker!: NgxsmkDatepickerComponent;
|
|
311
|
-
serverDate: Date | null = null;
|
|
312
|
-
|
|
313
|
-
ngAfterViewInit(): void {
|
|
314
|
-
// After view init, ensure component is hydrated
|
|
315
|
-
if (this.serverDate) {
|
|
316
|
-
// Component handles this automatically, but you can force update if needed
|
|
317
|
-
this.datepicker.ngOnChanges({
|
|
318
|
-
value: new SimpleChange(null, this.serverDate, true)
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
## Common SSR Issues & Solutions
|
|
326
|
-
|
|
327
|
-
### Issue 1: Locale Not Set
|
|
328
|
-
|
|
329
|
-
**Problem**: Component uses browser locale on server, causing inconsistencies.
|
|
330
|
-
|
|
331
|
-
**Solution**: Always provide explicit locale:
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
// ✅ Correct
|
|
335
|
-
<ngxsmk-datepicker [locale]="'en-US'"></ngxsmk-datepicker>
|
|
336
|
-
|
|
337
|
-
// Or use platform check
|
|
338
|
-
locale = isPlatformBrowser(this.platformId)
|
|
339
|
-
? navigator.language
|
|
340
|
-
: 'en-US';
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Issue 2: Date Formatting Differences
|
|
344
|
-
|
|
345
|
-
**Problem**: Date formatting may differ between server and client.
|
|
346
|
-
|
|
347
|
-
**Solution**: Use consistent locale and timezone:
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
// ✅ Correct
|
|
351
|
-
provideDatepickerConfig({
|
|
352
|
-
locale: 'en-US',
|
|
353
|
-
timezone: 'UTC' // Use UTC for consistency
|
|
354
|
-
})
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Issue 3: Window/Document Access
|
|
358
|
-
|
|
359
|
-
**Problem**: Component tries to access window/document on server.
|
|
360
|
-
|
|
361
|
-
**Solution**: Component already handles this with platform checks. No action needed.
|
|
362
|
-
|
|
363
|
-
## Testing SSR
|
|
364
|
-
|
|
365
|
-
### Unit Tests
|
|
366
|
-
|
|
367
|
-
```typescript
|
|
368
|
-
// datepicker-ssr.spec.ts
|
|
369
|
-
import { TestBed } from '@angular/core/testing';
|
|
370
|
-
import { PLATFORM_ID } from '@angular/core';
|
|
371
|
-
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
372
|
-
|
|
373
|
-
describe('SSR Compatibility', () => {
|
|
374
|
-
it('should work on server platform', () => {
|
|
375
|
-
TestBed.configureTestingModule({
|
|
376
|
-
imports: [NgxsmkDatepickerComponent],
|
|
377
|
-
providers: [
|
|
378
|
-
{ provide: PLATFORM_ID, useValue: 'server' }
|
|
379
|
-
]
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const fixture = TestBed.createComponent(NgxsmkDatepickerComponent);
|
|
383
|
-
expect(() => fixture.detectChanges()).not.toThrow();
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### E2E Tests
|
|
389
|
-
|
|
390
|
-
```typescript
|
|
391
|
-
// e2e/ssr.spec.ts
|
|
392
|
-
import { test, expect } from '@playwright/test';
|
|
393
|
-
|
|
394
|
-
test('datepicker works after SSR hydration', async ({ page }) => {
|
|
395
|
-
await page.goto('/');
|
|
396
|
-
|
|
397
|
-
// Wait for hydration
|
|
398
|
-
await page.waitForSelector('ngxsmk-datepicker');
|
|
399
|
-
|
|
400
|
-
// Click datepicker
|
|
401
|
-
await page.click('.ngxsmk-input-group');
|
|
402
|
-
|
|
403
|
-
// Select a date
|
|
404
|
-
await page.click('.ngxsmk-day-cell:has-text("15")');
|
|
405
|
-
|
|
406
|
-
// Verify value is set
|
|
407
|
-
const value = await page.inputValue('.ngxsmk-display-input');
|
|
408
|
-
expect(value).toBeTruthy();
|
|
409
|
-
});
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
## Best Practices
|
|
413
|
-
|
|
414
|
-
1. **Always Set Locale**: Provide explicit locale for SSR consistency
|
|
415
|
-
2. **Use Platform Checks**: Check `isPlatformBrowser` for browser-only code
|
|
416
|
-
3. **Test Both Platforms**: Test on both server and client
|
|
417
|
-
4. **Use UTC for Consistency**: Consider using UTC timezone for server/client consistency
|
|
418
|
-
5. **Handle Hydration**: Let the component handle hydration automatically
|
|
419
|
-
|
|
420
|
-
## Additional Resources
|
|
421
|
-
|
|
422
|
-
- [Angular Universal Guide](https://angular.io/guide/universal)
|
|
423
|
-
- [SSR Best Practices](https://angular.io/guide/universal#best-practices)
|
|
424
|
-
- [Platform Detection](https://angular.io/api/common/isPlatformBrowser)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1
|
+
# Server-Side Rendering (SSR) Example
|
|
2
|
+
|
|
3
|
+
**Last updated:** July 2, 2026 - **Current stable:** v2.4.0
|
|
4
|
+
|
|
5
|
+
Complete example demonstrating ngxsmk-datepicker with Angular Universal (SSR).
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
> **Note**: For setup instructions and best practices, see the [SSR Guide](./ssr.md).
|
|
12
|
+
|
|
13
|
+
## Basic SSR Setup
|
|
14
|
+
|
|
15
|
+
### 1. Install Angular Universal
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ng add @nguniversal/express-engine
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Configure App for SSR
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// app.config.ts
|
|
25
|
+
import { ApplicationConfig, PLATFORM_ID } from '@angular/core';
|
|
26
|
+
import { provideRouter } from '@angular/router';
|
|
27
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
28
|
+
import { provideDatepickerConfig } from 'ngxsmk-datepicker';
|
|
29
|
+
|
|
30
|
+
export const appConfig: ApplicationConfig = {
|
|
31
|
+
providers: [
|
|
32
|
+
provideRouter(routes),
|
|
33
|
+
provideDatepickerConfig({
|
|
34
|
+
locale: 'en-US', // Explicitly set locale for SSR consistency
|
|
35
|
+
weekStart: 1,
|
|
36
|
+
minuteInterval: 15
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Component Example
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// datepicker-demo.component.ts
|
|
46
|
+
import { Component, PLATFORM_ID, inject } from '@angular/core';
|
|
47
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
48
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
49
|
+
|
|
50
|
+
@Component({
|
|
51
|
+
selector: 'app-datepicker-demo',
|
|
52
|
+
standalone: true,
|
|
53
|
+
imports: [NgxsmkDatepickerComponent],
|
|
54
|
+
template: `
|
|
55
|
+
<div class="datepicker-container">
|
|
56
|
+
<h2>Date Selection</h2>
|
|
57
|
+
<ngxsmk-datepicker
|
|
58
|
+
mode="single"
|
|
59
|
+
[locale]="locale"
|
|
60
|
+
placeholder="Select a date"
|
|
61
|
+
(valueChange)="onDateChange($event)">
|
|
62
|
+
</ngxsmk-datepicker>
|
|
63
|
+
|
|
64
|
+
@if (selectedDate) {
|
|
65
|
+
<p>Selected: {{ selectedDate | date:'fullDate' }}</p>
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
`
|
|
69
|
+
})
|
|
70
|
+
export class DatepickerDemoComponent {
|
|
71
|
+
private platformId = inject(PLATFORM_ID);
|
|
72
|
+
|
|
73
|
+
selectedDate: Date | null = null;
|
|
74
|
+
|
|
75
|
+
// Use explicit locale for SSR consistency
|
|
76
|
+
locale = isPlatformBrowser(this.platformId)
|
|
77
|
+
? navigator.language || 'en-US'
|
|
78
|
+
: 'en-US';
|
|
79
|
+
|
|
80
|
+
onDateChange(date: Date | null): void {
|
|
81
|
+
this.selectedDate = date;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## SSR-Specific Considerations
|
|
87
|
+
|
|
88
|
+
### 1. Locale Detection
|
|
89
|
+
|
|
90
|
+
Always provide an explicit locale for SSR consistency:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// ❌ Bad - relies on browser API
|
|
94
|
+
locale = navigator.language;
|
|
95
|
+
|
|
96
|
+
// ✅ Good - platform-checked
|
|
97
|
+
locale = isPlatformBrowser(this.platformId)
|
|
98
|
+
? navigator.language || 'en-US'
|
|
99
|
+
: 'en-US';
|
|
100
|
+
|
|
101
|
+
// ✅ Better - use service or config
|
|
102
|
+
locale = this.localeService.getLocale(); // Returns 'en-US' on server
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 2. Date Initialization
|
|
106
|
+
|
|
107
|
+
Dates work the same on server and client:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// ✅ Safe - Date works on both server and client
|
|
111
|
+
const today = new Date();
|
|
112
|
+
const minDate = new Date(2025, 0, 1); // January 1, 2025
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Value Hydration
|
|
116
|
+
|
|
117
|
+
The component handles value hydration automatically:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
@Component({
|
|
121
|
+
template: `
|
|
122
|
+
<ngxsmk-datepicker
|
|
123
|
+
[value]="serverDate"
|
|
124
|
+
(valueChange)="onDateChange($event)">
|
|
125
|
+
</ngxsmk-datepicker>
|
|
126
|
+
`
|
|
127
|
+
})
|
|
128
|
+
export class MyComponent {
|
|
129
|
+
// Value from server API
|
|
130
|
+
serverDate: Date | null = null;
|
|
131
|
+
|
|
132
|
+
ngOnInit(): void {
|
|
133
|
+
// Fetch from API
|
|
134
|
+
this.apiService.getDate().subscribe(date => {
|
|
135
|
+
this.serverDate = new Date(date); // Works on both server and client
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Complete SSR Example
|
|
142
|
+
|
|
143
|
+
### Server-Side Component
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// server.component.ts
|
|
147
|
+
import { Component, OnInit, PLATFORM_ID, inject } from '@angular/core';
|
|
148
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
149
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
150
|
+
import { DateService } from './date.service';
|
|
151
|
+
|
|
152
|
+
@Component({
|
|
153
|
+
selector: 'app-server-demo',
|
|
154
|
+
standalone: true,
|
|
155
|
+
imports: [NgxsmkDatepickerComponent],
|
|
156
|
+
template: `
|
|
157
|
+
<div class="server-demo">
|
|
158
|
+
<h1>SSR Datepicker Demo</h1>
|
|
159
|
+
|
|
160
|
+
<section>
|
|
161
|
+
<h2>Single Date Selection</h2>
|
|
162
|
+
<ngxsmk-datepicker
|
|
163
|
+
mode="single"
|
|
164
|
+
[locale]="locale"
|
|
165
|
+
[value]="initialDate"
|
|
166
|
+
placeholder="Select a date"
|
|
167
|
+
(valueChange)="onSingleDateChange($event)">
|
|
168
|
+
</ngxsmk-datepicker>
|
|
169
|
+
<p *ngIf="singleDate">Selected: {{ singleDate | date:'fullDate' }}</p>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section>
|
|
173
|
+
<h2>Date Range Selection</h2>
|
|
174
|
+
<ngxsmk-datepicker
|
|
175
|
+
mode="range"
|
|
176
|
+
[locale]="locale"
|
|
177
|
+
[value]="initialRange"
|
|
178
|
+
placeholder="Select date range"
|
|
179
|
+
(valueChange)="onRangeChange($event)">
|
|
180
|
+
</ngxsmk-datepicker>
|
|
181
|
+
<p *ngIf="dateRange">
|
|
182
|
+
Range: {{ dateRange.start | date:'shortDate' }} -
|
|
183
|
+
{{ dateRange.end | date:'shortDate' }}
|
|
184
|
+
</p>
|
|
185
|
+
</section>
|
|
186
|
+
</div>
|
|
187
|
+
`
|
|
188
|
+
})
|
|
189
|
+
export class ServerDemoComponent implements OnInit {
|
|
190
|
+
private platformId = inject(PLATFORM_ID);
|
|
191
|
+
private dateService = inject(DateService);
|
|
192
|
+
|
|
193
|
+
locale = 'en-US';
|
|
194
|
+
initialDate: Date | null = null;
|
|
195
|
+
initialRange: { start: Date; end: Date } | null = null;
|
|
196
|
+
singleDate: Date | null = null;
|
|
197
|
+
dateRange: { start: Date; end: Date } | null = null;
|
|
198
|
+
|
|
199
|
+
ngOnInit(): void {
|
|
200
|
+
// Set locale based on platform
|
|
201
|
+
if (isPlatformBrowser(this.platformId)) {
|
|
202
|
+
this.locale = navigator.language || 'en-US';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Load initial data (works on both server and client)
|
|
206
|
+
this.loadInitialData();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private loadInitialData(): void {
|
|
210
|
+
// Simulate server-side data loading
|
|
211
|
+
this.dateService.getInitialDate().subscribe(date => {
|
|
212
|
+
this.initialDate = date ? new Date(date) : null;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
this.dateService.getInitialRange().subscribe(range => {
|
|
216
|
+
if (range) {
|
|
217
|
+
this.initialRange = {
|
|
218
|
+
start: new Date(range.start),
|
|
219
|
+
end: new Date(range.end)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
onSingleDateChange(date: Date | null): void {
|
|
226
|
+
this.singleDate = date;
|
|
227
|
+
// Save to server
|
|
228
|
+
if (date) {
|
|
229
|
+
this.dateService.saveDate(date).subscribe();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
onRangeChange(range: { start: Date; end: Date } | null): void {
|
|
234
|
+
this.dateRange = range;
|
|
235
|
+
// Save to server
|
|
236
|
+
if (range) {
|
|
237
|
+
this.dateService.saveRange(range).subscribe();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Service Example
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
// date.service.ts
|
|
247
|
+
import { Injectable } from '@angular/core';
|
|
248
|
+
import { HttpClient } from '@angular/common/http';
|
|
249
|
+
import { Observable } from 'rxjs';
|
|
250
|
+
|
|
251
|
+
@Injectable({
|
|
252
|
+
providedIn: 'root'
|
|
253
|
+
})
|
|
254
|
+
export class DateService {
|
|
255
|
+
constructor(private http: HttpClient) {}
|
|
256
|
+
|
|
257
|
+
getInitialDate(): Observable<string | null> {
|
|
258
|
+
// Works on both server and client
|
|
259
|
+
return this.http.get<string | null>('/api/initial-date');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getInitialRange(): Observable<{ start: string; end: string } | null> {
|
|
263
|
+
return this.http.get<{ start: string; end: string } | null>('/api/initial-range');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
saveDate(date: Date): Observable<void> {
|
|
267
|
+
return this.http.post<void>('/api/date', { date: date.toISOString() });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
saveRange(range: { start: Date; end: Date }): Observable<void> {
|
|
271
|
+
return this.http.post<void>('/api/range', {
|
|
272
|
+
start: range.start.toISOString(),
|
|
273
|
+
end: range.end.toISOString()
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Hydration Notes
|
|
280
|
+
|
|
281
|
+
### Automatic Hydration
|
|
282
|
+
|
|
283
|
+
The component automatically handles hydration when values are set:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// Value set on server
|
|
287
|
+
component.value = new Date('2025-01-15');
|
|
288
|
+
|
|
289
|
+
// Component automatically:
|
|
290
|
+
// 1. Initializes the selected date
|
|
291
|
+
// 2. Generates the calendar
|
|
292
|
+
// 3. Updates the display value
|
|
293
|
+
// 4. Works seamlessly after hydration
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Manual Hydration
|
|
297
|
+
|
|
298
|
+
If you need to manually trigger hydration:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
@Component({
|
|
302
|
+
template: `
|
|
303
|
+
<ngxsmk-datepicker
|
|
304
|
+
#datepicker
|
|
305
|
+
[value]="serverDate">
|
|
306
|
+
</ngxsmk-datepicker>
|
|
307
|
+
`
|
|
308
|
+
})
|
|
309
|
+
export class MyComponent implements AfterViewInit {
|
|
310
|
+
@ViewChild('datepicker') datepicker!: NgxsmkDatepickerComponent;
|
|
311
|
+
serverDate: Date | null = null;
|
|
312
|
+
|
|
313
|
+
ngAfterViewInit(): void {
|
|
314
|
+
// After view init, ensure component is hydrated
|
|
315
|
+
if (this.serverDate) {
|
|
316
|
+
// Component handles this automatically, but you can force update if needed
|
|
317
|
+
this.datepicker.ngOnChanges({
|
|
318
|
+
value: new SimpleChange(null, this.serverDate, true)
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Common SSR Issues & Solutions
|
|
326
|
+
|
|
327
|
+
### Issue 1: Locale Not Set
|
|
328
|
+
|
|
329
|
+
**Problem**: Component uses browser locale on server, causing inconsistencies.
|
|
330
|
+
|
|
331
|
+
**Solution**: Always provide explicit locale:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// ✅ Correct
|
|
335
|
+
<ngxsmk-datepicker [locale]="'en-US'"></ngxsmk-datepicker>
|
|
336
|
+
|
|
337
|
+
// Or use platform check
|
|
338
|
+
locale = isPlatformBrowser(this.platformId)
|
|
339
|
+
? navigator.language
|
|
340
|
+
: 'en-US';
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Issue 2: Date Formatting Differences
|
|
344
|
+
|
|
345
|
+
**Problem**: Date formatting may differ between server and client.
|
|
346
|
+
|
|
347
|
+
**Solution**: Use consistent locale and timezone:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// ✅ Correct
|
|
351
|
+
provideDatepickerConfig({
|
|
352
|
+
locale: 'en-US',
|
|
353
|
+
timezone: 'UTC' // Use UTC for consistency
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Issue 3: Window/Document Access
|
|
358
|
+
|
|
359
|
+
**Problem**: Component tries to access window/document on server.
|
|
360
|
+
|
|
361
|
+
**Solution**: Component already handles this with platform checks. No action needed.
|
|
362
|
+
|
|
363
|
+
## Testing SSR
|
|
364
|
+
|
|
365
|
+
### Unit Tests
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
// datepicker-ssr.spec.ts
|
|
369
|
+
import { TestBed } from '@angular/core/testing';
|
|
370
|
+
import { PLATFORM_ID } from '@angular/core';
|
|
371
|
+
import { NgxsmkDatepickerComponent } from 'ngxsmk-datepicker';
|
|
372
|
+
|
|
373
|
+
describe('SSR Compatibility', () => {
|
|
374
|
+
it('should work on server platform', () => {
|
|
375
|
+
TestBed.configureTestingModule({
|
|
376
|
+
imports: [NgxsmkDatepickerComponent],
|
|
377
|
+
providers: [
|
|
378
|
+
{ provide: PLATFORM_ID, useValue: 'server' }
|
|
379
|
+
]
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const fixture = TestBed.createComponent(NgxsmkDatepickerComponent);
|
|
383
|
+
expect(() => fixture.detectChanges()).not.toThrow();
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### E2E Tests
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
// e2e/ssr.spec.ts
|
|
392
|
+
import { test, expect } from '@playwright/test';
|
|
393
|
+
|
|
394
|
+
test('datepicker works after SSR hydration', async ({ page }) => {
|
|
395
|
+
await page.goto('/');
|
|
396
|
+
|
|
397
|
+
// Wait for hydration
|
|
398
|
+
await page.waitForSelector('ngxsmk-datepicker');
|
|
399
|
+
|
|
400
|
+
// Click datepicker
|
|
401
|
+
await page.click('.ngxsmk-input-group');
|
|
402
|
+
|
|
403
|
+
// Select a date
|
|
404
|
+
await page.click('.ngxsmk-day-cell:has-text("15")');
|
|
405
|
+
|
|
406
|
+
// Verify value is set
|
|
407
|
+
const value = await page.inputValue('.ngxsmk-display-input');
|
|
408
|
+
expect(value).toBeTruthy();
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Best Practices
|
|
413
|
+
|
|
414
|
+
1. **Always Set Locale**: Provide explicit locale for SSR consistency
|
|
415
|
+
2. **Use Platform Checks**: Check `isPlatformBrowser` for browser-only code
|
|
416
|
+
3. **Test Both Platforms**: Test on both server and client
|
|
417
|
+
4. **Use UTC for Consistency**: Consider using UTC timezone for server/client consistency
|
|
418
|
+
5. **Handle Hydration**: Let the component handle hydration automatically
|
|
419
|
+
|
|
420
|
+
## Additional Resources
|
|
421
|
+
|
|
422
|
+
- [Angular Universal Guide](https://angular.io/guide/universal)
|
|
423
|
+
- [SSR Best Practices](https://angular.io/guide/universal#best-practices)
|
|
424
|
+
- [Platform Detection](https://angular.io/api/common/isPlatformBrowser)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
|