@malamute/ai-rules 1.0.0 → 1.3.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/README.md +272 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/_shared/rules/conventions/documentation.md +324 -0
- package/configs/_shared/rules/conventions/git.md +265 -0
- package/configs/_shared/rules/conventions/npm.md +80 -0
- package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
- package/configs/_shared/rules/conventions/principles.md +334 -0
- package/configs/_shared/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/rules/devops/docker.md +275 -0
- package/configs/_shared/rules/devops/nx.md +194 -0
- package/configs/_shared/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/rules/lang/python/async.md +337 -0
- package/configs/_shared/rules/lang/python/celery.md +476 -0
- package/configs/_shared/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/rules/lang/python/python.md +172 -0
- package/configs/_shared/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/rules/quality/error-handling.md +48 -0
- package/configs/_shared/rules/quality/logging.md +45 -0
- package/configs/_shared/rules/quality/observability.md +240 -0
- package/configs/_shared/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/rules/security/secrets-management.md +222 -0
- package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
- package/configs/angular/rules/core/resource.md +285 -0
- package/configs/angular/rules/core/signals.md +323 -0
- package/configs/angular/rules/http.md +338 -0
- package/configs/angular/rules/routing.md +291 -0
- package/configs/angular/rules/ssr.md +312 -0
- package/configs/angular/rules/state/signal-store.md +408 -0
- package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
- package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
- package/configs/angular/rules/ui/aria.md +422 -0
- package/configs/angular/rules/ui/forms.md +424 -0
- package/configs/angular/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/{.claude/settings.json → settings.json} +3 -0
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/dotnet/rules/background-services.md +552 -0
- package/configs/dotnet/rules/configuration.md +426 -0
- package/configs/dotnet/rules/ddd.md +447 -0
- package/configs/dotnet/rules/dependency-injection.md +343 -0
- package/configs/dotnet/rules/mediatr.md +320 -0
- package/configs/dotnet/rules/middleware.md +489 -0
- package/configs/dotnet/rules/result-pattern.md +363 -0
- package/configs/dotnet/rules/validation.md +388 -0
- package/configs/dotnet/settings.json +29 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/fastapi/rules/background-tasks.md +254 -0
- package/configs/fastapi/rules/dependencies.md +170 -0
- package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
- package/configs/fastapi/rules/lifespan.md +274 -0
- package/configs/fastapi/rules/middleware.md +229 -0
- package/configs/fastapi/rules/pydantic.md +433 -0
- package/configs/fastapi/rules/responses.md +251 -0
- package/configs/fastapi/rules/routers.md +202 -0
- package/configs/fastapi/rules/security.md +222 -0
- package/configs/fastapi/rules/testing.md +251 -0
- package/configs/fastapi/rules/websockets.md +298 -0
- package/configs/fastapi/settings.json +35 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/flask/rules/blueprints.md +208 -0
- package/configs/flask/rules/cli.md +285 -0
- package/configs/flask/rules/configuration.md +281 -0
- package/configs/flask/rules/context.md +238 -0
- package/configs/flask/rules/error-handlers.md +278 -0
- package/configs/flask/rules/extensions.md +278 -0
- package/configs/flask/rules/flask.md +171 -0
- package/configs/flask/rules/marshmallow.md +206 -0
- package/configs/flask/rules/security.md +267 -0
- package/configs/flask/rules/testing.md +284 -0
- package/configs/flask/settings.json +35 -0
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nestjs/rules/common-patterns.md +300 -0
- package/configs/nestjs/rules/filters.md +376 -0
- package/configs/nestjs/rules/interceptors.md +317 -0
- package/configs/nestjs/rules/middleware.md +321 -0
- package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
- package/configs/nestjs/rules/pipes.md +351 -0
- package/configs/nestjs/rules/websockets.md +451 -0
- package/configs/nestjs/settings.json +31 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/configs/nextjs/rules/api-routes.md +358 -0
- package/configs/nextjs/rules/authentication.md +355 -0
- package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
- package/configs/nextjs/rules/data-fetching.md +249 -0
- package/configs/nextjs/rules/database.md +400 -0
- package/configs/nextjs/rules/middleware.md +303 -0
- package/configs/nextjs/rules/routing.md +324 -0
- package/configs/nextjs/rules/seo.md +350 -0
- package/configs/nextjs/rules/server-actions.md +353 -0
- package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
- package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
- package/package.json +24 -9
- package/src/cli.js +218 -0
- package/src/config.js +63 -0
- package/src/index.js +4 -0
- package/src/installer.js +414 -0
- package/src/merge.js +109 -0
- package/src/tech-config.json +45 -0
- package/src/utils.js +88 -0
- package/configs/dotnet/.claude/settings.json +0 -9
- package/configs/nestjs/.claude/settings.json +0 -15
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
- /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.pipe.ts"
|
|
4
|
+
- "**/*.directive.ts"
|
|
5
|
+
- "**/pipes/**"
|
|
6
|
+
- "**/directives/**"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Angular Pipes & Directives
|
|
10
|
+
|
|
11
|
+
## Custom Pipes
|
|
12
|
+
|
|
13
|
+
### Pure Pipe (default, memoized)
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// pipes/time-ago.pipe.ts
|
|
17
|
+
import { Pipe, PipeTransform } from '@angular/core';
|
|
18
|
+
|
|
19
|
+
@Pipe({
|
|
20
|
+
name: 'timeAgo',
|
|
21
|
+
})
|
|
22
|
+
export class TimeAgoPipe implements PipeTransform {
|
|
23
|
+
public transform(value: Date | string | number): string {
|
|
24
|
+
const date = new Date(value);
|
|
25
|
+
const now = new Date();
|
|
26
|
+
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
27
|
+
|
|
28
|
+
if (seconds < 60) return 'just now';
|
|
29
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
|
|
30
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
|
|
31
|
+
if (seconds < 2592000) return `${Math.floor(seconds / 86400)} days ago`;
|
|
32
|
+
|
|
33
|
+
return date.toLocaleDateString();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Usage: {{ createdAt | timeAgo }}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Pipe with Parameters
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// pipes/truncate.pipe.ts
|
|
44
|
+
@Pipe({
|
|
45
|
+
name: 'truncate',
|
|
46
|
+
})
|
|
47
|
+
export class TruncatePipe implements PipeTransform {
|
|
48
|
+
public transform(
|
|
49
|
+
value: string,
|
|
50
|
+
limit: number = 100,
|
|
51
|
+
trail: string = '...'
|
|
52
|
+
): string {
|
|
53
|
+
if (!value || value.length <= limit) {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
return value.substring(0, limit).trim() + trail;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Usage: {{ description | truncate:50:'…' }}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Filter Pipe
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// pipes/filter.pipe.ts
|
|
67
|
+
@Pipe({
|
|
68
|
+
name: 'filter',
|
|
69
|
+
})
|
|
70
|
+
export class FilterPipe implements PipeTransform {
|
|
71
|
+
public transform<T>(
|
|
72
|
+
items: T[],
|
|
73
|
+
field: keyof T,
|
|
74
|
+
value: unknown
|
|
75
|
+
): T[] {
|
|
76
|
+
if (!items || !field || value === undefined) {
|
|
77
|
+
return items;
|
|
78
|
+
}
|
|
79
|
+
return items.filter(item => item[field] === value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Usage: @for (user of users() | filter:'status':'active'; track user.id)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Safe HTML Pipe
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// pipes/safe-html.pipe.ts
|
|
90
|
+
import { Pipe, PipeTransform, inject } from '@angular/core';
|
|
91
|
+
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
92
|
+
|
|
93
|
+
@Pipe({
|
|
94
|
+
name: 'safeHtml',
|
|
95
|
+
})
|
|
96
|
+
export class SafeHtmlPipe implements PipeTransform {
|
|
97
|
+
private readonly sanitizer = inject(DomSanitizer);
|
|
98
|
+
|
|
99
|
+
public transform(value: string): SafeHtml {
|
|
100
|
+
return this.sanitizer.bypassSecurityTrustHtml(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Usage: <div [innerHTML]="content | safeHtml"></div>
|
|
105
|
+
// ⚠️ Only use with trusted content!
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Custom Directives
|
|
109
|
+
|
|
110
|
+
### Attribute Directive
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// directives/highlight.directive.ts
|
|
114
|
+
import { Directive, ElementRef, HostListener, input, inject } from '@angular/core';
|
|
115
|
+
|
|
116
|
+
@Directive({
|
|
117
|
+
selector: '[appHighlight]',
|
|
118
|
+
})
|
|
119
|
+
export class HighlightDirective {
|
|
120
|
+
private readonly el = inject(ElementRef);
|
|
121
|
+
|
|
122
|
+
public readonly appHighlight = input<string>('#ffff00');
|
|
123
|
+
public readonly defaultColor = input<string>('transparent');
|
|
124
|
+
|
|
125
|
+
@HostListener('mouseenter')
|
|
126
|
+
public onMouseEnter(): void {
|
|
127
|
+
this.highlight(this.appHighlight() || '#ffff00');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@HostListener('mouseleave')
|
|
131
|
+
public onMouseLeave(): void {
|
|
132
|
+
this.highlight(this.defaultColor());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private highlight(color: string): void {
|
|
136
|
+
this.el.nativeElement.style.backgroundColor = color;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Usage: <p appHighlight="#e0e0e0">Hover me</p>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Click Outside Directive
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// directives/click-outside.directive.ts
|
|
147
|
+
import { Directive, ElementRef, output, inject } from '@angular/core';
|
|
148
|
+
import { fromEvent } from 'rxjs';
|
|
149
|
+
import { filter } from 'rxjs/operators';
|
|
150
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
151
|
+
|
|
152
|
+
@Directive({
|
|
153
|
+
selector: '[appClickOutside]',
|
|
154
|
+
})
|
|
155
|
+
export class ClickOutsideDirective {
|
|
156
|
+
private readonly el = inject(ElementRef);
|
|
157
|
+
|
|
158
|
+
public readonly appClickOutside = output<void>();
|
|
159
|
+
|
|
160
|
+
constructor() {
|
|
161
|
+
fromEvent<MouseEvent>(document, 'click')
|
|
162
|
+
.pipe(
|
|
163
|
+
filter(event => !this.el.nativeElement.contains(event.target)),
|
|
164
|
+
takeUntilDestroyed(),
|
|
165
|
+
)
|
|
166
|
+
.subscribe(() => this.appClickOutside.emit());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Usage: <div appClickOutside (appClickOutside)="closeDropdown()">
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Auto Focus Directive
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// directives/auto-focus.directive.ts
|
|
177
|
+
import { Directive, ElementRef, AfterViewInit, input, inject } from '@angular/core';
|
|
178
|
+
|
|
179
|
+
@Directive({
|
|
180
|
+
selector: '[appAutoFocus]',
|
|
181
|
+
})
|
|
182
|
+
export class AutoFocusDirective implements AfterViewInit {
|
|
183
|
+
private readonly el = inject(ElementRef);
|
|
184
|
+
|
|
185
|
+
public readonly appAutoFocus = input<boolean>(true);
|
|
186
|
+
|
|
187
|
+
public ngAfterViewInit(): void {
|
|
188
|
+
if (this.appAutoFocus()) {
|
|
189
|
+
setTimeout(() => this.el.nativeElement.focus(), 0);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Usage: <input appAutoFocus />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Structural Directive
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// directives/permission.directive.ts
|
|
201
|
+
import { Directive, TemplateRef, ViewContainerRef, input, effect, inject } from '@angular/core';
|
|
202
|
+
import { AuthService } from '../services/auth.service';
|
|
203
|
+
|
|
204
|
+
@Directive({
|
|
205
|
+
selector: '[appHasPermission]',
|
|
206
|
+
})
|
|
207
|
+
export class HasPermissionDirective {
|
|
208
|
+
private readonly templateRef = inject(TemplateRef<unknown>);
|
|
209
|
+
private readonly viewContainer = inject(ViewContainerRef);
|
|
210
|
+
private readonly authService = inject(AuthService);
|
|
211
|
+
|
|
212
|
+
public readonly appHasPermission = input.required<string | string[]>();
|
|
213
|
+
|
|
214
|
+
private hasView = false;
|
|
215
|
+
|
|
216
|
+
constructor() {
|
|
217
|
+
effect(() => {
|
|
218
|
+
const permissions = this.appHasPermission();
|
|
219
|
+
const permissionArray = Array.isArray(permissions) ? permissions : [permissions];
|
|
220
|
+
const hasPermission = this.authService.hasAnyPermission(permissionArray);
|
|
221
|
+
|
|
222
|
+
if (hasPermission && !this.hasView) {
|
|
223
|
+
this.viewContainer.createEmbeddedView(this.templateRef);
|
|
224
|
+
this.hasView = true;
|
|
225
|
+
} else if (!hasPermission && this.hasView) {
|
|
226
|
+
this.viewContainer.clear();
|
|
227
|
+
this.hasView = false;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Usage: <button *appHasPermission="'users.create'">Create User</button>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Debounce Input Directive
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// directives/debounce-input.directive.ts
|
|
240
|
+
import { Directive, ElementRef, output, input, inject, OnInit, DestroyRef } from '@angular/core';
|
|
241
|
+
import { fromEvent, debounceTime, distinctUntilChanged, map } from 'rxjs';
|
|
242
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
243
|
+
|
|
244
|
+
@Directive({
|
|
245
|
+
selector: 'input[appDebounce]',
|
|
246
|
+
})
|
|
247
|
+
export class DebounceInputDirective implements OnInit {
|
|
248
|
+
private readonly el = inject(ElementRef<HTMLInputElement>);
|
|
249
|
+
private readonly destroyRef = inject(DestroyRef);
|
|
250
|
+
|
|
251
|
+
public readonly appDebounce = input<number>(300);
|
|
252
|
+
public readonly debounceValue = output<string>();
|
|
253
|
+
|
|
254
|
+
public ngOnInit(): void {
|
|
255
|
+
fromEvent<Event>(this.el.nativeElement, 'input')
|
|
256
|
+
.pipe(
|
|
257
|
+
map(event => (event.target as HTMLInputElement).value),
|
|
258
|
+
debounceTime(this.appDebounce()),
|
|
259
|
+
distinctUntilChanged(),
|
|
260
|
+
takeUntilDestroyed(this.destroyRef),
|
|
261
|
+
)
|
|
262
|
+
.subscribe(value => this.debounceValue.emit(value));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Usage: <input appDebounce [appDebounce]="500" (debounceValue)="onSearch($event)" />
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Composition
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// components/user-list.component.ts
|
|
273
|
+
@Component({
|
|
274
|
+
selector: 'app-user-list',
|
|
275
|
+
imports: [
|
|
276
|
+
TimeAgoPipe,
|
|
277
|
+
TruncatePipe,
|
|
278
|
+
FilterPipe,
|
|
279
|
+
HighlightDirective,
|
|
280
|
+
HasPermissionDirective,
|
|
281
|
+
],
|
|
282
|
+
template: `
|
|
283
|
+
@for (user of users() | filter:'status':'active'; track user.id) {
|
|
284
|
+
<div appHighlight="#f0f0f0">
|
|
285
|
+
<h3>{{ user.name }}</h3>
|
|
286
|
+
<p>{{ user.bio | truncate:100 }}</p>
|
|
287
|
+
<span>{{ user.createdAt | timeAgo }}</span>
|
|
288
|
+
|
|
289
|
+
<button *appHasPermission="'users.delete'">Delete</button>
|
|
290
|
+
</div>
|
|
291
|
+
}
|
|
292
|
+
`,
|
|
293
|
+
})
|
|
294
|
+
export class UserListComponent {
|
|
295
|
+
protected readonly users = input.required<User[]>();
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Anti-patterns
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// BAD: Impure pipe for filtering (causes performance issues)
|
|
303
|
+
@Pipe({ name: 'filter', pure: false })
|
|
304
|
+
|
|
305
|
+
// GOOD: Use pure pipe (default) + signal for reactivity
|
|
306
|
+
@Pipe({ name: 'filter' })
|
|
307
|
+
// And update source data via signals
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
// BAD: Direct DOM manipulation
|
|
311
|
+
this.el.nativeElement.innerHTML = '<b>text</b>';
|
|
312
|
+
|
|
313
|
+
// GOOD: Use Renderer2 or Angular bindings
|
|
314
|
+
@HostBinding('innerHTML') content = '<b>text</b>';
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
// BAD: Adding standalone: true (it's the default since Angular 19)
|
|
318
|
+
@Pipe({ name: 'myPipe', standalone: true })
|
|
319
|
+
|
|
320
|
+
// GOOD: Omit standalone (defaults to true)
|
|
321
|
+
@Pipe({ name: 'myPipe' })
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
// BAD: Using @Input() in directives
|
|
325
|
+
@Directive({ selector: '[appHighlight]' })
|
|
326
|
+
export class HighlightDirective {
|
|
327
|
+
@Input() appHighlight: string; // Use input() instead
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// GOOD: Use signal inputs
|
|
331
|
+
@Directive({ selector: '[appHighlight]' })
|
|
332
|
+
export class HighlightDirective {
|
|
333
|
+
public readonly appHighlight = input<string>();
|
|
334
|
+
}
|
|
335
|
+
```
|