
Angular Best Practices
- 738 installs
- 15 repo stars
- Updated August 3, 2026
- boise-state-development/agentcore-public-stack
angular-best-practices is a Claude Code frontend skill that generates Angular 21 components, services, and templates using signals, standalone defaults, OnPush, and strict TypeScript for developers building modern Angula
About
angular-best-practices is an agentcore-public-stack skill for Angular 21 development with signals, standalone components, reactive patterns, and accessibility baked in. It enforces strict TypeScript (avoid any, prefer unknown), signal-based state, reactive forms, lazy loading, ng-icon setup, and Tailwind styling. Standalone components are the default in Angular v20+, so the skill omits redundant standalone: true flags and favors OnPush change detection. Developers reach for angular-best-practices when scaffolding Angular UI without manually cross-checking current framework conventions.
- Enforces strict TypeScript with type inference and unknown over any
- Mandates standalone components, OnPush change detection, input/output signals, and computed()
- Recommends reactive forms, class/style bindings, relative paths, and host object over legacy decorators
- Signal-based state management rules including pure transformations and avoiding mutate()
- Covers accessibility, lazy loading, ng-icon, and Tailwind integration patterns
Angular Best Practices by the numbers
- 738 all-time installs (skills.sh)
- Ranked #471 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/boise-state-development/agentcore-public-stack --skill angular-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 738 |
|---|---|
| repo stars | ★ 15 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 3, 2026 |
| Repository | boise-state-development/agentcore-public-stack ↗ |
How do you scaffold Angular 21 signals components correctly?
Generate Angular 21 components, services, and templates that follow current signals, standalone, OnPush, and reactive best practices without manual rule lookup.
Who is it for?
Frontend developers shipping Angular 21 apps who want signal-based, standalone, OnPush components without looking up current style rules.
Skip if: Legacy NgModule-only Angular codebases or backends with no Angular frontend work.
When should I use this skill?
User creates Angular components, services, templates, reactive forms, or asks for signals, standalone, or OnPush patterns.
What you get
Standalone Angular components, services, templates, reactive forms, lazy-loaded routes, and ng-icon configurations aligned to Angular 21 conventions.
- Standalone components
- Typed services
- Reactive form templates
By the numbers
- Documents Angular 21 conventions with standalone default from v20+
Files
Angular 21 Best Practices
TypeScript
- Use strict type checking
- Prefer type inference when type is obvious
- Avoid
any; useunknownwhen type is uncertain
Components
- Always use standalone components (do NOT set
standalone: true— it's the default in v20+) - Set
changeDetection: ChangeDetectionStrategy.OnPush - Use
input()andoutput()functions instead of decorators - Use
computed()for derived state - Keep components small and single-responsibility
- Prefer inline templates for small components
- Use Reactive forms over Template-driven
- Use
classbindings instead ofngClass - Use
stylebindings instead ofngStyle - For external templates/styles, use paths relative to the component TS file
- Do NOT use
@HostBinding/@HostListener— use thehostobject in the decorator instead
State Management with Signals
- Use signals for local component state
- Use
computed()for derived state - Keep state transformations pure and predictable
- Do NOT use
mutateon signals — useupdateorsetinstead
For complex derived state patterns, see references/signal-patterns.md.
Resources (Async Data)
Use resource() for async data fetching with signals:
const userResource = resource({
params: () => ({ id: userId() }),
loader: ({ params, abortSignal }) => fetch(`/api/users/${params.id}`, { signal: abortSignal }),
});
const userName = computed(() => userResource.hasValue() ? userResource.value().name : undefined);Key resource patterns:
paramsreturnsundefined→ loader doesn't run, status becomes'idle'- Use
abortSignalto cancel in-flight requests - Check
hasValue()before accessingvalue()to handle loading/error states - Status values:
'idle','loading','reloading','resolved','error','local'
Templates
- Use native control flow:
@if,@for,@switch(NOT*ngIf,*ngFor,*ngSwitch) - Use async pipe for observables
- Keep templates simple — no complex logic
- Do NOT use arrow functions in templates (not supported)
- Do NOT assume globals like
new Date()are available
Services
- Single responsibility per service
- Use
providedIn: 'root'for singletons - Use
inject()function instead of constructor injection
@Injectable({ providedIn: 'root' })
export class UserService {
private readonly http = inject(HttpClient);
}Routing
- Implement lazy loading for feature routes:
export const routes: Routes = [
{
path: 'admin',
loadComponent: () => import('./admin/admin.page').then(m => m.AdminPage),
},
];File Naming
- Routable view components:
file-name.page.ts,file-name.page.html,file-name.page.css - Regular components:
file-name.component.ts - Services:
file-name.service.ts
Icons (ng-icon)
import { NgIcon, provideIcons } from '@ng-icons/core';
import { heroSparkles, heroTrash } from '@ng-icons/heroicons/outline';
@Component({
selector: 'app-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgIcon],
providers: [provideIcons({ heroSparkles, heroTrash })],
template: `<ng-icon name="heroSparkles" />`,
})
export class ExampleComponent {}Images
- Use
NgOptimizedImagefor all static images NgOptimizedImagedoes NOT work for inline base64 images
Styling
- Use Tailwind 4.1 for CSS (see tailwind skill if available)
- Angular CDK is available when needed
Accessibility
- MUST pass all AXE checks
- MUST meet WCAG AA minimums: focus management, color contrast, ARIA attributes
PK�
�[��<"�angular-best-practices/SKILL.md�Wݎ����8�Hj��A.�:]E���۰6m��P3��,��������}�>I�CΌFk7hov5<?<��9��f��\�0�F7�Jf�EP��Y)}�T�5KZ%��*�Qj[W��S�@�-�3��ԋ�2�nJe�������0���H*lU� 8v�e��j4�|$
��j��
�9��%������v6
uy�|����j-���j�v�U,&̩�9k��eNYw;��=J���T�M��w�N5�L[�f[�eɮI�0b/Y��+�Fh�ˉ�e6��*�������7B�;eX�I�i�͐���0��r���)ʞ<�e������A�P�'wҥce�S��
i:�d�Ge��Ѫ�r�*J
�捹5���
t� �����L�-+}'N>J2�4.-�z}�A��̲������oRa�)$�����_��� �oX�8�|�sd�*u}y� �ٟ�͛��6F�2uƓ<�>�Mh?w��帀}��$��充�|'�^4A�Q��@��:�K)롫�Zǻ<����IZW���(���s�F�I������E�,ݴ���
2��Zx�����/���~�/ן����H�&?p�
}�w�Q�v4���@a3�������
��x=O�+��{�÷��|�>P��H����XS~)G���dsi�Ji��Ks�ǾAtm4���>c̵-� �}f���'L@�� �����X��%�Tl?
@հ$�2�E��yS���( �{'9l��.�ગ���B��G��u�|?�M�d��Vz�8���ʟLA�E�,��q-���0[�N��� ��:�̲<�Q�����9��{��Z�3�GNT~I� =������e�FJ�a2c�tK�&v��ֺ�r&���Ղ���w��{�ʇ|J����G���a�4��
�f���L:1?����tI9��9d�'p(qB�e/��O���'ss(48c�n��,��W*��f�5&N��x�l��$�#Uj9�`�j�][��F��vZ�P"��`1�y {߀i��G��cG�2�3.�F�B:�Dž��6ɲ(�w�M�H��Oo5*>}E�����QفT��&���մ��W^��^���ߣX�CNcn���f���"q��&�LZũ�kUGl@�^�;p8�Wܳ�Í�;X۽*.qA����5�26��52��9K��^�-�V�H��wܲ1%�Su���(be�Dq�6q\���u�[�����=�R�ט�#gm��O�.@�<z���v8bbG��H�s����:R�bnF�f�ǂ���]�E>24bK+��':�P�}[۾��Z+�>:�!� ��^���5�)��b=�a?e�w���[gWD��'jF��;�R��G��ZG�xķ�;V�~(���Ț��k̹�d��h��U���<���4{�����0�Z��.*�x�;x`�����B�?q\�%�|��_��Yw\�R5^0��Y��k'� �Փ�1O1���3�+�$��FW��_`����g��tv����
�8���dZ���*5�U��8��Ԓ뛳[�3�Ap1����,�$#�wɹ��X��?��C_ǿ��$Jv��p��{�pȟ}6����</���J�.ݟ��B
�Л�ڿ�۲R��o���
�sy�~E�٧Xy�Et�Zl�f~�}�U'�������_Y_Ϳ���͆Ƽ���u�`�ڝQ��{�����OOK�!#%(^��[���ܠ��n���ҫ�w�J�!����ϴZ:PU
�6;[`����q�ô߫��+�*�Ԗ�#�PK�[F���4angular-best-practices/references/signal-patterns.md�X�n���SL��z�X����(���N�ADis�;Z���nI�\�Ї��'9ß����� �3ߐ��Q�`�S�2���F)T����gk���5J�b�����>-������B(-�F�g )0Z/Q$(�5���K(yYiBU֢��%d�2c���-d��^"�W��כ�fz]��%/u������{
3�u!�p��rJ�������$�%)^�&9����S{�t=�Y|��Ҩh�ˌ�0�_� _A�1�.��fZ
Rɓ���x�q��`�Q��~�Tͩ2�3P%�n;�57�3����� ��w�]}*4�.w_V,����H�E���/�+)�Z�ChoV��}��+�`�J�������?���0e+��
�c�� =��-�^����y~D�7K��ަ��?�Bj
{��:�MVX_�F.����EE�u�ѦD�"[�������\x��cw_}L���J��$jV��B �u�*ϙ\�DeMF�0:��p'#�liXďh�s�4k
�[��_ �,�D擔�[��Q�jw\�K�y�aʄ&+��IFU���"Uo�N���&Uz��y�
#</������Q�.&>yc�
L[Wj��]'G��jB��h�۔�D]Q��X���6�j�
tʨ����F�>7[~����%��#��|Or�~sS�@!�e�\���.q�$0r}������]7�Ӷ~����V��κd�|��ǣ5� B�Tܢxh�t��JpG��U�U���4̂���C��@�1Y��_6|�8�+��U�3��#+��I�\�ν[IJ�.T���a����߇K�:���`&�
dd�'�X�
����Fvgp��G���^��ӍŃ��N�z성��E�?:v�jr"pM��uQ��!/���T�E�Ä��絵�+�)8�F��F���p/��BWo
2�|^��R1O��s!o����AsR�����d˲5e~ȴN!&wk������r���������R��~S�;N��ր1�w�?"˛�`� g������a���vZ,D3Sȳ>ݦX�����gԲq��L���t���*��K�r�L��SƧ
ݖq���MEc�cȉ�?C�7"%P 6�i�x#h�p�fN+��Z.�c!s��B7Z���<���>X�ב�m����su�Vi�
�z]̩2���P##��I9�[Um�U��b>LeQ��+a����������2N���jh�����E+|ssj5��+�ѳB�ܷ���j�s��kǰ�NK�jjv�����E
v���o�,dK�(Í�.�Z�pv�����y�P�����7v-<X�'Y��ZĔjn3���a[N�1h^*�S��՚����H�
}/t�������jQ� �l��1��U3�8E��1�b�^.E�����X]C�bF�"l��^�L)p"Fzm�\s*t3&�~����<n�������;�o��ycێ� �:�ZR�ӓ$�+�c[��uJ��a\�ΐ���z�V�z���!;���$��Ƿ�������x�h28�}�;W%��{�T6��]ۏ�E��<���x�붑�&�E�A���=U>G9��ug��/H��4"%��h>�6Ё&�*�vՒ���� �z< A
is�)�w_~���qE�p��7{�#ȸ��n(����u[��A����y9�.*��c��I����Oy \Hk���[�o����'F�2�Q�m�c�̀�����ܧ�YN�+��Xm����p���v��h}�ٮ���.Q�\����iBS ���z�u7�����d����Q�D�g��<I,���C��ۙվ�l��c/C�R
����ǜ�����ZWE��1�dž?����-����4���2����;�$�V%1$Iվkꇈ�?L9�a#?�ܘ7���I�H���
�ۨl�y��X�t���?��.�&�:�c�#��<���A6�9߃:�0笌���ٶg��PK�
�[��<"���angular-best-practices/SKILL.mdPK�[F���4��_angular-best-practices/references/signal-patterns.mdPK�mSignal Patterns
Layered Derivation Pattern
This pattern demonstrates building a dependency graph of computed signals where each layer derives from the previous.
@Component({
selector: 'app-cost-dashboard',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="stats-grid">
@if (isLoading()) {
<app-loading-spinner />
} @else {
<app-stat-card label="Total Cost" [value]="totalCost() | currency" />
<app-stat-card label="Avg Cost/Request" [value]="averageCostPerRequest() | currency" />
<app-stat-card label="Cache Savings" [value]="cacheSavingsPercentage() | percent" />
}
</div>
`,
})
export class CostDashboardComponent {
// Layer 1: Source signals
readonly selectedPeriodType = signal<'current' | 'custom'>('current');
readonly costSummary = resource({ /* ... */ });
readonly customReportData = signal<CostData | null>(null);
// Layer 2: Data source switching
// Elegantly handles switching between data sources based on user selection
readonly activeData = computed(() => {
const periodType = this.selectedPeriodType();
if (periodType === 'current') {
return this.costSummary.value();
} else {
return this.customReportData();
}
});
// Layer 3: Direct derivations from active data
// Safe null handling with nullish coalescing
readonly totalCost = computed(() => this.activeData()?.totalCost ?? 0);
readonly totalRequests = computed(() => this.activeData()?.totalRequests ?? 0);
readonly totalInputTokens = computed(() => this.activeData()?.totalInputTokens ?? 0);
readonly totalOutputTokens = computed(() => this.activeData()?.totalOutputTokens ?? 0);
readonly totalCacheSavings = computed(() => this.activeData()?.cacheSavings ?? 0);
// Layer 4: Business logic computed from Layer 3
// Calculations encapsulated in computed signals keep templates simple
readonly averageCostPerRequest = computed(() => {
const total = this.totalCost();
const requests = this.totalRequests();
return requests > 0 ? total / requests : 0;
});
readonly totalTokens = computed(() => this.totalInputTokens() + this.totalOutputTokens());
readonly cacheSavingsPercentage = computed(() => {
const savings = this.totalCacheSavings();
const cost = this.totalCost();
const totalWithoutSavings = cost + savings;
return totalWithoutSavings > 0 ? (savings / totalWithoutSavings) * 100 : 0;
});
}Why This Pattern Works
1. Layered derivation — Each layer builds on the previous, creating a clean dependency graph 2. Data source switching — activeData abstracts the source, keeping downstream signals agnostic 3. Safe null handling — Nullish coalescing (?? 0) provides sensible defaults 4. Business logic isolation — Calculations live in computed signals, not templates 5. Automatic memoization — Angular only recalculates when dependencies change
Form State with Signals
@Component({
selector: 'app-user-form',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserFormComponent {
private readonly fb = inject(FormBuilder);
readonly form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
name: ['', Validators.required],
});
// Derive submission state
readonly isSubmitting = signal(false);
readonly submitError = signal<string | null>(null);
readonly canSubmit = computed(() =>
this.form.valid && !this.isSubmitting()
);
async onSubmit() {
if (!this.canSubmit()) return;
this.isSubmitting.set(true);
this.submitError.set(null);
try {
await this.userService.save(this.form.getRawValue());
} catch (e) {
this.submitError.set(e instanceof Error ? e.message : 'Unknown error');
} finally {
this.isSubmitting.set(false);
}
}
}Parent-Child Communication
// child.component.ts
@Component({
selector: 'app-counter',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button (click)="decrement()">-</button>
<span>{{ value() }}</span>
<button (click)="increment()">+</button>
`,
})
export class CounterComponent {
readonly value = input.required<number>();
readonly valueChange = output<number>();
increment() {
this.valueChange.emit(this.value() + 1);
}
decrement() {
this.valueChange.emit(this.value() - 1);
}
}
// parent.component.ts
@Component({
selector: 'app-parent',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CounterComponent],
template: `
<app-counter [value]="count()" (valueChange)="count.set($event)" />
<p>Double: {{ doubled() }}</p>
`,
})
export class ParentComponent {
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);
}List with Derived Selection State
@Component({
selector: 'app-item-list',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ItemListComponent {
readonly items = input.required<Item[]>();
readonly selectedIds = signal<Set<string>>(new Set());
readonly selectedItems = computed(() =>
this.items().filter(item => this.selectedIds().has(item.id))
);
readonly allSelected = computed(() =>
this.items().length > 0 && this.selectedIds().size === this.items().length
);
readonly someSelected = computed(() =>
this.selectedIds().size > 0 && !this.allSelected()
);
toggleItem(id: string) {
this.selectedIds.update(ids => {
const next = new Set(ids);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
}
toggleAll() {
if (this.allSelected()) {
this.selectedIds.set(new Set());
} else {
this.selectedIds.set(new Set(this.items().map(i => i.id)));
}
}
}Related skills
How it compares
Use angular-best-practices for opinionated Angular 21 scaffolding; reach for generic TypeScript skills when the stack is not Angular.
FAQ
Does angular-best-practices use NgModules or standalone?
angular-best-practices always uses standalone components and does not set standalone: true because that flag is the default in Angular v20 and later.
Which Angular 21 patterns does the skill enforce?
angular-best-practices enforces signal-based state, OnPush change detection, strict TypeScript typing, reactive forms, lazy loading, ng-icon setup, Tailwind styling, and accessibility requirements.
Is Angular Best Practices safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.