feat: upgrade Angular dependencies to version 21 and implement new animation features

- Updated Angular dependencies in package.json to version 21.x.x.
- Added `provideAnimationsAsync` to app configuration for improved animation handling.
- Refactored user input component to utilize a dynamic list of vacation days with animations.
- Introduced a new `DayInput` component for managing individual vacation days.
- Enhanced styling and animations for user input and day input components.
- Updated calendar service to support new vacation day structure.
- Adjusted tests to accommodate changes in component structure and dependencies.
This commit is contained in:
nessar committed 2025-11-29 19:08:28 +01:00
1 parent 5ab29ef770
commit e10f2ff4e5
15 files changed
+15528 -15284

No files matched your search

+15134 -15137
View File
File diff suppressed because it is too large. Load diff
+69 -68
View File
@@ -1,70 +1,71 @@
{ {
"name": "vac-optimizer", "name": "vac-optimizer",
"version": "1.5.0", "version": "1.5.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "ng test", "test": "ng test",
"serve:ssr:vac-optimizer": "node dist/vac-optimizer/server/server.mjs", "serve:ssr:vac-optimizer": "node dist/vac-optimizer/server/server.mjs",
"lint": "eslint \"src/**/*.ts\"", "lint": "eslint \"src/**/*.ts\"",
"lint:fix": "eslint \"src/**/*.ts\" --fix", "lint:fix": "eslint \"src/**/*.ts\" --fix",
"format": "prettier --write \"src/**/*.{ts,html,scss,css,json}\"", "format": "prettier --write \"src/**/*.{ts,html,scss,css,json}\"",
"format:check": "prettier --check \"src/**/*.{ts,html,scss,css,json}\"" "format:check": "prettier --check \"src/**/*.{ts,html,scss,css,json}\""
}, },
"prettier": { "prettier": {
"printWidth": 100, "printWidth": 100,
"singleQuote": true, "singleQuote": true,
"overrides": [ "overrides": [
{ {
"files": "*.html", "files": "*.html",
"options": { "options": {
"parser": "angular" "parser": "angular"
} }
} }
] ]
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^20.2.12", "@angular/animations": "^21.0.1",
"@angular/common": "^20.3.10", "@angular/cdk": "^21.0.1",
"@angular/compiler": "^20.3.10", "@angular/common": "^21.0.1",
"@angular/core": "^20.3.10", "@angular/compiler": "^21.0.1",
"@angular/forms": "^20.3.10", "@angular/core": "^21.0.1",
"@angular/material": "^20.2.12", "@angular/forms": "^21.0.1",
"@angular/platform-browser": "^20.3.10", "@angular/material": "^21.0.1",
"@angular/platform-server": "^20.3.10", "@angular/platform-browser": "^21.0.1",
"@angular/router": "^20.3.10", "@angular/platform-server": "^21.0.1",
"@angular/ssr": "^20.3.10", "@angular/router": "^21.0.1",
"date-holidays": "^3.26.2", "@angular/ssr": "^21.0.1",
"dayjs": "^1.11.19", "date-holidays": "^3.26.2",
"express": "^5.1.0", "dayjs": "^1.11.19",
"rxjs": "~7.8.0", "express": "^5.1.0",
"tslib": "^2.3.0" "rxjs": "~7.8.0",
}, "tslib": "^2.3.0"
"devDependencies": { },
"@angular/build": "^20.3.10", "devDependencies": {
"@angular/cli": "^20.3.10", "@angular/build": "^21.0.1",
"@angular/compiler-cli": "^20.3.10", "@angular/cli": "^21.0.1",
"@eslint/js": "^9.39.1", "@angular/compiler-cli": "^21.0.1",
"@types/express": "^5.0.1", "@eslint/js": "^9.39.1",
"@types/jasmine": "~5.1.0", "@types/express": "^5.0.1",
"@types/jest": "^30.0.0", "@types/jasmine": "~5.1.0",
"@types/node": "^24.10.0", "@types/jest": "^30.0.0",
"eslint": "^9.39.1", "@types/node": "^24.10.0",
"eslint-config-prettier": "^10.1.8", "eslint": "^9.39.1",
"eslint-plugin-prettier": "^5.5.4", "eslint-config-prettier": "^10.1.8",
"jasmine-core": "~5.12.1", "eslint-plugin-prettier": "^5.5.4",
"jest": "^30.2.0", "jasmine-core": "~5.12.1",
"jest-preset-angular": "^15.0.3", "jest": "^30.2.0",
"karma": "~6.4.0", "jest-preset-angular": "^15.0.3",
"karma-chrome-launcher": "~3.2.0", "karma": "~6.4.0",
"karma-coverage": "~2.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-jasmine": "~5.1.0", "karma-coverage": "~2.2.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine": "~5.1.0",
"prettier": "^3.6.2", "karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.2", "prettier": "^3.6.2",
"typescript-eslint": "^8.46.4" "typescript": "~5.9.2",
} "typescript-eslint": "^8.46.4"
}
} }
+2
View File
@@ -11,6 +11,7 @@ import {
withEventReplay, withEventReplay,
withIncrementalHydration, withIncrementalHydration,
} from '@angular/platform-browser'; } from '@angular/platform-browser';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@@ -18,5 +19,6 @@ export const appConfig: ApplicationConfig = {
provideZonelessChangeDetection(), provideZonelessChangeDetection(),
provideRouter(routes), provideRouter(routes),
provideClientHydration(withEventReplay(), withIncrementalHydration()), provideClientHydration(withEventReplay(), withIncrementalHydration()),
provideAnimationsAsync(),
], ],
}; };
@@ -3,14 +3,14 @@ import { UserInputService } from '../../services/user-input-service';
import { MatCard, MatCardContent } from '@angular/material/card'; import { MatCard, MatCardContent } from '@angular/material/card';
import { CalendarMonth } from '../calendar-month/calendar-month'; import { CalendarMonth } from '../calendar-month/calendar-month';
import { CalendarService, SelectedDates } from '../../services/calendar-service'; import { CalendarService, SelectedDates } from '../../services/calendar-service';
import { CommonModule } from '@angular/common';
import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { CalendarSettingsService } from '../../services/calendar-settings-service'; import { CalendarSettingsService } from '../../services/calendar-settings-service';
import { DateCacheService } from '../../services/date-cache.service'; import { DateCacheService } from '../../services/date-cache.service';
@Component({ @Component({
selector: 'app-calendar-year', selector: 'app-calendar-year',
imports: [MatCard, CalendarMonth, MatCardContent, CommonModule, MatProgressSpinner], imports: [MatCard, CalendarMonth, MatCardContent, MatProgressSpinner],
templateUrl: './calendar-year.html', templateUrl: './calendar-year.html',
styleUrl: './calendar-year.scss', styleUrl: './calendar-year.scss',
}) })
+13 -14
View File
@@ -1,19 +1,18 @@
<div class="user-input"> <div class="user-input">
<p>Optimisez vos jours de vacances en fonction des jours fériés et des week-ends !</p> <p>Optimisez vos jours de vacances en fonction des jours fériés et des week-ends !</p>
<form [formGroup]="userInputForm"> <div class="listOfDays">
<mat-form-field appearance="fill"> @for (day of daysOff; track day.id) {
<mat-label>Combien de CP par an ?</mat-label> <div class="day-input-wrapper" animate.enter="day-enter" animate.leave="day-leave">
<input matInput type="number" max="365" min="0" formControlName="CP" /> <app-day-input [day]="day" (deleteEntry)="removeFromList($index)"></app-day-input>
</mat-form-field> </div>
<mat-form-field appearance="fill"> }
<mat-label>Combien de jours de RTT par an ?</mat-label> <mat-card class="add-day-card mat-elevation-z2" (click)="addDay()">
<input matInput type="number" max="365" min="0" formControlName="RTT" /> <button matFab aria-label="Add entry">
</mat-form-field> <mat-icon>add</mat-icon>
<mat-form-field appearance="fill"> </button>
<mat-label>Autres jours</mat-label> <h3>Ajouter un jour de vacances</h3>
<input matInput type="number" max="365" min="0" formControlName="Others" /> </mat-card>
</mat-form-field> </div>
</form>
@if (hasRemainingDays()) { @if (hasRemainingDays()) {
<mat-card class="mat-elevation-z2 warning-card"> <mat-card class="mat-elevation-z2 warning-card">
<mat-card-content class="warning-content"> <mat-card-content class="warning-content">
@@ -25,7 +25,61 @@
} }
} }
.listOfDays {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.day-input-wrapper {
animation: slide-fade-in 200ms ease-out;
@starting-style {
opacity: 0;
transform: translateX(-20px);
}
}
.day-enter {
animation: slide-fade-in 200ms ease-out;
}
.day-leave {
opacity: 0;
transform: translateX(20px);
transition:
opacity 200ms ease-in,
transform 200ms ease-in;
}
@keyframes slide-fade-in {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
form { form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.add-day-card {
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
gap: 1rem;
margin-bottom: 1rem;
cursor: pointer;
}
.add-day-card h3 {
flex-grow: 1;
margin: 0;
font-weight: normal;
}
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideZonelessChangeDetection } from '@angular/core'; import { provideZonelessChangeDetection } from '@angular/core';
import { UserInput } from './user-input'; import { UserInput } from './user-input';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
describe('UserInput', () => { describe('UserInput', () => {
let component: UserInput; let component: UserInput;
@@ -9,7 +10,7 @@ describe('UserInput', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [UserInput], imports: [UserInput],
providers: [provideZonelessChangeDetection()], providers: [provideZonelessChangeDetection(), provideAnimationsAsync()],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(UserInput); fixture = TestBed.createComponent(UserInput);
+25 -43
View File
@@ -1,79 +1,61 @@
import { Component, OnInit, OnDestroy, effect, computed } from '@angular/core'; import {
import { MatFormField, MatLabel } from '@angular/material/form-field'; Component,
OnInit,
OnDestroy,
computed,
inject,
ChangeDetectionStrategy,
} from '@angular/core';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { UserInputService } from '../../services/user-input-service'; import { UserInputService } from '../../services/user-input-service';
import { VacationsNumber } from '../../services/calendar-service'; import { Subject } from 'rxjs';
import { debounceTime, takeUntil, Subject } from 'rxjs';
import { CalendarSettings } from '../calendar-settings/calendar-settings'; import { CalendarSettings } from '../calendar-settings/calendar-settings';
import { MatCard, MatCardContent } from '@angular/material/card'; import { MatCard, MatCardContent } from '@angular/material/card';
import { MatIcon } from '@angular/material/icon'; import { MatIcon } from '@angular/material/icon';
import { DayInput } from '../userInput/day-input/day-input';
import { MatButtonModule } from '@angular/material/button';
@Component({ @Component({
selector: 'app-user-input', selector: 'app-user-input',
imports: [ imports: [
MatFormField,
MatLabel,
MatInputModule, MatInputModule,
ReactiveFormsModule, ReactiveFormsModule,
CalendarSettings, CalendarSettings,
MatCard, MatCard,
MatCardContent, MatCardContent,
MatIcon, MatIcon,
MatButtonModule,
DayInput,
], ],
templateUrl: './user-input.html', templateUrl: './user-input.html',
styleUrl: './user-input.scss', styleUrl: './user-input.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class UserInput implements OnInit, OnDestroy { export class UserInput implements OnInit, OnDestroy {
constructor(private userInputService: UserInputService) { private userInputService: UserInputService = inject(UserInputService);
// Watch for signal changes and update form public daysOff = this.userInputService.vacationNumberSignal();
effect(() => {
const vacationData = this.userInputService.vacationNumberSignal();
this.userInputForm.patchValue(
{
CP: vacationData.cp,
RTT: vacationData.rtt,
Others: vacationData.other,
},
{ emitEvent: false },
);
});
}
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
userInputForm: FormGroup = new FormGroup({
CP: new FormControl<number | null>(null),
RTT: new FormControl<number | null>(null),
Others: new FormControl<number | null>(null),
});
protected remainingDays = computed(() => { protected remainingDays = computed(() => {
const remaining = this.userInputService.remainingVacationDaysSignal(); const remaining = this.userInputService.remainingVacationDaysSignal();
return remaining.cp + remaining.rtt + remaining.other; return remaining.length;
}); });
protected hasRemainingDays = computed(() => this.remainingDays() > 0); protected hasRemainingDays = computed(() => this.remainingDays() > 0);
ngOnInit() { ngOnInit() {}
this.userInputForm.valueChanges
.pipe(debounceTime(1500), takeUntil(this.destroy$))
.subscribe(() => this.submitForm());
}
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
} }
private submitForm() { public addDay() {
if (this.userInputForm.valid) { this.daysOff.push(this.userInputService.createVacationDay());
const vacationData: VacationsNumber = { }
cp: this.userInputForm.get('CP')?.value || 0,
rtt: this.userInputForm.get('RTT')?.value || 0, public removeFromList(index: number) {
other: this.userInputForm.get('Others')?.value || 0, this.daysOff.splice(index, 1);
};
this.userInputService.vacationNumberSignal.set(vacationData);
}
} }
} }
@@ -0,0 +1,36 @@
<!-- <mat-list-item role="listitem"> -->
<mat-card class="card">
<mat-form-field class="numberInput">
<input
matInput
type="number"
min="0"
[value]="day().numberOfDays"
(input)="onInput($event)"
/>
</mat-form-field>
<mat-form-field class="dayTypeInput">
<mat-select (valueChange)="dayChange.emit($event.value)" [value]="day().type">
<mat-option></mat-option>
@for (dayType of selectableDayTypes; track $index) {
<mat-option [value]="dayType">{{ tooltipTypeMapping[dayType] }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="dateInput">
<mat-label>Choose a date</mat-label>
<input
matInput
type="datetime"
[matDatepicker]="picker"
[value]="day().expiryDate"
(dateChange)="addEvent($event)"
/>
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<button matFab aria-label="Delete entry" (click)="deleteEntry.emit()">
<mat-icon>close</mat-icon>
</button>
</mat-card>
<!-- </mat-list-item> -->
@@ -0,0 +1,20 @@
.card {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px;
gap: 16px;
}
.dateInput {
flex-grow: 1;
}
.numberInput {
width: 80px;
}
::ng-deep .mat-mdc-form-field-subscript-wrapper {
display: none;
}
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DayInput } from './day-input';
describe('DayInput', () => {
let component: DayInput;
let fixture: ComponentFixture<DayInput>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DayInput],
}).compileComponents();
fixture = TestBed.createComponent(DayInput);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,101 @@
import { Component, input, output, ChangeDetectionStrategy } from '@angular/core';
import { SelectableDayType, tooltipTypeMapping, VacDay } from '../../../services/calendar-service';
import { MatCard } from '@angular/material/card';
import { MatOption, MatSelect } from '@angular/material/select';
import { MatInput, MatInputModule, MatLabel } from '@angular/material/input';
import {
MatDatepicker,
MatDatepickerInput,
MatDatepickerInputEvent,
MatDatepickerToggle,
} from '@angular/material/datepicker';
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
NativeDateAdapter,
} from '@angular/material/core';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
class CustomDateAdapter extends NativeDateAdapter {
override format(date: Date, displayFormat: unknown): string {
if (displayFormat === 'input') {
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
}
return super.format(date, displayFormat as object);
}
override parse(value: string): Date | null {
if (!value) return null;
const parts = value.split('/');
if (parts.length === 3) {
const day = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1;
const year = parseInt(parts[2], 10);
if (!isNaN(day) && !isNaN(month) && !isNaN(year)) {
return new Date(year, month, day);
}
}
return null;
}
}
const MY_DATE_FORMATS = {
parse: {
dateInput: 'input',
},
display: {
dateInput: 'input',
monthYearLabel: { year: 'numeric', month: 'short' },
dateA11yLabel: { year: 'numeric', month: 'long', day: 'numeric' },
monthYearA11yLabel: { year: 'numeric', month: 'long' },
},
};
@Component({
selector: 'app-day-input',
imports: [
MatIconModule,
MatCard,
MatSelect,
MatOption,
MatInput,
MatDatepicker,
MatLabel,
MatDatepickerToggle,
MatDatepickerInput,
MatInputModule,
MatButtonModule,
],
templateUrl: './day-input.html',
styleUrl: './day-input.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{ provide: DateAdapter, useClass: CustomDateAdapter },
{ provide: MAT_DATE_LOCALE, useValue: 'en-GB' },
{ provide: MAT_DATE_FORMATS, useValue: MY_DATE_FORMATS },
],
})
export class DayInput {
public day = input.required<VacDay>();
public dayChange = output<VacDay>();
public deleteEntry = output<void>();
protected readonly selectableDayTypes = Object.values(SelectableDayType);
protected readonly tooltipTypeMapping = tooltipTypeMapping;
onInput(event: Event) {
const value = Number((event.target as HTMLInputElement).value);
this.day().numberOfDays = value;
this.dayChange.emit(this.day());
}
addEvent(event: MatDatepickerInputEvent<Date>) {
this.day().expiryDate = event.value!;
this.dayChange.emit(this.day());
}
}
+14 -7
View File
@@ -184,10 +184,11 @@ export class CalendarService {
} }
} }
export type VacationsNumber = { export type VacDay = {
rtt: number; id: string;
cp: number; type: SelectableDayType;
other: number; numberOfDays: number;
expiryDate: Date;
}; };
export type Week = (Date | null)[]; export type Week = (Date | null)[];
@@ -203,6 +204,12 @@ export enum DayType {
TODAY = 'today', TODAY = 'today',
} }
export enum SelectableDayType {
CP = DayType.CP,
RTT = DayType.RTT,
OTHER = DayType.OTHER,
}
export const tooltipTypeMapping: { [key in DayType]: string } = { export const tooltipTypeMapping: { [key in DayType]: string } = {
[DayType.WEEKEND]: 'Weekend', [DayType.WEEKEND]: 'Weekend',
[DayType.CLOSED_DAY]: 'Jour Férié', [DayType.CLOSED_DAY]: 'Jour Férié',
@@ -537,11 +544,11 @@ export class SelectedDates implements SelectedDateInterface {
} }
async optimizeVacations( async optimizeVacations(
vacationsNumber: VacationsNumber, vacationsNumber: any,
calendarSettingsService: CalendarSettingsService, calendarSettingsService: CalendarSettingsService,
calendarService: CalendarService, calendarService: CalendarService,
progressCallback?: (progress: number) => void, progressCallback?: (progress: number) => void,
): Promise<VacationsNumber> { ): Promise<any> {
if (calendarSettingsService.get('samediMalin')()) this.samediMalin(vacationsNumber); if (calendarSettingsService.get('samediMalin')()) this.samediMalin(vacationsNumber);
const initialVacations = { ...vacationsNumber }; const initialVacations = { ...vacationsNumber };
@@ -605,7 +612,7 @@ export class SelectedDates implements SelectedDateInterface {
return vacationsNumber; return vacationsNumber;
} }
samediMalin(vacationsNumber: VacationsNumber): void { samediMalin(vacationsNumber: any): void {
const now = new Date(); const now = new Date();
now.setHours(0, 0, 0, 0); now.setHours(0, 0, 0, 0);
const nowTime = now.getTime(); const nowTime = now.getTime();
+32 -11
View File
@@ -1,5 +1,5 @@
import { Injectable, signal } from '@angular/core'; import { Injectable, signal } from '@angular/core';
import { VacationsNumber } from './calendar-service'; import { SelectableDayType, VacDay } from './calendar-service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -9,18 +9,39 @@ export class UserInputService {
return Math.floor(Math.random() * 9) + 1; return Math.floor(Math.random() * 9) + 1;
} }
private getInitialValues(): VacationsNumber { private getInitialValues(): VacDay[] {
return [
{
id: crypto.randomUUID(),
type: SelectableDayType.CP,
numberOfDays: this.getRandomNumber(),
// today + 365 days
expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
},
{
id: crypto.randomUUID(),
type: SelectableDayType.RTT,
numberOfDays: this.getRandomNumber(),
expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
},
{
id: crypto.randomUUID(),
type: SelectableDayType.OTHER,
numberOfDays: this.getRandomNumber(),
expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
},
];
}
public createVacationDay(): VacDay {
return { return {
cp: this.getRandomNumber(), id: crypto.randomUUID(),
rtt: this.getRandomNumber(), type: SelectableDayType.CP,
other: this.getRandomNumber(), numberOfDays: 1,
expiryDate: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
}; };
} }
readonly vacationNumberSignal = signal<VacationsNumber>(this.getInitialValues()); readonly vacationNumberSignal = signal<VacDay[]>(this.getInitialValues());
readonly remainingVacationDaysSignal = signal<VacationsNumber>({ readonly remainingVacationDaysSignal = signal<VacDay[]>([]);
cp: 0,
rtt: 0,
other: 0,
});
} }
+2 -1
View File
@@ -1,7 +1,8 @@
import { provideZoneChangeDetection } from "@angular/core";
import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser'; import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app'; import { App } from './app/app';
import { config } from './app/app.config.server'; import { config } from './app/app.config.server';
const bootstrap = (context: BootstrapContext) => bootstrapApplication(App, config, context); const bootstrap = (context: BootstrapContext) => bootstrapApplication(App, {...config, providers: [provideZoneChangeDetection(), ...config.providers]}, context);
export default bootstrap; export default bootstrap;