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
continuous-integration/drone/pr Build is passing
This commit is contained in:
+104
-116
@@ -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...
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user