feat: Implement tasks feature using NGRX signals and remove the old counter store, alongside general project configuration and skill documentation updates.
continuous-integration/drone/pr Build is passing

This commit is contained in:
Dennis Hundertmark
2026-03-08 09:50:17 +01:00
parent 2184971175
commit 9d13cc652a
47 changed files with 15272 additions and 14144 deletions
+104 -116
View File
@@ -14,43 +14,43 @@ Configure and use dependency injection in Angular v20+ with `inject()` and provi
Prefer `inject()` over constructor injection:
```typescript
import { Component, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { User } from "./user.service";
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { User } from './user.service';
@Component({
selector: "app-user-list",
template: `...`,
selector: 'app-user-list',
template: `...`,
})
export class UserList {
// Inject dependencies
private http = inject(HttpClient);
private userService = inject(User);
// Inject dependencies
private http = inject(HttpClient);
private userService = inject(User);
// Can use immediately
users = this.userService.getUsers();
// Can use immediately
users = this.userService.getUsers();
}
```
### Injectable Services
```typescript
import { Injectable, inject, signal } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: "root", // Singleton at root level
providedIn: 'root', // Singleton at root level
})
export class User {
private http = inject(HttpClient);
private http = inject(HttpClient);
private users = signal<User[]>([]);
readonly users$ = this.users.asReadonly();
private users = signal<User[]>([]);
readonly users$ = this.users.asReadonly();
async loadUsers() {
const users = await firstValueFrom(this.http.get<User[]>("/api/users"));
this.users.set(users);
}
async loadUsers() {
const users = await firstValueFrom(this.http.get<User[]>('/api/users'));
this.users.set(users);
}
}
```
@@ -61,13 +61,13 @@ export class User {
```typescript
// Recommended: providedIn
@Injectable({
providedIn: "root",
providedIn: 'root',
})
export class Auth {}
// Alternative: in app.config.ts
export const appConfig: ApplicationConfig = {
providers: [Auth],
providers: [Auth],
};
```
@@ -75,12 +75,12 @@ export const appConfig: ApplicationConfig = {
```typescript
@Component({
selector: "app-editor",
providers: [EditorState], // New instance for each component
template: `...`,
selector: 'app-editor',
providers: [EditorState], // New instance for each component
template: `...`,
})
export class Editor {
private editorState = inject(EditorState);
private editorState = inject(EditorState);
}
```
@@ -88,14 +88,14 @@ export class Editor {
```typescript
export const routes: Routes = [
{
path: "admin",
providers: [Admin], // Shared within this route tree
children: [
{ path: "", component: AdminDashboard },
{ path: "users", component: AdminUsers },
],
},
{
path: 'admin',
providers: [Admin], // Shared within this route tree
children: [
{ path: '', component: AdminDashboard },
{ path: 'users', component: AdminUsers },
],
},
];
```
@@ -104,31 +104,31 @@ export const routes: Routes = [
### Creating Tokens
```typescript
import { InjectionToken } from "@angular/core";
import { InjectionToken } from '@angular/core';
// Simple value token
export const API_URL = new InjectionToken<string>("API_URL");
export const API_URL = new InjectionToken<string>('API_URL');
// Object token
export interface AppConfig {
apiUrl: string;
features: {
darkMode: boolean;
analytics: boolean;
};
apiUrl: string;
features: {
darkMode: boolean;
analytics: boolean;
};
}
export const APP_CONFIG = new InjectionToken<AppConfig>("APP_CONFIG");
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
// Token with factory (self-providing)
export const WINDOW = new InjectionToken<Window>("Window", {
providedIn: "root",
factory: () => window,
export const WINDOW = new InjectionToken<Window>('Window', {
providedIn: 'root',
factory: () => window,
});
export const LOCAL_STORAGE = new InjectionToken<Storage>("LocalStorage", {
providedIn: "root",
factory: () => localStorage,
export const LOCAL_STORAGE = new InjectionToken<Storage>('LocalStorage', {
providedIn: 'root',
factory: () => localStorage,
});
```
@@ -137,31 +137,31 @@ export const LOCAL_STORAGE = new InjectionToken<Storage>("LocalStorage", {
```typescript
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
{ provide: API_URL, useValue: "https://api.example.com" },
{
provide: APP_CONFIG,
useValue: {
apiUrl: "https://api.example.com",
features: { darkMode: true, analytics: true },
},
},
],
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
{
provide: APP_CONFIG,
useValue: {
apiUrl: 'https://api.example.com',
features: { darkMode: true, analytics: true },
},
},
],
};
```
### Injecting Tokens
```typescript
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class Api {
private apiUrl = inject(API_URL);
private config = inject(APP_CONFIG);
private window = inject(WINDOW);
private apiUrl = inject(API_URL);
private config = inject(APP_CONFIG);
private window = inject(WINDOW);
getBaseUrl(): string {
return this.apiUrl;
}
getBaseUrl(): string {
return this.apiUrl;
}
}
```
@@ -268,23 +268,23 @@ Collect multiple values for same token:
```typescript
// Token for multiple validators
export const VALIDATORS = new InjectionToken<Validator[]>("Validators");
export const VALIDATORS = new InjectionToken<Validator[]>('Validators');
// Provide multiple values
providers: [
{ provide: VALIDATORS, useClass: RequiredValidator, multi: true },
{ provide: VALIDATORS, useClass: EmailValidator, multi: true },
{ provide: VALIDATORS, useClass: MinLengthValidator, multi: true },
{ provide: VALIDATORS, useClass: RequiredValidator, multi: true },
{ provide: VALIDATORS, useClass: EmailValidator, multi: true },
{ provide: VALIDATORS, useClass: MinLengthValidator, multi: true },
];
// Inject as array
@Injectable()
export class Validation {
private validators = inject(VALIDATORS); // Validator[]
private validators = inject(VALIDATORS); // Validator[]
validate(value: string): ValidationError[] {
return this.validators.map((v) => v.validate(value)).filter(Boolean);
}
validate(value: string): ValidationError[] {
return this.validators.map((v) => v.validate(value)).filter(Boolean);
}
}
```
@@ -293,11 +293,7 @@ export class Validation {
```typescript
// Interceptors use multi providers internally
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor]),
),
],
providers: [provideHttpClient(withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor]))],
};
```
@@ -306,16 +302,16 @@ export const appConfig: ApplicationConfig = {
Run async code before app starts using `provideAppInitializer`:
```typescript
import { provideAppInitializer, inject } from "@angular/core";
import { provideAppInitializer, inject } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
Config,
provideAppInitializer(() => {
const configService = inject(Config);
return configService.loadConfig();
}),
],
providers: [
Config,
provideAppInitializer(() => {
const configService = inject(Config);
return configService.loadConfig();
}),
],
};
```
@@ -323,14 +319,14 @@ export const appConfig: ApplicationConfig = {
```typescript
providers: [
provideAppInitializer(() => {
const config = inject(Config);
return config.load();
}),
provideAppInitializer(() => {
const auth = inject(Auth);
return auth.checkSession();
}),
provideAppInitializer(() => {
const config = inject(Config);
return config.load();
}),
provideAppInitializer(() => {
const auth = inject(Auth);
return auth.checkSession();
}),
];
```
@@ -339,19 +335,15 @@ providers: [
Create injectors programmatically:
```typescript
import {
createEnvironmentInjector,
EnvironmentInjector,
inject,
} from "@angular/core";
import { createEnvironmentInjector, EnvironmentInjector, inject } from '@angular/core';
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class Plugin {
private parentInjector = inject(EnvironmentInjector);
private parentInjector = inject(EnvironmentInjector);
loadPlugin(providers: Provider[]): EnvironmentInjector {
return createEnvironmentInjector(providers, this.parentInjector);
}
loadPlugin(providers: Provider[]): EnvironmentInjector {
return createEnvironmentInjector(providers, this.parentInjector);
}
}
```
@@ -360,25 +352,21 @@ export class Plugin {
Run code with injection context:
```typescript
import {
runInInjectionContext,
EnvironmentInjector,
inject,
} from "@angular/core";
import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core';
@Injectable({ providedIn: "root" })
@Injectable({ providedIn: 'root' })
export class Utility {
private injector = inject(EnvironmentInjector);
private injector = inject(EnvironmentInjector);
executeWithDI<T>(fn: () => T): T {
return runInInjectionContext(this.injector, fn);
}
executeWithDI<T>(fn: () => T): T {
return runInInjectionContext(this.injector, fn);
}
}
// Usage
utilityService.executeWithDI(() => {
const http = inject(HttpClient);
// Use http...
const http = inject(HttpClient);
// Use http...
});
```