@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
package/package.json
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Flow-External Plugin for Claude Code
|
|
2
|
+
|
|
3
|
+
Development flow skills for **standalone Angular 6-11 projects** without the MobileMS framework. These are external projects using Angular CLI with their own styling approach (Bootstrap 4, custom SCSS, or other).
|
|
4
|
+
|
|
5
|
+
## Target Projects
|
|
6
|
+
|
|
7
|
+
This plugin is designed for standalone MobileMS projects:
|
|
8
|
+
|
|
9
|
+
- **mobilems-asp** (Angular 11)
|
|
10
|
+
- **mobilems-etno** (Angular 11)
|
|
11
|
+
- **mobilems-mnw** (Angular 9)
|
|
12
|
+
- **mobilems-muzuem-malopolskie** (Angular 8)
|
|
13
|
+
- **mobilems-muzeum-powstan-slaskich** (Angular 6)
|
|
14
|
+
|
|
15
|
+
### Technology Stack
|
|
16
|
+
|
|
17
|
+
| Feature | External Projects |
|
|
18
|
+
|---------|-------------------|
|
|
19
|
+
| Angular | 6 - 11 |
|
|
20
|
+
| Build system | Angular CLI (`ng`) |
|
|
21
|
+
| Styling | Project-specific (Bootstrap 4 / custom SCSS) |
|
|
22
|
+
| i18n | ngx-translate + LocalizeRouter |
|
|
23
|
+
| State | NgRx + localStorage sync |
|
|
24
|
+
| Project structure | Standalone (app/cli/docker) |
|
|
25
|
+
|
|
26
|
+
### Patterns (what this plugin supports)
|
|
27
|
+
|
|
28
|
+
| Feature | External (Angular 6-11) | Legacy (Angular 14) | Modern (Angular 20+) |
|
|
29
|
+
|---------|-------------------------|---------------------|----------------------|
|
|
30
|
+
| Control flow | `*ngIf`, `*ngFor` | `*ngIf`, `*ngFor` | `@if`, `@for` |
|
|
31
|
+
| DI | Constructor injection | Constructor injection | `inject()` |
|
|
32
|
+
| Inputs/Outputs | `@Input()`, `@Output()` | `@Input()`, `@Output()` | `input()`, `output()` |
|
|
33
|
+
| State | BehaviorSubject, NgRx | BehaviorSubject | Signals |
|
|
34
|
+
| Components | NgModules | NgModules | Standalone |
|
|
35
|
+
| Styling | Bootstrap 4 / custom SCSS | Tailwind CSS | Tailwind CSS |
|
|
36
|
+
| Build | `ng build` | `nx build` | `nx build` |
|
|
37
|
+
|
|
38
|
+
## Workflow Skills
|
|
39
|
+
|
|
40
|
+
| Skill | Description |
|
|
41
|
+
|-------|-------------|
|
|
42
|
+
| `/commit` | Create conventional commit based on Linear task |
|
|
43
|
+
| `/impl` | Implement plans from Linear task comments |
|
|
44
|
+
| `/plan` | Create implementation plan and save to Linear |
|
|
45
|
+
| `/push` | Push changes and update Linear status |
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
### 1. Enable plugin in project's `.claude/settings.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"enabledPlugins": {
|
|
54
|
+
"flow-external@mobilems": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Merge permissions from template
|
|
60
|
+
|
|
61
|
+
Run the merge script:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node node_modules/@smartsoft001-mobilems/claude-plugins/plugins/flow-external/.claude-plugin/merge-permissions.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Required MCP Servers
|
|
68
|
+
|
|
69
|
+
Configure these in your `.mcp.json`:
|
|
70
|
+
|
|
71
|
+
- `linear-server` - Linear API integration
|
|
72
|
+
- `playwright` - Browser automation for screenshots
|
|
73
|
+
|
|
74
|
+
## Permission Strategy
|
|
75
|
+
|
|
76
|
+
### Automatic (allow)
|
|
77
|
+
- Git read operations (status, diff, log, show)
|
|
78
|
+
- Angular CLI commands (ng build, ng lint, ng serve)
|
|
79
|
+
- Linear API (read/write issues, comments)
|
|
80
|
+
- Playwright (screenshots)
|
|
81
|
+
- Utility skills
|
|
82
|
+
|
|
83
|
+
### Requires Confirmation (ask)
|
|
84
|
+
- Workflow skills (/commit, /impl, /plan, /push)
|
|
85
|
+
- `git commit` - creating commits
|
|
86
|
+
- `git push` - pushing to remote
|
|
87
|
+
- `gh pr create` - creating pull requests
|
|
88
|
+
|
|
89
|
+
## Utility Skills
|
|
90
|
+
|
|
91
|
+
| Skill | Description |
|
|
92
|
+
|-------|-------------|
|
|
93
|
+
| `maia-files-upload` | Upload files to Maia API |
|
|
94
|
+
| `maia-files-delete` | Delete files from Maia API |
|
|
95
|
+
| `browser-capture` | Capture screenshots with Playwright |
|
|
96
|
+
| `linear-suggestion` | Create Linear issues for improvements |
|
|
97
|
+
| `a11y-audit` | Accessibility audit |
|
|
98
|
+
|
|
99
|
+
## Agents
|
|
100
|
+
|
|
101
|
+
### Angular Agents (Legacy CLI patterns)
|
|
102
|
+
- `angular-component-scaffolder` - Components with NgModules, `*ngIf`/`*ngFor`, constructor DI
|
|
103
|
+
- `angular-service-builder` - Services with BehaviorSubject, constructor DI
|
|
104
|
+
- `angular-state-builder` - NgRx state management with reducers and effects
|
|
105
|
+
- `angular-directive-builder` - Directives with constructor DI
|
|
106
|
+
- `angular-pipe-builder` - Pipes with constructor DI
|
|
107
|
+
- `angular-guard-builder` - Class-based guards with constructor DI
|
|
108
|
+
- `angular-resolver-builder` - Class-based resolvers with constructor DI
|
|
109
|
+
|
|
110
|
+
### Shared Agents
|
|
111
|
+
- `shared-impl-orchestrator` - Implementation orchestration
|
|
112
|
+
- `shared-impl-reporter` - Implementation reporting
|
|
113
|
+
- `shared-linear-subtask-iterator` - Linear subtask iteration
|
|
114
|
+
- `shared-build-verifier` - Build verification via `ng build`
|
|
115
|
+
- `shared-ui-classifier` - UI change classification
|
|
116
|
+
- `ui-web-designer` - Styling with project-specific approach (Bootstrap 4 or custom SCSS)
|
|
117
|
+
|
|
118
|
+
## Differences from other plugins
|
|
119
|
+
|
|
120
|
+
| Aspect | `flow` (modern) | `flow-legacy` | `flow-external` |
|
|
121
|
+
|--------|-----------------|---------------|-----------------|
|
|
122
|
+
| Angular version | 20+ | 14 | 6-11 |
|
|
123
|
+
| Build system | Nx | Nx | Angular CLI |
|
|
124
|
+
| Styling | Tailwind | Tailwind | Project-specific |
|
|
125
|
+
| State | Signals | BehaviorSubject | NgRx + BehaviorSubject |
|
|
126
|
+
| Project structure | Nx monorepo | Nx monorepo | Standalone app/ |
|
|
127
|
+
|
|
128
|
+
## Example Project Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
project-root/
|
|
132
|
+
app/ # Frontend Angular application
|
|
133
|
+
src/
|
|
134
|
+
app/
|
|
135
|
+
components/
|
|
136
|
+
services/
|
|
137
|
+
pipes/
|
|
138
|
+
directives/
|
|
139
|
+
interceptors/
|
|
140
|
+
guards/
|
|
141
|
+
state/ # NgRx store
|
|
142
|
+
assets/
|
|
143
|
+
environments/
|
|
144
|
+
angular.json
|
|
145
|
+
package.json
|
|
146
|
+
tsconfig.json
|
|
147
|
+
cli/ # CLI scripts
|
|
148
|
+
frontend-cli
|
|
149
|
+
docker/ # Docker configuration
|
|
150
|
+
Dockerfile
|
|
151
|
+
.env
|
|
152
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Merge flow-external plugin permissions into project's .claude/settings.json
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @smartsoft001-mobilems/claude-plugins flow-external:merge-permissions
|
|
7
|
+
*
|
|
8
|
+
* Or directly:
|
|
9
|
+
* node node_modules/@smartsoft001-mobilems/claude-plugins/plugins/flow-external/.claude-plugin/merge-permissions.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const SETTINGS_PATH = path.join(process.cwd(), '.claude', 'settings.json');
|
|
16
|
+
const TEMPLATE_PATH = path.join(__dirname, 'settings.template.json');
|
|
17
|
+
|
|
18
|
+
function loadJson(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mergeArrays(existing, template) {
|
|
27
|
+
return [...new Set([...existing, ...template])];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function main() {
|
|
31
|
+
console.log('Flow-External Plugin - Merge Permissions\n');
|
|
32
|
+
|
|
33
|
+
// Load template
|
|
34
|
+
const template = loadJson(TEMPLATE_PATH);
|
|
35
|
+
if (!template) {
|
|
36
|
+
console.error('ERROR: Could not load template from:', TEMPLATE_PATH);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Load or create settings
|
|
41
|
+
let settings = loadJson(SETTINGS_PATH);
|
|
42
|
+
if (!settings) {
|
|
43
|
+
console.log('Creating new .claude/settings.json...');
|
|
44
|
+
fs.mkdirSync(path.dirname(SETTINGS_PATH), { recursive: true });
|
|
45
|
+
settings = { permissions: { allow: [], deny: [], ask: [] } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Ensure permissions structure exists
|
|
49
|
+
settings.permissions = settings.permissions || { allow: [], deny: [], ask: [] };
|
|
50
|
+
settings.permissions.allow = settings.permissions.allow || [];
|
|
51
|
+
settings.permissions.ask = settings.permissions.ask || [];
|
|
52
|
+
settings.permissions.deny = settings.permissions.deny || [];
|
|
53
|
+
|
|
54
|
+
// Count before
|
|
55
|
+
const beforeAllow = settings.permissions.allow.length;
|
|
56
|
+
const beforeAsk = settings.permissions.ask.length;
|
|
57
|
+
|
|
58
|
+
// Merge permissions
|
|
59
|
+
settings.permissions.allow = mergeArrays(settings.permissions.allow, template.permissions.allow);
|
|
60
|
+
settings.permissions.ask = mergeArrays(settings.permissions.ask, template.permissions.ask);
|
|
61
|
+
|
|
62
|
+
// Enable plugin
|
|
63
|
+
settings.enabledPlugins = settings.enabledPlugins || {};
|
|
64
|
+
settings.enabledPlugins['flow-external@mobilems'] = true;
|
|
65
|
+
|
|
66
|
+
// Write back
|
|
67
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
68
|
+
|
|
69
|
+
// Report
|
|
70
|
+
const addedAllow = settings.permissions.allow.length - beforeAllow;
|
|
71
|
+
const addedAsk = settings.permissions.ask.length - beforeAsk;
|
|
72
|
+
|
|
73
|
+
console.log('Permissions merged successfully!\n');
|
|
74
|
+
console.log(` allow: ${beforeAllow} -> ${settings.permissions.allow.length} (+${addedAllow})`);
|
|
75
|
+
console.log(` ask: ${beforeAsk} -> ${settings.permissions.ask.length} (+${addedAsk})`);
|
|
76
|
+
console.log(`\nPlugin enabled: flow-external@mobilems`);
|
|
77
|
+
console.log(`\nFile updated: ${SETTINGS_PATH}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/anthropics/claude-code/main/schemas/settings.schema.json",
|
|
3
|
+
"_description": "Template permissions for flow-external@mobilems plugin. Merge these into your project's .claude/settings.json",
|
|
4
|
+
"permissions": {
|
|
5
|
+
"allow": [
|
|
6
|
+
"Bash(git status:*)",
|
|
7
|
+
"Bash(git diff:*)",
|
|
8
|
+
"Bash(git log:*)",
|
|
9
|
+
"Bash(git show:*)",
|
|
10
|
+
"Bash(git add:*)",
|
|
11
|
+
"Bash(git fetch:*)",
|
|
12
|
+
"Bash(git pull:*)",
|
|
13
|
+
"Bash(gh run list:*)",
|
|
14
|
+
"Bash(gh run view:*)",
|
|
15
|
+
"Bash(gh pr list:*)",
|
|
16
|
+
"Bash(ng:*)",
|
|
17
|
+
"Bash(npx ng:*)",
|
|
18
|
+
"Bash(npx tsc:*)",
|
|
19
|
+
"Bash(npm start)",
|
|
20
|
+
"Bash(npm run:*)",
|
|
21
|
+
"Bash(npm audit:*)",
|
|
22
|
+
"Bash(npm install:*)",
|
|
23
|
+
"Bash(source:*)",
|
|
24
|
+
"Bash(nvm use:*)",
|
|
25
|
+
"Bash(curl:*)",
|
|
26
|
+
"Bash(lsof:*)",
|
|
27
|
+
"Bash(ls:*)",
|
|
28
|
+
"Bash(find:*)",
|
|
29
|
+
"Bash(grep:*)",
|
|
30
|
+
"Bash(xargs:*)",
|
|
31
|
+
"Bash(sleep:*)",
|
|
32
|
+
"Bash(rm:*)",
|
|
33
|
+
"Bash(test:*)",
|
|
34
|
+
"Bash(for:*)",
|
|
35
|
+
"Bash(node:*)",
|
|
36
|
+
"Bash(mkdir:*)",
|
|
37
|
+
"mcp__linear-server__get_issue",
|
|
38
|
+
"mcp__linear-server__list_issues",
|
|
39
|
+
"mcp__linear-server__list_comments",
|
|
40
|
+
"mcp__linear-server__create_comment",
|
|
41
|
+
"mcp__linear-server__delete_comment",
|
|
42
|
+
"mcp__linear-server__update_issue",
|
|
43
|
+
"mcp__linear-server__list_issue_statuses",
|
|
44
|
+
"mcp__linear-server__create_issue",
|
|
45
|
+
"mcp__playwright__browser_snapshot",
|
|
46
|
+
"mcp__playwright__browser_take_screenshot",
|
|
47
|
+
"mcp__playwright__browser_navigate",
|
|
48
|
+
"mcp__playwright__browser_close",
|
|
49
|
+
"mcp__playwright__browser_wait_for",
|
|
50
|
+
"mcp__playwright__browser_click",
|
|
51
|
+
"mcp__playwright__browser_resize",
|
|
52
|
+
"mcp__playwright__browser_evaluate",
|
|
53
|
+
"mcp__playwright__browser_console_messages",
|
|
54
|
+
"mcp__playwright__browser_press_key",
|
|
55
|
+
"mcp__playwright__browser_network_requests",
|
|
56
|
+
"Skill(flow-external:maia-files-upload)",
|
|
57
|
+
"Skill(flow-external:maia-files-delete)",
|
|
58
|
+
"Skill(flow-external:browser-capture)",
|
|
59
|
+
"Skill(flow-external:linear-suggestion)",
|
|
60
|
+
"Skill(flow-external:a11y-audit)"
|
|
61
|
+
],
|
|
62
|
+
"ask": [
|
|
63
|
+
"Skill(flow-external:commit)",
|
|
64
|
+
"Skill(flow-external:impl)",
|
|
65
|
+
"Skill(flow-external:plan)",
|
|
66
|
+
"Skill(flow-external:push)",
|
|
67
|
+
"Bash(git commit:*)",
|
|
68
|
+
"Bash(git push:*)",
|
|
69
|
+
"Bash(gh pr create:*)"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"enabledPlugins": {
|
|
73
|
+
"flow-external@mobilems": true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-component-scaffolder
|
|
3
|
+
description: Create Angular 6-11 components with NgModules and legacy patterns. Use when scaffolding new components for standalone museum projects (Bootstrap 4, *ngIf/*ngFor, constructor DI).
|
|
4
|
+
tools: Read, Write, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: "#DD0031"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are an expert at creating Angular components for standalone museum projects (Angular 6-11).
|
|
10
|
+
|
|
11
|
+
## Primary Responsibility
|
|
12
|
+
|
|
13
|
+
Create Angular components using Angular 6-11 patterns including NgModules, `*ngIf`/`*ngFor`, constructor injection, `@Input()`/`@Output()` decorators, and Bootstrap 4 styling.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Creating new Angular components in standalone Angular CLI projects
|
|
18
|
+
- Scaffolding component structure with template and styles
|
|
19
|
+
- Setting up component with proper constructor DI and Bootstrap 4
|
|
20
|
+
|
|
21
|
+
## CRITICAL: Angular 6-11 Patterns (MANDATORY)
|
|
22
|
+
|
|
23
|
+
**NEVER use modern Angular patterns. ALWAYS use these legacy patterns:**
|
|
24
|
+
|
|
25
|
+
| Feature | CORRECT (Angular 6-11) | WRONG (Angular 14+/20+) |
|
|
26
|
+
|---------|------------------------|-------------------------|
|
|
27
|
+
| Control flow | `*ngIf`, `*ngFor` | `@if`, `@for` |
|
|
28
|
+
| DI | Constructor injection | `inject()` |
|
|
29
|
+
| Inputs | `@Input()` decorator | `input()`, `input.required()` |
|
|
30
|
+
| Outputs | `@Output()` decorator | `output()` |
|
|
31
|
+
| Two-way binding | `@Input()` + `@Output()` | `model()` |
|
|
32
|
+
| State | Class properties, BehaviorSubject | `signal()` |
|
|
33
|
+
| Derived state | Getters | `computed()` |
|
|
34
|
+
| Styling | Bootstrap 4 + SCSS | Tailwind CSS |
|
|
35
|
+
| Components | NgModules | Standalone |
|
|
36
|
+
|
|
37
|
+
## Project-Specific Patterns
|
|
38
|
+
|
|
39
|
+
These projects use:
|
|
40
|
+
|
|
41
|
+
- **NgModules** - components must be declared in modules
|
|
42
|
+
- **`*ngIf` and `*ngFor`** for control flow
|
|
43
|
+
- **Constructor injection** for DI
|
|
44
|
+
- **`@Input()` and `@Output()`** decorators
|
|
45
|
+
- **Bootstrap 4** with ng-bootstrap for UI
|
|
46
|
+
- **ngx-translate** for i18n (`TranslateModule`, `| translate` pipe)
|
|
47
|
+
- **SCSS** for styles (not Tailwind)
|
|
48
|
+
- **No signals** - use class properties and BehaviorSubject
|
|
49
|
+
|
|
50
|
+
## Component Templates
|
|
51
|
+
|
|
52
|
+
### Basic Component
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
56
|
+
import { Subject } from 'rxjs';
|
|
57
|
+
import { takeUntil } from 'rxjs/operators';
|
|
58
|
+
|
|
59
|
+
@Component({
|
|
60
|
+
selector: 'app-feature-name',
|
|
61
|
+
templateUrl: './feature-name.component.html',
|
|
62
|
+
styleUrls: ['./feature-name.component.scss'],
|
|
63
|
+
})
|
|
64
|
+
export class FeatureNameComponent implements OnInit, OnDestroy {
|
|
65
|
+
isLoading = false;
|
|
66
|
+
data: DataType[] = [];
|
|
67
|
+
error: string | null = null;
|
|
68
|
+
|
|
69
|
+
private readonly destroy$ = new Subject<void>();
|
|
70
|
+
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly dataService: DataService,
|
|
73
|
+
private readonly router: Router
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
ngOnInit(): void {
|
|
77
|
+
this.loadData();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ngOnDestroy(): void {
|
|
81
|
+
this.destroy$.next();
|
|
82
|
+
this.destroy$.complete();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private loadData(): void {
|
|
86
|
+
this.isLoading = true;
|
|
87
|
+
this.dataService.getData()
|
|
88
|
+
.pipe(takeUntil(this.destroy$))
|
|
89
|
+
.subscribe({
|
|
90
|
+
next: (data) => {
|
|
91
|
+
this.data = data;
|
|
92
|
+
this.isLoading = false;
|
|
93
|
+
},
|
|
94
|
+
error: (err) => {
|
|
95
|
+
this.error = err.message;
|
|
96
|
+
this.isLoading = false;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get itemCount(): number {
|
|
102
|
+
return this.data.length;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get hasData(): boolean {
|
|
106
|
+
return this.data.length > 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Component with Inputs/Outputs
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
|
115
|
+
|
|
116
|
+
@Component({
|
|
117
|
+
selector: 'app-item-card',
|
|
118
|
+
templateUrl: './item-card.component.html',
|
|
119
|
+
styleUrls: ['./item-card.component.scss'],
|
|
120
|
+
})
|
|
121
|
+
export class ItemCardComponent implements OnChanges {
|
|
122
|
+
@Input() label!: string;
|
|
123
|
+
@Input() item!: Item;
|
|
124
|
+
@Input() showActions = true;
|
|
125
|
+
|
|
126
|
+
@Output() selected = new EventEmitter<Item>();
|
|
127
|
+
@Output() closed = new EventEmitter<void>();
|
|
128
|
+
|
|
129
|
+
isOpen = false;
|
|
130
|
+
|
|
131
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
132
|
+
if (changes['item']) {
|
|
133
|
+
console.log('Item changed:', this.item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onSelect(): void {
|
|
138
|
+
this.selected.emit(this.item);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onClose(): void {
|
|
142
|
+
this.closed.emit();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Template Patterns (Bootstrap 4 + ngx-translate)
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<!-- Loading state -->
|
|
151
|
+
<div *ngIf="isLoading" class="d-flex justify-content-center p-5">
|
|
152
|
+
<div class="spinner-border text-primary" role="status">
|
|
153
|
+
<span class="sr-only">{{ 'COMMON.loading' | translate }}</span>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<!-- Error state -->
|
|
158
|
+
<div *ngIf="error" class="alert alert-danger" role="alert">
|
|
159
|
+
{{ error }}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<!-- Content -->
|
|
163
|
+
<div *ngIf="!isLoading && !error">
|
|
164
|
+
<div class="row" *ngIf="hasData; else emptyTemplate">
|
|
165
|
+
<div class="col-md-4 mb-4" *ngFor="let item of data; trackBy: trackByFn">
|
|
166
|
+
<div class="card h-100">
|
|
167
|
+
<img *ngIf="item.image" [src]="item.image" class="card-img-top" [alt]="item.title">
|
|
168
|
+
<div class="card-body">
|
|
169
|
+
<h5 class="card-title">{{ item.title }}</h5>
|
|
170
|
+
<p class="card-text">{{ item.description | truncate:150 }}</p>
|
|
171
|
+
</div>
|
|
172
|
+
<div class="card-footer">
|
|
173
|
+
<button class="btn btn-primary btn-sm" (click)="onSelect(item)">
|
|
174
|
+
{{ 'COMMON.details' | translate }}
|
|
175
|
+
</button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<ng-template #emptyTemplate>
|
|
182
|
+
<div class="text-center p-5 text-muted">
|
|
183
|
+
<p>{{ 'COMMON.noItems' | translate }}</p>
|
|
184
|
+
</div>
|
|
185
|
+
</ng-template>
|
|
186
|
+
</div>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### TrackBy Function
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
trackByFn(index: number, item: Item): string {
|
|
193
|
+
return item.id;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## SCSS Styling (Bootstrap 4)
|
|
198
|
+
|
|
199
|
+
```scss
|
|
200
|
+
:host {
|
|
201
|
+
display: block;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.card {
|
|
205
|
+
transition: box-shadow 0.2s ease;
|
|
206
|
+
|
|
207
|
+
&:hover {
|
|
208
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.card-img-top {
|
|
212
|
+
height: 200px;
|
|
213
|
+
object-fit: cover;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Module Declaration
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { NgModule } from '@angular/core';
|
|
222
|
+
import { CommonModule } from '@angular/common';
|
|
223
|
+
import { TranslateModule } from '@ngx-translate/core';
|
|
224
|
+
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|
225
|
+
|
|
226
|
+
import { FeatureNameComponent } from './feature-name.component';
|
|
227
|
+
import { ItemCardComponent } from './item-card/item-card.component';
|
|
228
|
+
|
|
229
|
+
@NgModule({
|
|
230
|
+
declarations: [
|
|
231
|
+
FeatureNameComponent,
|
|
232
|
+
ItemCardComponent,
|
|
233
|
+
],
|
|
234
|
+
imports: [
|
|
235
|
+
CommonModule,
|
|
236
|
+
TranslateModule,
|
|
237
|
+
NgbModule,
|
|
238
|
+
],
|
|
239
|
+
exports: [
|
|
240
|
+
FeatureNameComponent,
|
|
241
|
+
ItemCardComponent,
|
|
242
|
+
],
|
|
243
|
+
})
|
|
244
|
+
export class FeatureModule {}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## File Structure
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
feature-name/
|
|
251
|
+
feature-name.component.ts
|
|
252
|
+
feature-name.component.html
|
|
253
|
+
feature-name.component.scss
|
|
254
|
+
feature-name.component.spec.ts
|
|
255
|
+
feature-name.module.ts
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Checklist
|
|
259
|
+
|
|
260
|
+
- [ ] Uses NgModule (NOT standalone)
|
|
261
|
+
- [ ] Uses constructor injection (NOT `inject()`)
|
|
262
|
+
- [ ] Uses `@Input()` / `@Output()` decorators (NOT `input()` / `output()`)
|
|
263
|
+
- [ ] Uses `*ngIf` / `*ngFor` in template (NOT `@if` / `@for`)
|
|
264
|
+
- [ ] Has `trackBy` function for all `*ngFor` loops
|
|
265
|
+
- [ ] Uses Bootstrap 4 classes for layout (NOT Tailwind)
|
|
266
|
+
- [ ] Uses ngx-translate `| translate` pipe for i18n
|
|
267
|
+
- [ ] Has `OnDestroy` with Subject for cleanup
|
|
268
|
+
- [ ] Declared in a module with TranslateModule and NgbModule imports
|
|
269
|
+
- [ ] Has proper TypeScript types
|