diff --git a/README.md b/README.md
index 2680b4a..4250a0a 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ If you would like to propose a challenge, this project is open source, so feel f
## Challenges
-Check [all 39 challenges](https://angular-challenges.vercel.app/)
+Check [all 40 challenges](https://angular-challenges.vercel.app/)
## Contributors ✨
diff --git a/apps/performance/christmas-web-worker/.eslintrc.json b/apps/performance/christmas-web-worker/.eslintrc.json
new file mode 100644
index 0000000..d3cd799
--- /dev/null
+++ b/apps/performance/christmas-web-worker/.eslintrc.json
@@ -0,0 +1,19 @@
+{
+ "extends": ["../../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts"],
+ "extends": [
+ "plugin:@nx/angular",
+ "plugin:@angular-eslint/template/process-inline-templates"
+ ],
+ "rules": {}
+ },
+ {
+ "files": ["*.html"],
+ "extends": ["plugin:@nx/angular-template"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/apps/performance/christmas-web-worker/README.md b/apps/performance/christmas-web-worker/README.md
new file mode 100644
index 0000000..6da9394
--- /dev/null
+++ b/apps/performance/christmas-web-worker/README.md
@@ -0,0 +1,13 @@
+# Web workers
+
+> Author: Thomas Laforge
+
+### Run Application
+
+```bash
+npx nx serve performance-christmas-web-worker
+```
+
+### Documentation and Instruction
+
+Challenge documentation is [here](https://angular-challenges.vercel.app/challenges/performance/40-christmas-web-worker/).
diff --git a/apps/performance/christmas-web-worker/project.json b/apps/performance/christmas-web-worker/project.json
new file mode 100644
index 0000000..0f33ede
--- /dev/null
+++ b/apps/performance/christmas-web-worker/project.json
@@ -0,0 +1,81 @@
+{
+ "name": "performance-christmas-web-worker",
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "prefix": "app",
+ "sourceRoot": "apps/performance/christmas-web-worker/src",
+ "tags": [],
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "outputs": ["{options.outputPath}"],
+ "options": {
+ "outputPath": "dist/apps/performance/christmas-web-worker",
+ "index": "apps/performance/christmas-web-worker/src/index.html",
+ "main": "apps/performance/christmas-web-worker/src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "apps/performance/christmas-web-worker/tsconfig.app.json",
+ "assets": [
+ "apps/performance/christmas-web-worker/src/favicon.ico",
+ "apps/performance/christmas-web-worker/src/assets"
+ ],
+ "styles": ["apps/performance/christmas-web-worker/src/styles.scss"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "buildOptimizer": false,
+ "optimization": false,
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "sourceMap": true,
+ "namedChunks": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "browserTarget": "performance-christmas-web-worker:build:production"
+ },
+ "development": {
+ "browserTarget": "performance-christmas-web-worker:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "performance-christmas-web-worker:build"
+ }
+ },
+ "lint": {
+ "executor": "@nx/eslint:lint",
+ "outputs": ["{options.outputFile}"],
+ "options": {
+ "lintFilePatterns": [
+ "apps/performance/christmas-web-worker/**/*.ts",
+ "apps/performance/christmas-web-worker/**/*.html"
+ ]
+ }
+ }
+ }
+}
diff --git a/apps/performance/christmas-web-worker/src/app/app.component.ts b/apps/performance/christmas-web-worker/src/app/app.component.ts
new file mode 100644
index 0000000..a7e757f
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/app/app.component.ts
@@ -0,0 +1,32 @@
+import { CommonModule } from '@angular/common';
+import { Component, inject } from '@angular/core';
+import { PrimeService } from './prime-number.service';
+import { UnknownPersonComponent } from './unknown-person/unknown-person.component';
+
+@Component({
+ standalone: true,
+ imports: [CommonModule, UnknownPersonComponent],
+ providers: [PrimeService],
+ selector: 'app-root',
+ template: `
+
+
+
Progress: {{ loadingPercentage() }}%
+ `,
+ host: {
+ class: `flex flex-col h-screen w-screen bg-[#1f75c0]`,
+ },
+})
+export class AppComponent {
+ primeService = inject(PrimeService);
+
+ loadingPercentage = this.primeService.loadingPercentage;
+
+ discover() {
+ this.primeService.calculatePrimeLength();
+ }
+}
diff --git a/apps/performance/christmas-web-worker/src/app/app.config.ts b/apps/performance/christmas-web-worker/src/app/app.config.ts
new file mode 100644
index 0000000..81a6edd
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/app/app.config.ts
@@ -0,0 +1,5 @@
+import { ApplicationConfig } from '@angular/core';
+
+export const appConfig: ApplicationConfig = {
+ providers: [],
+};
diff --git a/apps/performance/christmas-web-worker/src/app/prime-number.service.ts b/apps/performance/christmas-web-worker/src/app/prime-number.service.ts
new file mode 100644
index 0000000..9ef52fa
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/app/prime-number.service.ts
@@ -0,0 +1,30 @@
+import { Injectable, computed, signal } from '@angular/core';
+
+@Injectable()
+export class PrimeService {
+ private finalResult = 664579;
+ private primesLength = signal(0);
+
+ loadingPercentage = computed(
+ () => (this.primesLength() * 100) / this.finalResult
+ );
+
+ calculatePrimeLength() {
+ this.findPrimesUpToLimit(10000000);
+ }
+
+ private findPrimesUpToLimit(limit: number) {
+ for (let num = 2; num <= limit; num++) {
+ let isPrime = true;
+ for (let i = 2; i <= Math.sqrt(num); i++) {
+ if (num % i === 0) {
+ isPrime = false;
+ break;
+ }
+ }
+ if (isPrime) {
+ this.primesLength.update((l) => l + 1);
+ }
+ }
+ }
+}
diff --git a/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.css b/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.css
new file mode 100644
index 0000000..254caa2
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.css
@@ -0,0 +1,223 @@
+*,
+*:before,
+*:after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+body {
+ background-color: #fff2c8;
+}
+.container {
+ width: 380px;
+ height: 500px;
+ position: absolute;
+ transform: translate(-50%, -50%);
+ top: 50%;
+ left: 50%;
+}
+.container *:before,
+.container *:after {
+ position: absolute;
+ content: '';
+}
+.santa {
+ height: 220px;
+ width: 200px;
+ background-color: #e84701;
+ border-radius: 80px;
+ position: absolute;
+ left: 115px;
+ top: 200px;
+}
+.santa:before,
+.santa:after {
+ width: 40px;
+ margin: auto;
+ left: 0;
+ right: 0;
+ background-color: #fff2c8;
+}
+.santa:before {
+ height: 130px;
+}
+.santa:after {
+ height: 40px;
+ bottom: -15px;
+}
+.hand-l {
+ background-color: #e84701;
+ height: 90px;
+ width: 105px;
+ position: absolute;
+ right: -30px;
+ top: 20px;
+ border-radius: 0 80px 0 0;
+}
+.hand-l:before {
+ margin: auto;
+ width: 35px;
+ height: 15px;
+ background-color: #ffffff;
+ top: 85px;
+ left: 71px;
+}
+.hand-l:after {
+ height: 15px;
+ width: 30px;
+ background-color: #fad2af;
+ left: 74px;
+ top: 101px;
+ border-radius: 0 0 30px 30px;
+}
+.hand-r {
+ height: 150px;
+ width: 180px;
+ border: 30px solid transparent;
+ border-bottom: 40px solid #e84701;
+ position: absolute;
+ left: -100px;
+ bottom: 150px;
+ border-radius: 50%;
+ animation: wave 1.5s infinite;
+ transform-origin: right;
+}
+@keyframes wave {
+ 50% {
+ transform: rotate(15deg);
+ }
+}
+.hand-r:before {
+ width: 35px;
+ height: 15px;
+ background-color: #ffffff;
+ transform: rotate(-50deg);
+ top: 68px;
+ left: -22px;
+}
+.hand-r:after {
+ width: 30px;
+ height: 15px;
+ background-color: #fad2af;
+ transform: rotate(-50deg);
+ left: -31px;
+ top: 58px;
+ border-radius: 15px 15px 0 0;
+}
+.face {
+ position: absolute;
+ margin: auto;
+ height: 180px;
+ width: 180px;
+ background-color: #fad2af;
+ border: 25px solid #f2e6da;
+ border-radius: 50%;
+ left: 0;
+ right: 0;
+ bottom: 140px;
+}
+.beard {
+ position: absolute;
+ height: 90px;
+ width: 180px;
+ background-color: #ffffff;
+ border-radius: 0 0 90px 90px;
+ right: -25px;
+ bottom: -25px;
+}
+.beard:before {
+ width: 55px;
+ height: 25px;
+ background-color: #f2e6da;
+ border-radius: 20px 0;
+ left: 34px;
+}
+.beard:after {
+ height: 25px;
+ width: 55px;
+ background-color: #f2e6da;
+ border-radius: 0 20px;
+ right: 34px;
+}
+.eyes {
+ height: 12px;
+ width: 12px;
+ background-color: #0078ca;
+ border-radius: 50%;
+ position: absolute;
+ top: 40px;
+ left: 40px;
+ box-shadow: 38px 0 #0078ca, 19px 20px #f69697;
+}
+.eyes:before {
+ height: 12px;
+ width: 25px;
+ background-color: #ffffff;
+ border-radius: 10px 0;
+ left: -10px;
+ bottom: 20px;
+}
+.eyes:after {
+ height: 12px;
+ width: 25px;
+ background-color: #ffffff;
+ border-radius: 0 10px;
+ left: 32px;
+ bottom: 20px;
+}
+.hat {
+ position: absolute;
+ height: 60px;
+ width: 120px;
+ background-color: #e84701;
+ top: -98px;
+ left: 85px;
+ border-radius: 0 0 60px 60px;
+}
+.hat:before {
+ height: 40px;
+ width: 40px;
+ background-color: #ffffff;
+ left: 100px;
+ top: -15px;
+ border-radius: 50%;
+}
+.belt {
+ position: absolute;
+ width: 100%;
+ height: 30px;
+ background-color: #000000;
+ top: 100px;
+}
+.belt:before {
+ height: 100%;
+ width: 40px;
+ border: 7px solid #ffdc2e;
+ margin: auto;
+ left: 0;
+ right: 0;
+}
+.belt:after {
+ height: 5px;
+ width: 15px;
+ background-color: #ffdc2e;
+ left: 100px;
+ top: 12px;
+}
+.shoe {
+ height: 20px;
+ width: 20px;
+ background-color: #000000;
+ position: absolute;
+ top: 220px;
+ left: 60px;
+ border-radius: 20px 0 0 0;
+}
+.shoe:before {
+ height: 20px;
+ width: 20px;
+ background-color: #000000;
+ top: 0;
+ left: 60px;
+ border-radius: 0 20px 0 0;
+}
diff --git a/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.ts b/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.ts
new file mode 100644
index 0000000..adbfa86
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/app/unknown-person/unknown-person.component.ts
@@ -0,0 +1,35 @@
+import { NgIf } from '@angular/common';
+import { Component, Input } from '@angular/core';
+@Component({
+ selector: 'unknown-person',
+ standalone: true,
+ imports: [NgIf],
+ template: `
+
+
+ `,
+ styleUrls: [`unknown-person.component.css`],
+})
+export class UnknownPersonComponent {
+ @Input({ required: true }) step!: number;
+}
diff --git a/apps/performance/christmas-web-worker/src/assets/.gitkeep b/apps/performance/christmas-web-worker/src/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/apps/performance/christmas-web-worker/src/favicon.ico b/apps/performance/christmas-web-worker/src/favicon.ico
new file mode 100644
index 0000000..317ebcb
Binary files /dev/null and b/apps/performance/christmas-web-worker/src/favicon.ico differ
diff --git a/apps/performance/christmas-web-worker/src/index.html b/apps/performance/christmas-web-worker/src/index.html
new file mode 100644
index 0000000..fc6ebdf
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ performance-christmas-web-worker
+
+
+
+
+
+
+
+
diff --git a/apps/performance/christmas-web-worker/src/main.ts b/apps/performance/christmas-web-worker/src/main.ts
new file mode 100644
index 0000000..514c89a
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/main.ts
@@ -0,0 +1,7 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { appConfig } from './app/app.config';
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err)
+);
diff --git a/apps/performance/christmas-web-worker/src/styles.scss b/apps/performance/christmas-web-worker/src/styles.scss
new file mode 100644
index 0000000..77e408a
--- /dev/null
+++ b/apps/performance/christmas-web-worker/src/styles.scss
@@ -0,0 +1,5 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* You can add global styles to this file, and also import other style files */
diff --git a/apps/performance/christmas-web-worker/tailwind.config.js b/apps/performance/christmas-web-worker/tailwind.config.js
new file mode 100644
index 0000000..38183db
--- /dev/null
+++ b/apps/performance/christmas-web-worker/tailwind.config.js
@@ -0,0 +1,14 @@
+const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/performance/christmas-web-worker/tsconfig.app.json b/apps/performance/christmas-web-worker/tsconfig.app.json
new file mode 100644
index 0000000..5822042
--- /dev/null
+++ b/apps/performance/christmas-web-worker/tsconfig.app.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../dist/out-tsc",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"],
+ "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
+}
diff --git a/apps/performance/christmas-web-worker/tsconfig.editor.json b/apps/performance/christmas-web-worker/tsconfig.editor.json
new file mode 100644
index 0000000..4ee6393
--- /dev/null
+++ b/apps/performance/christmas-web-worker/tsconfig.editor.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*.ts"],
+ "compilerOptions": {
+ "types": []
+ }
+}
diff --git a/apps/performance/christmas-web-worker/tsconfig.json b/apps/performance/christmas-web-worker/tsconfig.json
new file mode 100644
index 0000000..51c7908
--- /dev/null
+++ b/apps/performance/christmas-web-worker/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "es2022",
+ "useDefineForClassFields": false,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.editor.json"
+ }
+ ],
+ "extends": "../../../tsconfig.base.json",
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/challenge-number.json b/challenge-number.json
index 6e455d3..cd65533 100644
--- a/challenge-number.json
+++ b/challenge-number.json
@@ -1,6 +1,6 @@
{
- "total": 39,
+ "total": 40,
"🟢": 14,
- "🟠": 118,
+ "🟠": 119,
"🔴": 207
}
diff --git a/docs/src/content/docs/challenges/angular/39-injection-token.md b/docs/src/content/docs/challenges/angular/39-injection-token.md
index 5dfdbd8..36aab19 100644
--- a/docs/src/content/docs/challenges/angular/39-injection-token.md
+++ b/docs/src/content/docs/challenges/angular/39-injection-token.md
@@ -6,7 +6,6 @@ challengeNumber: 39
command: angular-injection-token
sidebar:
order: 118
- badge: New
---
## Information
diff --git a/docs/src/content/docs/challenges/performance/40-christmas-web-worker.md b/docs/src/content/docs/challenges/performance/40-christmas-web-worker.md
new file mode 100644
index 0000000..91570e9
--- /dev/null
+++ b/docs/src/content/docs/challenges/performance/40-christmas-web-worker.md
@@ -0,0 +1,20 @@
+---
+title: 🟠 Web workers
+description: Challenge 40 is about ...
+author: Thomas Laforge
+challengeNumber: 40
+command: performance-christmas-web-worker
+sidebar:
+ order: 119
+ badge: New
+---
+
+:::note
+WIP: The following documentation need to be written.
+:::
+
+## Information
+
+## Statement
+
+## Constraints
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
index 3c7eabf..8cc1ae6 100644
--- a/docs/src/content/docs/index.mdx
+++ b/docs/src/content/docs/index.mdx
@@ -15,7 +15,7 @@ hero:
icon: right-arrow
variant: primary
- text: Go to the latest Challenge
- link: /challenges/angular/39-injection-token/
+ link: /challenges/performance/40-christmas-web-worker/
icon: rocket
- text: Give a star
link: https://github.com/tomalaforge/angular-challenges
@@ -27,8 +27,8 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
import MyIcon from '../../components/MyIcon.astro';
-
- This repository gathers 39 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript.
+
+ This repository gathers 40 Challenges related to Angular, Nx, RxJS, Ngrx and Typescript.
These challenges resolve around real-life issues or specific features to elevate your skills.