Refactor: create DateCacheService to reduce date duplication
Co-authored-by: THoguet <61246905+THoguet@users.noreply.github.com>
This commit is contained in:
1 parent
dbf504b060
commit
125786e2d9
5 files changed
+187
-31
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import { CalendarService, Month, monthNames, SelectedDates } from '../../services/calendar-service';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
||||
import { DateCacheService } from '../../services/date-cache.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calendar-month',
|
||||
@@ -10,7 +11,8 @@ import { CalendarSettingsService } from '../../services/calendar-settings-servic
|
||||
styleUrl: './calendar-month.scss',
|
||||
})
|
||||
export class CalendarMonth implements OnInit {
|
||||
constructor(protected calendarSettingsService: CalendarSettingsService) {}
|
||||
protected calendarSettingsService = inject(CalendarSettingsService);
|
||||
private dateCache = inject(DateCacheService);
|
||||
|
||||
@Input({ required: true })
|
||||
monthIndex!: number;
|
||||
@@ -18,18 +20,9 @@ export class CalendarMonth implements OnInit {
|
||||
@Input({ required: true })
|
||||
year!: number;
|
||||
|
||||
// Cache "now" as a class property to avoid recreating it on every call
|
||||
// Note: This value is cached at component initialization and won't update if the day changes
|
||||
// while the component is still mounted. This is acceptable for this use case since the app
|
||||
// is primarily used within a single day session.
|
||||
private readonly now: Date = (() => {
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
})();
|
||||
|
||||
// Use DateCacheService instead of creating Date objects
|
||||
getNow(): Date {
|
||||
return this.now;
|
||||
return this.dateCache.getNow();
|
||||
}
|
||||
|
||||
currentMonth: Date = new Date();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, effect, signal } from '@angular/core';
|
||||
import { Component, effect, signal, inject } from '@angular/core';
|
||||
import { UserInputService } from '../../services/user-input-service';
|
||||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||
import { CalendarMonth } from '../calendar-month/calendar-month';
|
||||
@@ -6,6 +6,7 @@ import { CalendarService, SelectedDates } from '../../services/calendar-service'
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
||||
import { DateCacheService } from '../../services/date-cache.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calendar-year',
|
||||
@@ -14,17 +15,23 @@ import { CalendarSettingsService } from '../../services/calendar-settings-servic
|
||||
styleUrl: './calendar-year.scss',
|
||||
})
|
||||
export class CalendarYear {
|
||||
private dateCache = inject(DateCacheService);
|
||||
private userInput = inject(UserInputService);
|
||||
private calendarService = inject(CalendarService);
|
||||
private calendarSettingsService = inject(CalendarSettingsService);
|
||||
|
||||
protected isLoading = signal(true);
|
||||
|
||||
// Cache current month and year to avoid repeated Date object creation
|
||||
private readonly currentMonth = new Date().getMonth();
|
||||
private readonly currentYear = new Date().getFullYear();
|
||||
// Use DateCacheService instead of creating Date objects
|
||||
private get currentMonth() {
|
||||
return this.dateCache.getCurrentMonth();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private userInput: UserInputService,
|
||||
private calendarService: CalendarService,
|
||||
private calendarSettingsService: CalendarSettingsService,
|
||||
) {
|
||||
private get currentYear() {
|
||||
return this.dateCache.getCurrentYear();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.selectedDates = this.calendarService.getSelectedDatesForYear();
|
||||
const vacationData = this.userInput.vacationNumberSignal();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { DateRange } from '@angular/material/datepicker';
|
||||
import Holidays, { HolidaysTypes } from 'date-holidays';
|
||||
import dayjs from 'dayjs';
|
||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||
import { CalendarSettingsService } from './calendar-settings-service';
|
||||
import { DateCacheService } from './date-cache.service';
|
||||
|
||||
dayjs.extend(weekOfYear);
|
||||
dayjs.extend(isoWeek);
|
||||
@@ -19,6 +20,8 @@ const allTypesOptions: HolidaysTypes.Options = {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CalendarService {
|
||||
private dateCache = inject(DateCacheService);
|
||||
|
||||
// Cache for holiday timestamps
|
||||
private holidayTimestampsCache: Set<number> | null = null;
|
||||
private holidayCacheYear: number | null = null;
|
||||
@@ -36,11 +39,12 @@ export class CalendarService {
|
||||
const monthInAYear = CalendarService.monthsInAYearFromNow();
|
||||
let selectedDates: SelectedDates = new SelectedDates();
|
||||
|
||||
// Cache today's date components to avoid repeated new Date() calls
|
||||
const today = new Date();
|
||||
const todayDate = today.getDate();
|
||||
const todayMonth = today.getMonth();
|
||||
const todayYear = today.getFullYear();
|
||||
// Use DateCacheService to get today's date components
|
||||
const {
|
||||
date: todayDate,
|
||||
month: todayMonth,
|
||||
year: todayYear,
|
||||
} = this.dateCache.getTodayComponents();
|
||||
|
||||
// Create a Set of holiday timestamps for O(1) lookup instead of O(n)
|
||||
const holidayTimestamps = new Set(holidays.map((h) => h.getTime()));
|
||||
@@ -99,7 +103,7 @@ export class CalendarService {
|
||||
|
||||
getHolidays(): Date[] {
|
||||
// Cache holidays to avoid recalculation
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYear = this.dateCache.getCurrentYear();
|
||||
if (this.holidayCacheYear === currentYear && this.holidayTimestampsCache) {
|
||||
// Return timestamps directly for efficient lookup
|
||||
return Array.from(this.holidayTimestampsCache).map((ts) => new Date(ts));
|
||||
@@ -164,9 +168,10 @@ export class CalendarService {
|
||||
}
|
||||
|
||||
static monthsInAYearFromNow(): Year {
|
||||
const dateCache = inject(DateCacheService);
|
||||
const months: Year = [];
|
||||
const currentMonthIndex = new Date().getMonth();
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentMonthIndex = dateCache.getCurrentMonth();
|
||||
const currentYear = dateCache.getCurrentYear();
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const monthIndex = (currentMonthIndex + i) % 12;
|
||||
const year = currentYear + Math.floor((currentMonthIndex + i) / 12);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { DateCacheService } from './date-cache.service';
|
||||
|
||||
describe('DateCacheService', () => {
|
||||
let service: DateCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(DateCacheService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should cache today date', () => {
|
||||
const today1 = service.getToday();
|
||||
const today2 = service.getToday();
|
||||
expect(today1).toBe(today2); // Same reference
|
||||
});
|
||||
|
||||
it('should cache today components', () => {
|
||||
const components1 = service.getTodayComponents();
|
||||
const components2 = service.getTodayComponents();
|
||||
expect(components1).toBe(components2); // Same reference
|
||||
});
|
||||
|
||||
it('should cache current month', () => {
|
||||
const month1 = service.getCurrentMonth();
|
||||
const month2 = service.getCurrentMonth();
|
||||
expect(month1).toBe(month2);
|
||||
});
|
||||
|
||||
it('should cache current year', () => {
|
||||
const year1 = service.getCurrentYear();
|
||||
const year2 = service.getCurrentYear();
|
||||
expect(year1).toBe(year2);
|
||||
});
|
||||
|
||||
it('should clear cache', () => {
|
||||
const year1 = service.getCurrentYear();
|
||||
service.clearCache();
|
||||
const year2 = service.getCurrentYear();
|
||||
expect(year1).toBe(year2); // Values should be the same even after clearing
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Service to cache and provide commonly used date values.
|
||||
* This reduces redundant Date object creation across the application.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DateCacheService {
|
||||
private _today: Date | null = null;
|
||||
private _todayComponents: { date: number; month: number; year: number } | null = null;
|
||||
private _currentMonth: number | null = null;
|
||||
private _currentYear: number | null = null;
|
||||
|
||||
/**
|
||||
* Get today's date (time set to 00:00:00.000)
|
||||
*/
|
||||
getToday(): Date {
|
||||
if (!this._today || this.isNewDay(this._today)) {
|
||||
this._today = new Date();
|
||||
this._today.setHours(0, 0, 0, 0);
|
||||
this._todayComponents = null; // Invalidate components cache
|
||||
}
|
||||
return this._today;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date components (date, month, year) for efficient comparison
|
||||
*/
|
||||
getTodayComponents(): { date: number; month: number; year: number } {
|
||||
if (!this._todayComponents || (this._today && this.isNewDay(this._today))) {
|
||||
const today = this.getToday();
|
||||
this._todayComponents = {
|
||||
date: today.getDate(),
|
||||
month: today.getMonth(),
|
||||
year: today.getFullYear(),
|
||||
};
|
||||
}
|
||||
return this._todayComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current month (0-11)
|
||||
*/
|
||||
getCurrentMonth(): number {
|
||||
if (this._currentMonth === null || this.isNewMonth()) {
|
||||
this._currentMonth = new Date().getMonth();
|
||||
}
|
||||
return this._currentMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current year
|
||||
*/
|
||||
getCurrentYear(): number {
|
||||
if (this._currentYear === null || this.isNewYear()) {
|
||||
this._currentYear = new Date().getFullYear();
|
||||
}
|
||||
return this._currentYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current date with time set to 00:00:00.000 (alias for getToday)
|
||||
*/
|
||||
getNow(): Date {
|
||||
return this.getToday();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cached date is from a previous day
|
||||
*/
|
||||
private isNewDay(cachedDate: Date): boolean {
|
||||
const now = new Date();
|
||||
return (
|
||||
cachedDate.getDate() !== now.getDate() ||
|
||||
cachedDate.getMonth() !== now.getMonth() ||
|
||||
cachedDate.getFullYear() !== now.getFullYear()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're in a new month
|
||||
*/
|
||||
private isNewMonth(): boolean {
|
||||
return this._currentMonth !== null && this._currentMonth !== new Date().getMonth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're in a new year
|
||||
*/
|
||||
private isNewYear(): boolean {
|
||||
return this._currentYear !== null && this._currentYear !== new Date().getFullYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all caches (useful for testing)
|
||||
*/
|
||||
clearCache(): void {
|
||||
this._today = null;
|
||||
this._todayComponents = null;
|
||||
this._currentMonth = null;
|
||||
this._currentYear = null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user