Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Angular is Google's TypeScript-based frontend framework for building scalable single-page applications. It provides components, dependency injection, RxJS-based reactivity, routing, forms, HTTP client, and a powerful CLI for development.
.claude/skills/terminalskills-angular/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 56% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 139% | 0% |
Angular is an opinionated, full-featured frontend framework. It uses TypeScript, components with templates, dependency injection, RxJS for async data, and a CLI for scaffolding.
bash# Create new Angular project npm i -g @angular/cli ng new my-app --routing --style=scss cd my-app ng serve
# Angular project layout
src/app/
├── app.component.ts # Root component
├── app.config.ts # Application config
├── app.routes.ts # Route definitions
├── articles/
│ ├── article-list/ # List component
│ ├── article-detail/ # Detail component
│ ├── article.service.ts # Data service
│ └── article.model.ts # Interface/type
├── auth/
│ ├── auth.service.ts
│ ├── auth.guard.ts
│ └── auth.interceptor.ts
└── shared/
├── components/
└── pipes/typescript// src/app/articles/article-list/article-list.component.ts — standalone component import { Component, inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterLink } from '@angular/router'; import { ArticleService } from '../article.service'; import { Article } from '../article.model'; @Component({ selector: 'app-article-list', standalone: true, imports: [CommonModule, RouterLink], template: ` <h1>Articles</h1> @for (article of articles; track article.id) { <article> <h2><a [routerLink]="['/articles', article.slug]">{{ article.title }}</a></h2> <p>{{ article.excerpt }}</p> </article> } @empty { <p>No articles found.</p> } `, }) export class ArticleListComponent implements OnInit { private articleService = inject(ArticleService); articles: Article[] = []; ngOnInit() { this.articleService.getAll().subscribe((data) => (this.articles = data)); } }
typescript// src/app/articles/article-list/article-list.component.ts — signals-based component import { Component, signal, computed, inject, OnInit } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ArticleService } from '../article.service'; @Component({ selector: 'app-article-list', standalone: true, template: ` <input (input)="search.set($any($event.target).value)" placeholder="Search..." /> <div>{{ filteredCount() }} articles found</div> @for (article of filtered(); track article.id) { <article><h2>{{ article.title }}</h2></article> } `, }) export class ArticleListComponent { private svc = inject(ArticleService); articles = toSignal(this.svc.getAll(), { initialValue: [] }); search = signal(''); filtered = computed(() => this.articles().filter((a) => a.title.toLowerCase().includes(this.search().toLowerCase())) ); filteredCount = computed(() => this.filtered().length); }
typescript// src/app/articles/article.service.ts — injectable data service import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Article } from './article.model'; @Injectable({ providedIn: 'root' }) export class ArticleService { private http = inject(HttpClient); private baseUrl = '/api/articles'; getAll(): Observable<Article[]> { return this.http.get<Article[]>(this.baseUrl); } getBySlug(slug: string): Observable<Article> { return this.http.get<Article>(`${this.baseUrl}/${slug}`); } create(article: Partial<Article>): Observable<Article> { return this.http.post<Article>(this.baseUrl, article); } }
typescript// src/app/app.routes.ts — application routes import { Routes } from '@angular/router'; import { authGuard } from './auth/auth.guard'; export const routes: Routes = [ { path: '', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) }, { path: 'articles', loadComponent: () => import('./articles/article-list/article-list.component').then(m => m.ArticleListComponent) }, { path: 'articles/:slug', loadComponent: () => import('./articles/article-detail/article-detail.component').then(m => m.ArticleDetailComponent) }, { path: 'admin', loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent), canActivate: [authGuard] }, { path: '**', redirectTo: '' }, ];
typescript// src/app/auth/auth.guard.ts — functional route guard import { inject } from '@angular/core'; import { Router, CanActivateFn } from '@angular/router'; import { AuthService } from './auth.service'; export const authGuard: CanActivateFn = () => { const auth = inject(AuthService); const router = inject(Router); return auth.isLoggedIn() ? true : router.createUrlTree(['/login']); };
typescript// src/app/auth/auth.interceptor.ts — HTTP interceptor import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; import { AuthService } from './auth.service'; export const authInterceptor: HttpInterceptorFn = (req, next) => { const token = inject(AuthService).getToken(); if (token) { req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next(req); };
typescript// src/app/articles/article-form/article-form.component.ts — reactive form import { Component, inject } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { ArticleService } from '../article.service'; import { Router } from '@angular/router'; @Component({ selector: 'app-article-form', standalone: true, imports: [ReactiveFormsModule], template: ` <form [formGroup]="form" (ngSubmit)="submit()"> <input formControlName="title" placeholder="Title" /> <textarea formControlName="body" placeholder="Body"></textarea> <button type="submit" [disabled]="form.invalid">Create</button> </form> `, }) export class ArticleFormComponent { private fb = inject(FormBuilder); private svc = inject(ArticleService); private router = inject(Router); form = this.fb.nonNullable.group({ title: ['', [Validators.required, Validators.maxLength(200)]], body: ['', Validators.required], }); submit() { if (this.form.valid) { this.svc.create(this.form.getRawValue()).subscribe(() => this.router.navigate(['/articles'])); } } }
typescript// src/app/app.config.ts — application configuration import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { routes } from './app.routes'; import { authInterceptor } from './auth/auth.interceptor'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), ], };
inject() function instead of constructor injection for cleaner codeloadComponent for smaller initial bundles@for/@if/@switch control flow syntax (Angular 17+) instead of *ngFor/*ngIf| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | pass→pass | 10,742 | 5,992 | -44% | 1 | 1 | 0% | 1,977 | 3,207 | +62% | 0 | 0 | — |
case-07 | fail→pass | 14,625 | 10,994 | -25% | 1 | 1 | 0% | 3,380 | 4,674 | +38% | 0 | 0 | — |
case-01 | fail→pass | 10,118 | 9,025 | -11% | 1 | 1 | 0% | 2,312 | 3,602 | +56% | 0 | 0 | — |
case-02 | pass→pass | 8,996 | 6,688 | -26% | 1 | 1 | 0% | 2,322 | 3,448 | +48% | 0 | 0 | — |
case-03 | fail→pass | 13,780 | 11,895 | -14% | 1 | 1 | 0% | 2,954 | 4,595 | +56% | 0 | 0 | — |
case-04 | fail→fail | 10,398 | 8,779 | -16% | 1 | 1 | 0% | 2,431 | 3,538 | +46% | 0 | 0 | — |
case-05 | pass→pass | 9,718 | 6,589 | -32% | 1 | 1 | 0% | 2,129 | 3,535 | +66% | 0 | 0 | — |
case-06 | pass→pass | 7,709 | 6,549 | -15% | 1 | 1 | 0% | 1,666 | 3,123 | +87% | 0 | 0 | — |
case-08 | fail→pass | 7,290 | 5,029 | -31% | 1 | 1 | 0% | 1,633 | 3,221 | +97% | 0 | 0 | — |
case-09 | pass→pass | 5,201 | 5,019 | -3% | 1 | 1 | 0% | 1,014 | 3,028 | +199% | 0 | 0 | — |
case-10 | fail→pass | 5,795 | 4,553 | -21% | 1 | 1 | 0% | 1,207 | 2,883 | +139% | 0 | 0 | — |
case-11 | pass→pass | 8,595 | 6,305 | -27% | 1 | 1 | 0% | 1,692 | 3,255 | +92% | 0 | 0 | — |
case-13 | pass→pass | 5,892 | 3,191 | -46% | 1 | 1 | 0% | 1,127 | 2,633 | +134% | 0 | 0 | — |
case-14 | pass→pass | 8,425 | 6,773 | -20% | 1 | 1 | 0% | 1,749 | 3,470 | +98% | 0 | 0 | — |
case-15 | pass→pass | 7,341 | 3,974 | -46% | 1 | 1 | 0% | 1,334 | 2,723 | +104% | 0 | 0 | — |
case-16 | pass→pass | 6,289 | 3,456 | -45% | 1 | 1 | 0% | 1,181 | 2,596 | +120% | 0 | 0 | — |
case-17 | pass→pass | 11,318 | 9,600 | -15% | 1 | 1 | 0% | 2,138 | 3,848 | +80% | 0 | 0 | — |
case-18 | pass→pass | 2,134 | 1,316 | -38% | 1 | 1 | 0% | 348 | 2,221 | +538% | 0 | 0 | — |
case-19 | pass→pass | 4,231 | 3,239 | -23% | 1 | 1 | 0% | 634 | 2,597 | +310% | 0 | 0 | — |
case-20 | pass→pass | 14,251 | 14,819 | +4% | 1 | 1 | 0% | 2,987 | 5,164 | +73% | 0 | 0 | — |
case-21 | pass→pass | 10,819 | 9,395 | -13% | 1 | 1 | 0% | 2,225 | 3,992 | +79% | 0 | 0 | — |
case-22 | pass→pass | 8,638 | 6,121 | -29% | 1 | 1 | 0% | 1,677 | 3,157 | +88% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.