@aliaksei-raketski/pi-angular-developer 0.1.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 +44 -0
- package/overlays/angular-developer/references/docs-helpers.md +36 -0
- package/overlays/angular-developer/scripts/get-best-practices.mjs +268 -0
- package/overlays/angular-developer/scripts/search-documentation.mjs +396 -0
- package/package.json +41 -0
- package/scripts/sync-angular-skill.mjs +307 -0
- package/skills/angular-developer/SKILL.md +133 -0
- package/skills/angular-developer/UPSTREAM.md +9 -0
- package/skills/angular-developer/data/best-practices.md +56 -0
- package/skills/angular-developer/references/angular-animations.md +160 -0
- package/skills/angular-developer/references/angular-aria.md +597 -0
- package/skills/angular-developer/references/cli.md +86 -0
- package/skills/angular-developer/references/component-harnesses.md +57 -0
- package/skills/angular-developer/references/component-styling.md +91 -0
- package/skills/angular-developer/references/components.md +117 -0
- package/skills/angular-developer/references/creating-services.md +97 -0
- package/skills/angular-developer/references/data-resolvers.md +69 -0
- package/skills/angular-developer/references/define-routes.md +67 -0
- package/skills/angular-developer/references/defining-providers.md +72 -0
- package/skills/angular-developer/references/di-fundamentals.md +118 -0
- package/skills/angular-developer/references/docs-helpers.md +36 -0
- package/skills/angular-developer/references/e2e-testing.md +66 -0
- package/skills/angular-developer/references/effects.md +83 -0
- package/skills/angular-developer/references/environment-configuration.md +132 -0
- package/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/skills/angular-developer/references/host-elements.md +80 -0
- package/skills/angular-developer/references/injection-context.md +63 -0
- package/skills/angular-developer/references/inputs.md +101 -0
- package/skills/angular-developer/references/linked-signal.md +59 -0
- package/skills/angular-developer/references/loading-strategies.md +61 -0
- package/skills/angular-developer/references/migrations.md +30 -0
- package/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/skills/angular-developer/references/outputs.md +86 -0
- package/skills/angular-developer/references/reactive-forms.md +118 -0
- package/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/skills/angular-developer/references/resource.md +74 -0
- package/skills/angular-developer/references/route-animations.md +56 -0
- package/skills/angular-developer/references/route-guards.md +52 -0
- package/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/skills/angular-developer/references/router-testing.md +87 -0
- package/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/skills/angular-developer/references/signal-forms.md +907 -0
- package/skills/angular-developer/references/signals-overview.md +94 -0
- package/skills/angular-developer/references/tailwind-css.md +69 -0
- package/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/skills/angular-developer/references/testing-fundamentals.md +63 -0
- package/skills/angular-developer/scripts/get-best-practices.mjs +268 -0
- package/skills/angular-developer/scripts/search-documentation.mjs +396 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import {spawnSync} from 'node:child_process';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
|
|
9
|
+
const UPSTREAM_REPO = 'https://github.com/angular/skills';
|
|
10
|
+
const DEFAULT_REF = '28a90e30bba8cbc9d3a6aab56093e5b9b974bc9e';
|
|
11
|
+
const KNOWN_OVERLAY_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'overlays', 'angular-developer');
|
|
12
|
+
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const TARGET_SKILL_DIR = path.join(PKG_ROOT, 'skills', 'angular-developer');
|
|
14
|
+
const TARGET_UPSTREAM_FILE = path.join(TARGET_SKILL_DIR, 'UPSTREAM.md');
|
|
15
|
+
const TARGET_DATA_FILE = path.join(TARGET_SKILL_DIR, 'data', 'best-practices.md');
|
|
16
|
+
const STALE_PATTERNS = [
|
|
17
|
+
/find_examples/i,
|
|
18
|
+
/get_best_practices/i,
|
|
19
|
+
/search_documentation/i,
|
|
20
|
+
/references\/mcp\.md/i,
|
|
21
|
+
/Angular MCP Server/i,
|
|
22
|
+
/\bMCP\b/i,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
main().catch((error) => {
|
|
26
|
+
console.error(`Fatal: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
const requestedRef = process.env.ANGULAR_SKILLS_REF || DEFAULT_REF;
|
|
32
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'angular-skills-'));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const checkout = await syncUpstreamSource(tempDir, requestedRef, requestedRef === DEFAULT_REF);
|
|
36
|
+
await copySkillFromUpstream(checkout.repoDir);
|
|
37
|
+
await applyOverlayAndMetadata(checkout, requestedRef);
|
|
38
|
+
await sanitizeVendoredContent(TARGET_SKILL_DIR);
|
|
39
|
+
|
|
40
|
+
await refreshBestPractices(TARGET_SKILL_DIR);
|
|
41
|
+
await validateNoStaleMcpReferences(TARGET_SKILL_DIR);
|
|
42
|
+
|
|
43
|
+
console.log(`Synced angular-developer skill to ${checkout.resolvedRef} (${checkout.requestedRef}).`);
|
|
44
|
+
} finally {
|
|
45
|
+
await fs.rm(tempDir, {recursive: true, force: true});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function syncUpstreamSource(tempDir, requestedRef, allowFallback = false) {
|
|
50
|
+
const cloneDir = path.join(tempDir, 'angular-skills');
|
|
51
|
+
|
|
52
|
+
run('git', ['clone', '--depth', '1', '--filter=blob:none', UPSTREAM_REPO, cloneDir]);
|
|
53
|
+
|
|
54
|
+
let resolvedRef;
|
|
55
|
+
let usedFallback = false;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
run('git', ['-C', cloneDir, 'checkout', '--quiet', requestedRef]);
|
|
59
|
+
resolvedRef = revParse(cloneDir, 'HEAD');
|
|
60
|
+
} catch {
|
|
61
|
+
try {
|
|
62
|
+
run('git', ['-C', cloneDir, 'fetch', '--depth', '1', 'origin', requestedRef]);
|
|
63
|
+
run('git', ['-C', cloneDir, 'checkout', '--quiet', 'FETCH_HEAD']);
|
|
64
|
+
resolvedRef = revParse(cloneDir, 'HEAD');
|
|
65
|
+
console.error(`Info: checked out requested ref ${requestedRef} after remote fetch.`);
|
|
66
|
+
} catch {
|
|
67
|
+
if (!allowFallback) {
|
|
68
|
+
throw new Error(`Unable to resolve requested ref ${requestedRef}.`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
usedFallback = true;
|
|
72
|
+
console.error(`Warning: could not resolve ref ${requestedRef}; falling back to origin/main.`);
|
|
73
|
+
run('git', ['-C', cloneDir, 'fetch', '--depth', '1', 'origin', 'main:refs/remotes/origin/main']);
|
|
74
|
+
run('git', ['-C', cloneDir, 'checkout', '--quiet', 'origin/main']);
|
|
75
|
+
resolvedRef = revParse(cloneDir, 'HEAD');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
repoDir: cloneDir,
|
|
81
|
+
requestedRef,
|
|
82
|
+
resolvedRef,
|
|
83
|
+
usedFallback,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function copySkillFromUpstream(repoDir) {
|
|
88
|
+
const sourceDir = path.join(repoDir, 'angular-developer');
|
|
89
|
+
|
|
90
|
+
if (!(await exists(sourceDir))) {
|
|
91
|
+
throw new Error(`Expected upstream directory ${sourceDir} not found.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await fs.rm(TARGET_SKILL_DIR, {recursive: true, force: true});
|
|
95
|
+
await fs.mkdir(path.dirname(TARGET_SKILL_DIR), {recursive: true});
|
|
96
|
+
await fs.cp(sourceDir, TARGET_SKILL_DIR, {recursive: true});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function applyOverlayAndMetadata(meta) {
|
|
100
|
+
const skillPath = path.join(TARGET_SKILL_DIR, 'SKILL.md');
|
|
101
|
+
let skillContent = await fs.readFile(skillPath, 'utf8');
|
|
102
|
+
|
|
103
|
+
const mcpToolingLine =
|
|
104
|
+
'- **Angular MCP Server**: Available tools, configuration, and experimental features. Read [mcp.md](references/mcp.md)';
|
|
105
|
+
const mcpToolingReplacement =
|
|
106
|
+
'- **Angular documentation helpers**: Use local helper scripts for version-aware best practices and official angular.dev documentation search. Read [docs-helpers.md](references/docs-helpers.md)';
|
|
107
|
+
|
|
108
|
+
if (!skillContent.includes(mcpToolingReplacement)) {
|
|
109
|
+
if (!skillContent.includes(mcpToolingLine)) {
|
|
110
|
+
throw new Error('Expected MCP tooling line was not found in upstream SKILL.md.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
skillContent = skillContent.replace(mcpToolingLine, mcpToolingReplacement);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const topRule =
|
|
117
|
+
'4. When you need version-aware Angular best practices or official angular.dev documentation search, use the local helper scripts documented in [docs-helpers.md](references/docs-helpers.md).';
|
|
118
|
+
|
|
119
|
+
if (!skillContent.includes(topRule)) {
|
|
120
|
+
skillContent = skillContent.replace(
|
|
121
|
+
'\n## Creating New Projects\n',
|
|
122
|
+
`\n${topRule}\n\n## Creating New Projects\n`,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!skillContent.includes(topRule)) {
|
|
126
|
+
throw new Error('Failed to insert local helper rule in SKILL.md.');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await fs.writeFile(skillPath, skillContent, 'utf8');
|
|
131
|
+
|
|
132
|
+
await removeIfExists(path.join(TARGET_SKILL_DIR, 'references', 'mcp.md'));
|
|
133
|
+
|
|
134
|
+
const overlayReference = path.join(KNOWN_OVERLAY_PATH, 'references', 'docs-helpers.md');
|
|
135
|
+
await ensureDir(path.join(TARGET_SKILL_DIR, 'references'));
|
|
136
|
+
await fs.copyFile(overlayReference, path.join(TARGET_SKILL_DIR, 'references', 'docs-helpers.md'));
|
|
137
|
+
|
|
138
|
+
const overlayScripts = path.join(KNOWN_OVERLAY_PATH, 'scripts');
|
|
139
|
+
const targetScripts = path.join(TARGET_SKILL_DIR, 'scripts');
|
|
140
|
+
await ensureDir(targetScripts);
|
|
141
|
+
|
|
142
|
+
const scriptFiles = await fs.readdir(overlayScripts);
|
|
143
|
+
for (const file of scriptFiles) {
|
|
144
|
+
await fs.copyFile(path.join(overlayScripts, file), path.join(targetScripts, file));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const timestamp = new Date().toISOString();
|
|
148
|
+
const sourceRef = `resolved ref ${meta.resolvedRef}`;
|
|
149
|
+
|
|
150
|
+
const upstreamContent = `# Angular Developer upstream sync metadata
|
|
151
|
+
|
|
152
|
+
Repository: ${UPSTREAM_REPO}
|
|
153
|
+
Synced at: ${timestamp}
|
|
154
|
+
Requested ref: ${meta.requestedRef}
|
|
155
|
+
Synced ref: ${meta.resolvedRef}
|
|
156
|
+
Fallback used: ${meta.usedFallback ? 'yes' : 'no'}
|
|
157
|
+
Upstream source: ${sourceRef}
|
|
158
|
+
Local overlay source: overlays/angular-developer/
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
await fs.writeFile(TARGET_UPSTREAM_FILE, upstreamContent, 'utf8');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function sanitizeVendoredContent(skillDir) {
|
|
165
|
+
const e2eReference = path.join(skillDir, 'references', 'e2e-testing.md');
|
|
166
|
+
if (await exists(e2eReference)) {
|
|
167
|
+
const content = await fs.readFile(e2eReference, 'utf8');
|
|
168
|
+
const sanitized = content.replace(/ng-devtools-mcp/g, 'ng-devtools');
|
|
169
|
+
if (sanitized !== content) {
|
|
170
|
+
await fs.writeFile(e2eReference, sanitized, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function refreshBestPractices(skillDir) {
|
|
176
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'angular-core-'));
|
|
177
|
+
try {
|
|
178
|
+
const packResult = run('npm', ['pack', '@angular/core@latest', '--silent', '--json'], {cwd: tempDir});
|
|
179
|
+
const packEntries = JSON.parse(packResult);
|
|
180
|
+
|
|
181
|
+
if (!Array.isArray(packEntries) || !packEntries.length) {
|
|
182
|
+
throw new Error('npm pack output did not contain package metadata.');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const entry = packEntries[0];
|
|
186
|
+
if (typeof entry.filename !== 'string') {
|
|
187
|
+
throw new Error('npm pack output missing filename.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const tarballPath = path.join(tempDir, entry.filename);
|
|
191
|
+
const packageJsonText = run('tar', ['-xOf', tarballPath, 'package/package.json']);
|
|
192
|
+
const packageJson = JSON.parse(packageJsonText);
|
|
193
|
+
|
|
194
|
+
const bestPractices = packageJson?.angular?.bestPractices;
|
|
195
|
+
if (!bestPractices || bestPractices.format !== 'markdown' || typeof bestPractices.path !== 'string') {
|
|
196
|
+
throw new Error('Invalid @angular/core best-practices metadata.');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const normalizedPath = normalizePackagePath(bestPractices.path);
|
|
200
|
+
const markdownText = run('tar', ['-xOf', tarballPath, `package/${normalizedPath}`]);
|
|
201
|
+
|
|
202
|
+
await ensureDir(path.join(skillDir, 'data'));
|
|
203
|
+
await fs.writeFile(TARGET_DATA_FILE, markdownText, 'utf8');
|
|
204
|
+
console.log(`Updated data/best-practices.md from ${packageJson.version ?? 'latest @angular/core'}.`);
|
|
205
|
+
} finally {
|
|
206
|
+
await fs.rm(tempDir, {recursive: true, force: true});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizePackagePath(rawPath) {
|
|
211
|
+
const normalized = path.posix.normalize(rawPath.replace(/^\/+/, ''));
|
|
212
|
+
|
|
213
|
+
if (normalized.startsWith('../') || normalized.includes('/../') || path.posix.isAbsolute(normalized)) {
|
|
214
|
+
throw new Error(`Unsafe best-practices path in package metadata: ${rawPath}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return normalized;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function validateNoStaleMcpReferences(skillDir) {
|
|
221
|
+
const staleMatches = [];
|
|
222
|
+
|
|
223
|
+
const paths = await gatherFiles(skillDir);
|
|
224
|
+
for (const filePath of paths) {
|
|
225
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
226
|
+
if (!['.md', '.mjs', '.json'].includes(ext)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const text = await fs.readFile(filePath, 'utf8');
|
|
231
|
+
|
|
232
|
+
for (const pattern of STALE_PATTERNS) {
|
|
233
|
+
if (pattern.test(text)) {
|
|
234
|
+
staleMatches.push(`${path.relative(PKG_ROOT, filePath)}: ${pattern.source}`);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (staleMatches.length > 0) {
|
|
241
|
+
throw new Error(`Found stale MCP references in vendored skill:\n${staleMatches.join('\n')}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('Validation passed: no stale MCP references found.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function gatherFiles(root) {
|
|
248
|
+
const results = [];
|
|
249
|
+
const entries = await fs.readdir(root, {withFileTypes: true});
|
|
250
|
+
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
const fullPath = path.join(root, entry.name);
|
|
253
|
+
|
|
254
|
+
if (entry.isDirectory()) {
|
|
255
|
+
results.push(...(await gatherFiles(fullPath)));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (entry.isFile()) {
|
|
260
|
+
results.push(fullPath);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function revParse(repoDir, rev) {
|
|
268
|
+
return run('git', ['-C', repoDir, 'rev-parse', rev]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function run(command, args, options = {}) {
|
|
272
|
+
const {cwd} = options;
|
|
273
|
+
const result = spawnSync(command, args, {
|
|
274
|
+
cwd,
|
|
275
|
+
encoding: 'utf8',
|
|
276
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (result.error) {
|
|
280
|
+
throw new Error(result.error.message);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (result.status !== 0) {
|
|
284
|
+
const stderr = String(result.stderr || '').trim();
|
|
285
|
+
const prefix = stderr ? `: ${stderr}` : '';
|
|
286
|
+
throw new Error(`${command} ${args.join(' ')} failed with code ${result.status}${prefix}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return String(result.stdout || '').trim();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function exists(filePath) {
|
|
293
|
+
try {
|
|
294
|
+
await fs.access(filePath);
|
|
295
|
+
return true;
|
|
296
|
+
} catch {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function removeIfExists(target) {
|
|
302
|
+
await fs.rm(target, {recursive: true, force: true});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function ensureDir(target) {
|
|
306
|
+
await fs.mkdir(target, {recursive: true});
|
|
307
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-developer
|
|
3
|
+
description: Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: Copyright 2026 Google LLC
|
|
7
|
+
version: '1.0'
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Angular Developer Guidelines
|
|
11
|
+
|
|
12
|
+
1. Always analyze the project's Angular version before providing guidance, as best practices and available features can vary significantly between versions. If creating a new project with Angular CLI, do not specify a version unless prompted by the user.
|
|
13
|
+
|
|
14
|
+
2. When generating code, follow Angular's style guide and best practices for maintainability and performance. Use the Angular CLI for scaffolding components, services, directives, pipes, and routes to ensure consistency.
|
|
15
|
+
|
|
16
|
+
3. Once you finish generating code, run `ng build` to ensure there are no build errors. If there are errors, analyze the error messages and fix them before proceeding. Do not skip this step, as it is critical for ensuring the generated code is correct and functional.
|
|
17
|
+
|
|
18
|
+
4. When you need version-aware Angular best practices or official angular.dev documentation search, use the local helper scripts documented in [docs-helpers.md](references/docs-helpers.md).
|
|
19
|
+
|
|
20
|
+
## Creating New Projects
|
|
21
|
+
|
|
22
|
+
If no guidelines are provided by the user, here are same default rules to follow when creating a new Angular project:
|
|
23
|
+
|
|
24
|
+
1. Use the latest stable version of Angular unless the user specifies otherwise.
|
|
25
|
+
2. Use Signals Forms for form management in new projects (available in Angular v21 and newer) [Find out more](references/signal-forms.md).
|
|
26
|
+
|
|
27
|
+
**Execution Rules for `ng new`:**
|
|
28
|
+
When asked to create a new Angular project, you must determine the correct execution command by following these strict steps:
|
|
29
|
+
|
|
30
|
+
**Step 1: Check for an explicit user version.**
|
|
31
|
+
|
|
32
|
+
- **IF** the user requests a specific version (e.g., Angular 15), bypass local installations and strictly use `npx`.
|
|
33
|
+
- **Command:** `npx @angular/cli@<requested_version> new <project-name>`
|
|
34
|
+
|
|
35
|
+
**Step 2: Check for an existing Angular installation.**
|
|
36
|
+
|
|
37
|
+
- **IF** no specific version is requested, run `ng version` in the terminal to check if the Angular CLI is already installed on the system.
|
|
38
|
+
- **IF** the command succeeds and returns an installed version, use the local/global installation directly.
|
|
39
|
+
- **Command:** `ng new <project-name>`
|
|
40
|
+
|
|
41
|
+
**Step 3: Fallback to Latest.**
|
|
42
|
+
|
|
43
|
+
- **IF** no specific version is requested AND the `ng version` command fails (indicating no Angular installation exists), you must use `npx` to fetch the latest version.
|
|
44
|
+
- **Command:** `npx @angular/cli@latest new <project-name>`
|
|
45
|
+
|
|
46
|
+
## Components
|
|
47
|
+
|
|
48
|
+
When working with Angular components, consult the following references based on the task:
|
|
49
|
+
|
|
50
|
+
- **Fundamentals**: Anatomy, metadata, core concepts, and template control flow (@if, @for, @switch). Read [components.md](references/components.md)
|
|
51
|
+
- **Inputs**: Signal-based inputs, transforms, and model inputs. Read [inputs.md](references/inputs.md)
|
|
52
|
+
- **Outputs**: Signal-based outputs and custom event best practices. Read [outputs.md](references/outputs.md)
|
|
53
|
+
- **Host Elements**: Host bindings and attribute injection. Read [host-elements.md](references/host-elements.md)
|
|
54
|
+
|
|
55
|
+
If you require deeper documentation not found in the references above, read the documentation at `https://angular.dev/guide/components`.
|
|
56
|
+
|
|
57
|
+
## Reactivity and Data Management
|
|
58
|
+
|
|
59
|
+
When managing state and data reactivity, use Angular Signals and consult the following references:
|
|
60
|
+
|
|
61
|
+
- **Signals Overview**: Core signal concepts (`signal`, `computed`), reactive contexts, and `untracked`. Read [signals-overview.md](references/signals-overview.md)
|
|
62
|
+
- **Dependent State (`linkedSignal`)**: Creating writable state linked to source signals. Read [linked-signal.md](references/linked-signal.md)
|
|
63
|
+
- **Async Reactivity (`resource`)**: Fetching asynchronous data directly into signal state. Read [resource.md](references/resource.md)
|
|
64
|
+
- **Side Effects (`effect`)**: Logging, third-party DOM manipulation (`afterRenderEffect`), and when NOT to use effects. Read [effects.md](references/effects.md)
|
|
65
|
+
|
|
66
|
+
## Forms
|
|
67
|
+
|
|
68
|
+
In most cases for new apps, **prefer signal forms**. When making a forms decision, analyze the project and consider the following guidelines:
|
|
69
|
+
|
|
70
|
+
- if the application is using v21 or newer and this is a new form, **prefer signal forms**.
|
|
71
|
+
-For older applications or when working with existing forms, use the appropriate form type that matches the applications current form strategy.
|
|
72
|
+
|
|
73
|
+
- **Signal Forms**: Use signals for form state management. Read [signal-forms.md](references/signal-forms.md)
|
|
74
|
+
- **Template-driven forms**: Use for simple forms. Read [template-driven-forms.md](references/template-driven-forms.md)
|
|
75
|
+
- **Reactive forms**: Use for complex forms. Read [reactive-forms.md](references/reactive-forms.md)
|
|
76
|
+
|
|
77
|
+
## Dependency Injection
|
|
78
|
+
|
|
79
|
+
When implementing dependency injection in Angular, follow these guidelines:
|
|
80
|
+
|
|
81
|
+
- **Fundamentals**: Overview of Dependency Injection, services, and the `inject()` function. Read [di-fundamentals.md](references/di-fundamentals.md)
|
|
82
|
+
- **Creating and Using Services**: Creating services, the `providedIn: 'root'` option, and injecting into components or other services. Read [creating-services.md](references/creating-services.md)
|
|
83
|
+
- **Defining Dependency Providers**: Automatic vs manual provision, `InjectionToken`, `useClass`, `useValue`, `useFactory`, and scopes. Read [defining-providers.md](references/defining-providers.md)
|
|
84
|
+
- **Injection Context**: Where `inject()` is allowed, `runInInjectionContext`, and `assertInInjectionContext`. Read [injection-context.md](references/injection-context.md)
|
|
85
|
+
- **Hierarchical Injectors**: The `EnvironmentInjector` vs `ElementInjector`, resolution rules, modifiers (`optional`, `skipSelf`), and `providers` vs `viewProviders`. Read [hierarchical-injectors.md](references/hierarchical-injectors.md)
|
|
86
|
+
|
|
87
|
+
## Angular Aria
|
|
88
|
+
|
|
89
|
+
When building accessible custom components for any of the following patterns: Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid, consult the following reference:
|
|
90
|
+
|
|
91
|
+
- **Angular Aria Components**: Building headless, accessible components (Accordion, Listbox, Combobox, Menu, Tabs, Toolbar, Tree, Grid) and styling ARIA attributes. Read [angular-aria.md](references/angular-aria.md)
|
|
92
|
+
|
|
93
|
+
## Routing
|
|
94
|
+
|
|
95
|
+
When implementing navigation in Angular, consult the following references:
|
|
96
|
+
|
|
97
|
+
- **Define Routes**: URL paths, static vs dynamic segments, wildcards, and redirects. Read [define-routes.md](references/define-routes.md)
|
|
98
|
+
- **Route Loading Strategies**: Eager vs lazy loading, and context-aware loading. Read [loading-strategies.md](references/loading-strategies.md)
|
|
99
|
+
- **Show Routes with Outlets**: Using `<router-outlet>`, nested outlets, and named outlets. Read [show-routes-with-outlets.md](references/show-routes-with-outlets.md)
|
|
100
|
+
- **Navigate to Routes**: Declarative navigation with `RouterLink` and programmatic navigation with `Router`. Read [navigate-to-routes.md](references/navigate-to-routes.md)
|
|
101
|
+
- **Control Route Access with Guards**: Implementing `CanActivate`, `CanMatch`, and other guards for security. Read [route-guards.md](references/route-guards.md)
|
|
102
|
+
- **Data Resolvers**: Pre-fetching data before route activation with `ResolveFn`. Read [data-resolvers.md](references/data-resolvers.md)
|
|
103
|
+
- **Router Lifecycle and Events**: Chronological order of navigation events and debugging. Read [router-lifecycle.md](references/router-lifecycle.md)
|
|
104
|
+
- **Rendering Strategies**: CSR, SSG (Prerendering), and SSR with hydration. Read [rendering-strategies.md](references/rendering-strategies.md)
|
|
105
|
+
- **Route Transition Animations**: Enabling and customizing the View Transitions API. Read [route-animations.md](references/route-animations.md)
|
|
106
|
+
|
|
107
|
+
If you require deeper documentation or more context, visit the [official Angular Routing guide](https://angular.dev/guide/routing).
|
|
108
|
+
|
|
109
|
+
## Styling and Animations
|
|
110
|
+
|
|
111
|
+
When implementing styling and animations in Angular, consult the following references:
|
|
112
|
+
|
|
113
|
+
- **Using Tailwind CSS with Angular**: Integrating Tailwind CSS into Angular projects. Read [tailwind-css.md](references/tailwind-css.md)
|
|
114
|
+
- **Angular Animations**: Using native CSS (recommended) or the legacy DSL for dynamic effects. Read [angular-animations.md](references/angular-animations.md)
|
|
115
|
+
- **Styling components**: Best practices for component styles and encapsulation. Read [component-styling.md](references/component-styling.md)
|
|
116
|
+
|
|
117
|
+
## Testing
|
|
118
|
+
|
|
119
|
+
When writing or updating tests, consult the following references based on the task:
|
|
120
|
+
|
|
121
|
+
- **Fundamentals**: Best practices for unit testing (Vitest), async patterns, and `TestBed`. Read [testing-fundamentals.md](references/testing-fundamentals.md)
|
|
122
|
+
- **Component Harnesses**: Standard patterns for robust component interaction. Read [component-harnesses.md](references/component-harnesses.md)
|
|
123
|
+
- **Router Testing**: Using `RouterTestingHarness` for reliable navigation tests. Read [router-testing.md](references/router-testing.md)
|
|
124
|
+
- **End-to-End (E2E) Testing**: Best practices for E2E tests with Cypress. Read [e2e-testing.md](references/e2e-testing.md)
|
|
125
|
+
|
|
126
|
+
## Tooling
|
|
127
|
+
|
|
128
|
+
When working with Angular tooling, consult the following references:
|
|
129
|
+
|
|
130
|
+
- **Angular CLI**: Creating applications, generating code (components, routes, services), serving, and building. Read [cli.md](references/cli.md)
|
|
131
|
+
- **Code Modernization**: Automatically refactoring to modern standards using migrations. Read [migrations.md](references/migrations.md)
|
|
132
|
+
- **Angular documentation helpers**: Use local helper scripts for version-aware best practices and official angular.dev documentation search. Read [docs-helpers.md](references/docs-helpers.md)
|
|
133
|
+
- **Environment Configuration**: Strategies for build-time and runtime configuration. Read [environment-configuration.md](references/environment-configuration.md)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Angular Developer upstream sync metadata
|
|
2
|
+
|
|
3
|
+
Repository: https://github.com/angular/skills
|
|
4
|
+
Synced at: 2026-06-25T20:51:50.643Z
|
|
5
|
+
Requested ref: 28a90e30bba8cbc9d3a6aab56093e5b9b974bc9e
|
|
6
|
+
Synced ref: ba11b0bc3e2382082b8961bff15798a19666bee9
|
|
7
|
+
Fallback used: yes
|
|
8
|
+
Upstream source: resolved ref ba11b0bc3e2382082b8961bff15798a19666bee9
|
|
9
|
+
Local overlay source: overlays/angular-developer/
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.
|
|
2
|
+
|
|
3
|
+
## TypeScript Best Practices
|
|
4
|
+
|
|
5
|
+
- Use strict type checking
|
|
6
|
+
- Prefer type inference when the type is obvious
|
|
7
|
+
- Avoid the `any` type; use `unknown` when type is uncertain
|
|
8
|
+
|
|
9
|
+
## Angular Best Practices
|
|
10
|
+
|
|
11
|
+
- Always use standalone components over NgModules
|
|
12
|
+
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
|
|
13
|
+
- Do NOT set `changeDetection: ChangeDetectionStrategy.OnPush` explicitly. `OnPush` is the default in Angular v22+.
|
|
14
|
+
- Use signals for state management
|
|
15
|
+
- Implement lazy loading for feature routes
|
|
16
|
+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
|
|
17
|
+
- Use `NgOptimizedImage` for all static images.
|
|
18
|
+
- `NgOptimizedImage` does not work for inline base64 images.
|
|
19
|
+
|
|
20
|
+
## Accessibility Requirements
|
|
21
|
+
|
|
22
|
+
- It MUST pass all AXE checks.
|
|
23
|
+
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.
|
|
24
|
+
|
|
25
|
+
### Components
|
|
26
|
+
|
|
27
|
+
- Keep components small and focused on a single responsibility
|
|
28
|
+
- Use `input()` and `output()` functions instead of decorators
|
|
29
|
+
- Use `computed()` for derived state
|
|
30
|
+
- Prefer inline templates for small components
|
|
31
|
+
- Prefer Signal Forms (`@angular/forms/signals`) for new forms. They are stable in Angular v22+ and provide signal-based state, type-safe field access, and schema-based validation
|
|
32
|
+
- When not using Signal Forms, prefer Reactive forms instead of Template-driven ones
|
|
33
|
+
- Do NOT use `ngClass`, use `class` bindings instead
|
|
34
|
+
- Do NOT use `ngStyle`, use `style` bindings instead
|
|
35
|
+
- When using external templates/styles, use paths relative to the component TS file.
|
|
36
|
+
|
|
37
|
+
## State Management
|
|
38
|
+
|
|
39
|
+
- Use signals for local component state
|
|
40
|
+
- Use `computed()` for derived state
|
|
41
|
+
- Keep state transformations pure and predictable
|
|
42
|
+
- Do NOT use `mutate` on signals, use `update` or `set` instead
|
|
43
|
+
|
|
44
|
+
## Templates
|
|
45
|
+
|
|
46
|
+
- Keep templates simple and avoid complex logic
|
|
47
|
+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
|
48
|
+
- Use the async pipe to handle observables
|
|
49
|
+
- Do not assume globals like (`new Date()`) are available.
|
|
50
|
+
|
|
51
|
+
## Services
|
|
52
|
+
|
|
53
|
+
- Design services around a single responsibility
|
|
54
|
+
- Use the `providedIn: 'root'` option for singleton services
|
|
55
|
+
- Prefer the `@Service` decorator over `@Injectable({providedIn: 'root'})` for new singleton services (Angular v22+)
|
|
56
|
+
- Use the `inject()` function instead of constructor injection
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Angular Animations
|
|
2
|
+
|
|
3
|
+
When animating elements in Angular, **first analyze the project's Angular version** in `package.json`.
|
|
4
|
+
For modern applications (**Angular v20.2 and above**), prefer using native CSS with `animate.enter` and `animate.leave`. For older applications, you may need to use the deprecated `@angular/animations` package.
|
|
5
|
+
|
|
6
|
+
## 1. Native CSS Animations (v20.2+ Recommended)
|
|
7
|
+
|
|
8
|
+
Modern Angular provides `animate.enter` and `animate.leave` to animate elements as they enter or leave the DOM. They apply CSS classes at the appropriate times.
|
|
9
|
+
|
|
10
|
+
### `animate.enter` and `animate.leave`
|
|
11
|
+
|
|
12
|
+
Use these directly on elements to apply CSS classes during the enter or leave phase. Angular automatically removes the enter classes when the animation completes. For `animate.leave`, Angular waits for the animation to finish before removing the element from the DOM.
|
|
13
|
+
|
|
14
|
+
`animate.enter` example:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
@if (isShown()) {
|
|
18
|
+
<div class="enter-container" animate.enter="enter-animation">
|
|
19
|
+
<p>The box is entering.</p>
|
|
20
|
+
</div>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
/* Ensure you have a starting style if using transitions instead of keyframes */
|
|
26
|
+
.enter-container {
|
|
27
|
+
border: 1px solid #dddddd;
|
|
28
|
+
margin-top: 1em;
|
|
29
|
+
padding: 20px;
|
|
30
|
+
font-weight: bold;
|
|
31
|
+
font-size: 20px;
|
|
32
|
+
}
|
|
33
|
+
.enter-container p {
|
|
34
|
+
margin: 0;
|
|
35
|
+
}
|
|
36
|
+
.enter-animation {
|
|
37
|
+
animation: slide-fade 1s;
|
|
38
|
+
}
|
|
39
|
+
@keyframes slide-fade {
|
|
40
|
+
from {
|
|
41
|
+
opacity: 0;
|
|
42
|
+
transform: translateY(20px);
|
|
43
|
+
}
|
|
44
|
+
to {
|
|
45
|
+
opacity: 1;
|
|
46
|
+
transform: translateY(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
_Note: `animate.leave` may be added to child elements being removed._
|
|
52
|
+
|
|
53
|
+
### Event Bindings and Third-party Libraries
|
|
54
|
+
|
|
55
|
+
You can bind to `(animate.enter)` and `(animate.leave)` to call functions or use JS libraries like GSAP.
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
@if(show()) {
|
|
59
|
+
<div (animate.leave)="onLeave($event)">...</div>
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { AnimationCallbackEvent } from '@angular/core';
|
|
65
|
+
|
|
66
|
+
onLeave(event: AnimationCallbackEvent) {
|
|
67
|
+
// Custom animation logic here
|
|
68
|
+
// CRITICAL: You MUST call animationComplete() when done so Angular removes the element!
|
|
69
|
+
event.animationComplete();
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 2. Advanced CSS Animations
|
|
74
|
+
|
|
75
|
+
CSS offers robust tools for advanced animation sequences.
|
|
76
|
+
|
|
77
|
+
### Animating State and Styles
|
|
78
|
+
|
|
79
|
+
Toggle CSS classes on elements using property binding to trigger transitions.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<div [class.open]="isOpen">...</div>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```css
|
|
86
|
+
div {
|
|
87
|
+
transition: height 0.3s ease-out;
|
|
88
|
+
height: 100px;
|
|
89
|
+
}
|
|
90
|
+
div.open {
|
|
91
|
+
height: 200px;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Animating Auto Height
|
|
96
|
+
|
|
97
|
+
You can use `css-grid` to animate to auto height.
|
|
98
|
+
|
|
99
|
+
```css
|
|
100
|
+
.container {
|
|
101
|
+
display: grid;
|
|
102
|
+
grid-template-rows: 0fr;
|
|
103
|
+
transition: grid-template-rows 0.3s;
|
|
104
|
+
}
|
|
105
|
+
.container.open {
|
|
106
|
+
grid-template-rows: 1fr;
|
|
107
|
+
}
|
|
108
|
+
.container > div {
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Staggering and Parallel Animations
|
|
114
|
+
|
|
115
|
+
- **Staggering**: Use `animation-delay` or `transition-delay` with different values for items in a list.
|
|
116
|
+
- **Parallel**: Apply multiple animations in the `animation` shorthand (e.g., `animation: rotate 3s, fade-in 2s;`).
|
|
117
|
+
|
|
118
|
+
### Programmatic Control
|
|
119
|
+
|
|
120
|
+
Retrieve animations directly using standard Web APIs:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const animations = element.getAnimations();
|
|
124
|
+
animations.forEach((anim) => anim.pause());
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 3. Legacy Animations DSL (Deprecated)
|
|
128
|
+
|
|
129
|
+
For older projects (pre v20.2 or where `@angular/animations` is already heavily used), you use the component metadata DSL.
|
|
130
|
+
|
|
131
|
+
**Important:** Do not mix legacy animations and `animate.enter`/`leave` in the same component.
|
|
132
|
+
|
|
133
|
+
### Setup
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
bootstrapApplication(App, {
|
|
137
|
+
providers: [provideAnimationsAsync()],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Defining Transitions
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import {signal} from '@angular/core';
|
|
145
|
+
import {trigger, state, style, animate, transition} from '@angular/animations';
|
|
146
|
+
|
|
147
|
+
@Component({
|
|
148
|
+
animations: [
|
|
149
|
+
trigger('openClose', [
|
|
150
|
+
state('open', style({opacity: 1})),
|
|
151
|
+
state('closed', style({opacity: 0})),
|
|
152
|
+
transition('open <=> closed', [animate('0.5s')]),
|
|
153
|
+
]),
|
|
154
|
+
],
|
|
155
|
+
template: `<div [@openClose]="isOpen() ? 'open' : 'closed'">...</div>`,
|
|
156
|
+
})
|
|
157
|
+
export class OpenClose {
|
|
158
|
+
protected readonly isOpen = signal(true);
|
|
159
|
+
}
|
|
160
|
+
```
|