@smartsoft001-mobilems/claude-plugins 2.77.0 → 2.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugins/flow/.claude-plugin/plugin.json +1 -1
- package/plugins/flow-external/.claude-plugin/README.md +152 -0
- package/plugins/flow-external/.claude-plugin/merge-permissions.js +80 -0
- package/plugins/flow-external/.claude-plugin/plugin.json +5 -0
- package/plugins/flow-external/.claude-plugin/settings.template.json +75 -0
- package/plugins/flow-external/agents/angular-component-scaffolder.md +269 -0
- package/plugins/flow-external/agents/angular-directive-builder.md +130 -0
- package/plugins/flow-external/agents/angular-guard-builder.md +70 -0
- package/plugins/flow-external/agents/angular-pipe-builder.md +82 -0
- package/plugins/flow-external/agents/angular-resolver-builder.md +74 -0
- package/plugins/flow-external/agents/angular-service-builder.md +160 -0
- package/plugins/flow-external/agents/angular-state-builder.md +254 -0
- package/plugins/flow-external/agents/shared-build-verifier.md +67 -0
- package/plugins/flow-external/agents/shared-impl-orchestrator.md +88 -0
- package/plugins/flow-external/agents/shared-impl-reporter.md +107 -0
- package/plugins/flow-external/agents/shared-linear-subtask-iterator.md +70 -0
- package/plugins/flow-external/agents/shared-ui-classifier.md +38 -0
- package/plugins/flow-external/agents/ui-web-designer.md +179 -0
- package/plugins/flow-external/skills/a11y-audit/SKILL.md +96 -0
- package/plugins/flow-external/skills/angular-cli-conventions/SKILL.md +267 -0
- package/plugins/flow-external/skills/angular-patterns/SKILL.md +170 -0
- package/plugins/flow-external/skills/browser-capture/SKILL.md +68 -0
- package/plugins/flow-external/skills/commit/SKILL.md +113 -0
- package/plugins/flow-external/skills/debug-helper/SKILL.md +131 -0
- package/plugins/flow-external/skills/impl/SKILL.md +158 -0
- package/plugins/flow-external/skills/linear-suggestion/SKILL.md +62 -0
- package/plugins/flow-external/skills/maia-files-delete/SKILL.md +34 -0
- package/plugins/flow-external/skills/maia-files-upload/SKILL.md +41 -0
- package/plugins/flow-external/skills/plan/SKILL.md +163 -0
- package/plugins/flow-external/skills/push/SKILL.md +201 -0
- package/plugins/flow-legacy/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-directive-builder
|
|
3
|
+
description: Create Angular 6-11 directives with constructor DI. Use when building attribute or structural directives for standalone museum projects.
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular 6-11 directives for standalone museum projects.
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create Angular directives using Angular 6-11 patterns including constructor injection and `@Input()` decorators.
|
|
14
|
+
|
|
15
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
16
|
+
|
|
17
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 14+/20+) |
|
|
18
|
+
|---------|------------------------|-------------------------|
|
|
19
|
+
| DI | Constructor injection | `inject()` |
|
|
20
|
+
| Inputs | `@Input()` decorator | `input()` |
|
|
21
|
+
| Host binding | `@HostBinding()` | `host: {}` in decorator |
|
|
22
|
+
| Host listener | `@HostListener()` | `host: {}` in decorator |
|
|
23
|
+
|
|
24
|
+
## Directive Templates
|
|
25
|
+
|
|
26
|
+
### Attribute Directive
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import {
|
|
30
|
+
Directive,
|
|
31
|
+
ElementRef,
|
|
32
|
+
Input,
|
|
33
|
+
OnInit,
|
|
34
|
+
OnDestroy,
|
|
35
|
+
Renderer2,
|
|
36
|
+
HostBinding,
|
|
37
|
+
HostListener,
|
|
38
|
+
} from '@angular/core';
|
|
39
|
+
|
|
40
|
+
@Directive({
|
|
41
|
+
selector: '[appHighlight]',
|
|
42
|
+
})
|
|
43
|
+
export class HighlightDirective implements OnInit, OnDestroy {
|
|
44
|
+
@Input() appHighlight = '';
|
|
45
|
+
@Input() highlightColor = 'yellow';
|
|
46
|
+
|
|
47
|
+
@HostBinding('style.backgroundColor') backgroundColor: string | null = null;
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
private readonly el: ElementRef,
|
|
51
|
+
private readonly renderer: Renderer2
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
ngOnInit(): void {
|
|
55
|
+
this.setBackgroundColor(this.highlightColor);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ngOnDestroy(): void {}
|
|
59
|
+
|
|
60
|
+
@HostListener('mouseenter')
|
|
61
|
+
onMouseEnter(): void {
|
|
62
|
+
this.setBackgroundColor(this.appHighlight || this.highlightColor);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@HostListener('mouseleave')
|
|
66
|
+
onMouseLeave(): void {
|
|
67
|
+
this.setBackgroundColor(null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private setBackgroundColor(color: string | null): void {
|
|
71
|
+
this.backgroundColor = color;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## WCAG Accessibility Directive (Common Pattern)
|
|
77
|
+
|
|
78
|
+
These museum projects often implement WCAG accessibility directives:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { Directive, HostBinding, Input, OnInit } from '@angular/core';
|
|
82
|
+
|
|
83
|
+
@Directive({
|
|
84
|
+
selector: '[appWcagFontSize]',
|
|
85
|
+
})
|
|
86
|
+
export class WcagFontSizeDirective implements OnInit {
|
|
87
|
+
@Input() appWcagFontSize: 'normal' | 'large' | 'x-large' = 'normal';
|
|
88
|
+
|
|
89
|
+
@HostBinding('style.fontSize') fontSize: string;
|
|
90
|
+
|
|
91
|
+
constructor(private readonly wcagService: WcagService) {}
|
|
92
|
+
|
|
93
|
+
ngOnInit(): void {
|
|
94
|
+
this.wcagService.fontSize$.subscribe(size => {
|
|
95
|
+
this.fontSize = this.getSizeValue(size);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private getSizeValue(size: string): string {
|
|
100
|
+
switch (size) {
|
|
101
|
+
case 'large': return '120%';
|
|
102
|
+
case 'x-large': return '150%';
|
|
103
|
+
default: return '100%';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Module Declaration
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { NgModule } from '@angular/core';
|
|
113
|
+
|
|
114
|
+
import { HighlightDirective } from './highlight.directive';
|
|
115
|
+
import { WcagFontSizeDirective } from './wcag-font-size.directive';
|
|
116
|
+
|
|
117
|
+
@NgModule({
|
|
118
|
+
declarations: [HighlightDirective, WcagFontSizeDirective],
|
|
119
|
+
exports: [HighlightDirective, WcagFontSizeDirective],
|
|
120
|
+
})
|
|
121
|
+
export class DirectivesModule {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Checklist
|
|
125
|
+
|
|
126
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
127
|
+
- [ ] Uses `@Input()` decorator (NOT `input()`)
|
|
128
|
+
- [ ] Uses `@HostBinding()` and `@HostListener()`
|
|
129
|
+
- [ ] Implements `OnDestroy` for cleanup
|
|
130
|
+
- [ ] Declared in a module
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-guard-builder
|
|
3
|
+
description: Create Angular 6-11 class-based route guards with constructor DI. Use when building CanActivate, CanDeactivate, or other guards for standalone museum projects.
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular 6-11 route guards for standalone museum projects.
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create class-based route guards using Angular 6-11 patterns with constructor injection.
|
|
14
|
+
|
|
15
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
16
|
+
|
|
17
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 15+) |
|
|
18
|
+
|---------|------------------------|----------------------|
|
|
19
|
+
| Guard type | Class implementing interface | Functional guard |
|
|
20
|
+
| DI | Constructor injection | `inject()` |
|
|
21
|
+
|
|
22
|
+
## Guard Template
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Injectable } from '@angular/core';
|
|
26
|
+
import {
|
|
27
|
+
CanActivate,
|
|
28
|
+
ActivatedRouteSnapshot,
|
|
29
|
+
RouterStateSnapshot,
|
|
30
|
+
Router,
|
|
31
|
+
} from '@angular/router';
|
|
32
|
+
import { Observable } from 'rxjs';
|
|
33
|
+
import { map, take } from 'rxjs/operators';
|
|
34
|
+
|
|
35
|
+
@Injectable({
|
|
36
|
+
providedIn: 'root',
|
|
37
|
+
})
|
|
38
|
+
export class AuthGuard implements CanActivate {
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly authService: AuthService,
|
|
41
|
+
private readonly router: Router
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
canActivate(
|
|
45
|
+
route: ActivatedRouteSnapshot,
|
|
46
|
+
state: RouterStateSnapshot
|
|
47
|
+
): Observable<boolean> | boolean {
|
|
48
|
+
return this.authService.isAuthenticated$.pipe(
|
|
49
|
+
take(1),
|
|
50
|
+
map(isAuthenticated => {
|
|
51
|
+
if (isAuthenticated) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
|
|
55
|
+
return false;
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Note: Angular 6-7 guards often use `router.navigate()` instead of `UrlTree` (UrlTree was introduced in Angular 7.1).
|
|
63
|
+
|
|
64
|
+
## Checklist
|
|
65
|
+
|
|
66
|
+
- [ ] Uses class implementing guard interface (NOT functional guard)
|
|
67
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
68
|
+
- [ ] Uses `Observable<boolean>` or `boolean` return
|
|
69
|
+
- [ ] Uses `take(1)` for observable returns
|
|
70
|
+
- [ ] Has `providedIn: 'root'`
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-pipe-builder
|
|
3
|
+
description: Create Angular 6-11 pipes with constructor DI. Use when building transform pipes for templates in standalone museum projects.
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: haiku
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular 6-11 pipes for standalone museum projects.
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create Angular pipes using Angular 6-11 patterns including constructor injection for dependencies.
|
|
14
|
+
|
|
15
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
16
|
+
|
|
17
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 14+/20+) |
|
|
18
|
+
|---------|------------------------|-------------------------|
|
|
19
|
+
| DI | Constructor injection | `inject()` |
|
|
20
|
+
| Standalone | NO (use modules) | `standalone: true` |
|
|
21
|
+
|
|
22
|
+
## Pipe Templates
|
|
23
|
+
|
|
24
|
+
### Pure Pipe
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { Pipe, PipeTransform } from '@angular/core';
|
|
28
|
+
|
|
29
|
+
@Pipe({
|
|
30
|
+
name: 'truncate',
|
|
31
|
+
})
|
|
32
|
+
export class TruncatePipe implements PipeTransform {
|
|
33
|
+
transform(value: string | null | undefined, limit: number = 100, ellipsis: string = '...'): string {
|
|
34
|
+
if (!value) return '';
|
|
35
|
+
if (value.length <= limit) return value;
|
|
36
|
+
return value.substring(0, limit) + ellipsis;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Pipe with Service Injection
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { Pipe, PipeTransform } from '@angular/core';
|
|
45
|
+
import { TranslateService } from '@ngx-translate/core';
|
|
46
|
+
|
|
47
|
+
@Pipe({
|
|
48
|
+
name: 'localizedDate',
|
|
49
|
+
})
|
|
50
|
+
export class LocalizedDatePipe implements PipeTransform {
|
|
51
|
+
constructor(private readonly translateService: TranslateService) {}
|
|
52
|
+
|
|
53
|
+
transform(value: Date | string | null | undefined, format: string = 'short'): string {
|
|
54
|
+
if (!value) return '';
|
|
55
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
56
|
+
const locale = this.translateService.currentLang || 'pl';
|
|
57
|
+
return date.toLocaleDateString(locale);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Module Declaration
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { NgModule } from '@angular/core';
|
|
66
|
+
|
|
67
|
+
import { TruncatePipe } from './truncate.pipe';
|
|
68
|
+
import { LocalizedDatePipe } from './localized-date.pipe';
|
|
69
|
+
|
|
70
|
+
@NgModule({
|
|
71
|
+
declarations: [TruncatePipe, LocalizedDatePipe],
|
|
72
|
+
exports: [TruncatePipe, LocalizedDatePipe],
|
|
73
|
+
})
|
|
74
|
+
export class PipesModule {}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Checklist
|
|
78
|
+
|
|
79
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
80
|
+
- [ ] Implements `PipeTransform`
|
|
81
|
+
- [ ] Handles null/undefined input
|
|
82
|
+
- [ ] Declared in a module
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-resolver-builder
|
|
3
|
+
description: Create Angular 6-11 class-based route resolvers with constructor DI. Use when pre-fetching data for routes in standalone museum projects.
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular 6-11 route resolvers for standalone museum projects.
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create class-based route resolvers using Angular 6-11 patterns with constructor injection.
|
|
14
|
+
|
|
15
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
16
|
+
|
|
17
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 15+) |
|
|
18
|
+
|---------|------------------------|----------------------|
|
|
19
|
+
| Resolver type | Class implementing Resolve | Functional resolver |
|
|
20
|
+
| DI | Constructor injection | `inject()` |
|
|
21
|
+
|
|
22
|
+
## Resolver Template
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Injectable } from '@angular/core';
|
|
26
|
+
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
|
27
|
+
import { Observable } from 'rxjs';
|
|
28
|
+
|
|
29
|
+
@Injectable({
|
|
30
|
+
providedIn: 'root',
|
|
31
|
+
})
|
|
32
|
+
export class ArticleResolver implements Resolve<Article> {
|
|
33
|
+
constructor(private readonly articleService: ArticleService) {}
|
|
34
|
+
|
|
35
|
+
resolve(
|
|
36
|
+
route: ActivatedRouteSnapshot,
|
|
37
|
+
state: RouterStateSnapshot
|
|
38
|
+
): Observable<Article> {
|
|
39
|
+
const id = route.paramMap.get('id');
|
|
40
|
+
return this.articleService.getById(id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Routing Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
{
|
|
49
|
+
path: 'articles/:id',
|
|
50
|
+
component: ArticleDetailComponent,
|
|
51
|
+
resolve: { article: ArticleResolver }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Component Usage
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
constructor(private readonly route: ActivatedRoute) {}
|
|
59
|
+
|
|
60
|
+
ngOnInit(): void {
|
|
61
|
+
this.article = this.route.snapshot.data['article'];
|
|
62
|
+
// Or reactive:
|
|
63
|
+
this.route.data.pipe(map(data => data['article'])).subscribe(article => {
|
|
64
|
+
this.article = article;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Checklist
|
|
70
|
+
|
|
71
|
+
- [ ] Uses class implementing `Resolve<T>` (NOT functional resolver)
|
|
72
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
73
|
+
- [ ] Returns `Observable<T>`
|
|
74
|
+
- [ ] Has `providedIn: 'root'`
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-service-builder
|
|
3
|
+
description: Create Angular 6-11 injectable services with BehaviorSubject and constructor DI. Use when building services for standalone museum projects.
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular services for standalone museum projects (Angular 6-11).
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create Angular services using Angular 6-11 patterns with constructor injection, BehaviorSubject for state, and RxJS operators.
|
|
14
|
+
|
|
15
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
16
|
+
|
|
17
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 14+/20+) |
|
|
18
|
+
|---------|------------------------|-------------------------|
|
|
19
|
+
| DI | Constructor injection | `inject()` |
|
|
20
|
+
| State | BehaviorSubject | `signal()` |
|
|
21
|
+
| Derived | `pipe(map())` | `computed()` |
|
|
22
|
+
| Side effects | `tap()` in pipe | `effect()` |
|
|
23
|
+
|
|
24
|
+
## Service Templates
|
|
25
|
+
|
|
26
|
+
### Root-Level Service
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Injectable } from '@angular/core';
|
|
30
|
+
import { BehaviorSubject, Observable } from 'rxjs';
|
|
31
|
+
import { map, distinctUntilChanged } from 'rxjs/operators';
|
|
32
|
+
|
|
33
|
+
@Injectable({
|
|
34
|
+
providedIn: 'root',
|
|
35
|
+
})
|
|
36
|
+
export class FeatureService {
|
|
37
|
+
private readonly _items$ = new BehaviorSubject<Item[]>([]);
|
|
38
|
+
private readonly _isLoading$ = new BehaviorSubject<boolean>(false);
|
|
39
|
+
private readonly _error$ = new BehaviorSubject<string | null>(null);
|
|
40
|
+
|
|
41
|
+
readonly items$ = this._items$.asObservable();
|
|
42
|
+
readonly isLoading$ = this._isLoading$.asObservable();
|
|
43
|
+
readonly error$ = this._error$.asObservable();
|
|
44
|
+
|
|
45
|
+
readonly itemCount$ = this._items$.pipe(
|
|
46
|
+
map(items => items.length),
|
|
47
|
+
distinctUntilChanged()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
constructor(private readonly http: HttpClient) {}
|
|
51
|
+
|
|
52
|
+
loadItems(): void {
|
|
53
|
+
this._isLoading$.next(true);
|
|
54
|
+
this._error$.next(null);
|
|
55
|
+
|
|
56
|
+
this.http.get<Item[]>('/api/items').subscribe({
|
|
57
|
+
next: (items) => {
|
|
58
|
+
this._items$.next(items);
|
|
59
|
+
this._isLoading$.next(false);
|
|
60
|
+
},
|
|
61
|
+
error: (err) => {
|
|
62
|
+
this._error$.next(err.message);
|
|
63
|
+
this._isLoading$.next(false);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getItemById(id: string): Observable<Item | undefined> {
|
|
69
|
+
return this._items$.pipe(
|
|
70
|
+
map(items => items.find(item => item.id === id))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### HTTP Data Service
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Injectable } from '@angular/core';
|
|
80
|
+
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
81
|
+
import { Observable } from 'rxjs';
|
|
82
|
+
import { map } from 'rxjs/operators';
|
|
83
|
+
|
|
84
|
+
import { environment } from '../../environments/environment';
|
|
85
|
+
|
|
86
|
+
@Injectable({
|
|
87
|
+
providedIn: 'root',
|
|
88
|
+
})
|
|
89
|
+
export class ApiService {
|
|
90
|
+
private readonly baseUrl = environment.apiUrl;
|
|
91
|
+
|
|
92
|
+
constructor(private readonly http: HttpClient) {}
|
|
93
|
+
|
|
94
|
+
getAll<T>(endpoint: string, params?: Record<string, string>): Observable<T[]> {
|
|
95
|
+
let httpParams = new HttpParams();
|
|
96
|
+
if (params) {
|
|
97
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
98
|
+
httpParams = httpParams.set(key, value);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.http.get<T[]>(`${this.baseUrl}/${endpoint}`, { params: httpParams });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getById<T>(endpoint: string, id: string): Observable<T> {
|
|
106
|
+
return this.http.get<T>(`${this.baseUrl}/${endpoint}/${id}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Service with LocalStorage Sync
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Injectable } from '@angular/core';
|
|
115
|
+
import { BehaviorSubject, Observable } from 'rxjs';
|
|
116
|
+
|
|
117
|
+
@Injectable({
|
|
118
|
+
providedIn: 'root',
|
|
119
|
+
})
|
|
120
|
+
export class PreferencesService {
|
|
121
|
+
private readonly STORAGE_KEY = 'app_preferences';
|
|
122
|
+
private readonly _preferences$ = new BehaviorSubject<Preferences>(this.loadFromStorage());
|
|
123
|
+
|
|
124
|
+
readonly preferences$ = this._preferences$.asObservable();
|
|
125
|
+
|
|
126
|
+
updatePreferences(partial: Partial<Preferences>): void {
|
|
127
|
+
const current = this._preferences$.getValue();
|
|
128
|
+
const updated = { ...current, ...partial };
|
|
129
|
+
this._preferences$.next(updated);
|
|
130
|
+
this.saveToStorage(updated);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private loadFromStorage(): Preferences {
|
|
134
|
+
try {
|
|
135
|
+
const stored = localStorage.getItem(this.STORAGE_KEY);
|
|
136
|
+
return stored ? JSON.parse(stored) : this.getDefaults();
|
|
137
|
+
} catch {
|
|
138
|
+
return this.getDefaults();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private saveToStorage(prefs: Preferences): void {
|
|
143
|
+
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(prefs));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private getDefaults(): Preferences {
|
|
147
|
+
return { language: 'pl', theme: 'light', fontSize: 'medium' };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Checklist
|
|
153
|
+
|
|
154
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
155
|
+
- [ ] Uses BehaviorSubject for state (NOT signals)
|
|
156
|
+
- [ ] Uses `pipe(map())` for derived values (NOT `computed()`)
|
|
157
|
+
- [ ] Has `providedIn: 'root'` or is provided in module
|
|
158
|
+
- [ ] Uses environment.ts for API URLs
|
|
159
|
+
- [ ] Has proper TypeScript types
|
|
160
|
+
- [ ] Has proper error handling
|