Angular Cheat Sheet π
A quick-reference card for Angular 21 syntax and APIs. Bookmark this page for when you need to look something up fast.
π οΈ CLI Commands
Section titled βπ οΈ CLI Commandsβ# Create a new projectng new my-app
# Generate components, services, etc.ng generate component features/users/user-listng generate service core/authng generate pipe shared/pipes/truncateng generate directive shared/directives/highlightng generate guard core/authng generate interceptor core/api
# Serve, build, testng serve # Dev server on localhost:4200ng build # Production buildng test # Unit testsng e2e # End-to-end tests
# Useful flagsng serve --port 4300 # Custom portng build --configuration=stagingng generate component my-comp --inline-template --inline-styleπ§© Component Syntax
Section titled βπ§© Component Syntaxβimport { Component, ChangeDetectionStrategy, input, output, computed, signal } from '@angular/core';
@Component({ selector: 'app-example', changeDetection: ChangeDetectionStrategy.OnPush, template: `<h1>{{ title() }}</h1>`, styles: `h1 { color: blue; }`,})export class ExampleComponent { // Inputs title = input.required<string>(); // required count = input(0); // with default label = input('default', { // with alias alias: 'buttonLabel' });
// Two-way binding value = model(0); // model signal
// Outputs clicked = output<MouseEvent>(); closed = output<void>();
// State isOpen = signal(false);
// Derived state displayText = computed(() => `${this.title()} (${this.count()})`);}π Template Syntax
Section titled βπ Template SyntaxβControl Flow
Section titled βControl Flowβ<!-- Conditional -->@if (user()) { <p>Hello, {{ user()!.name }}</p>} @else if (loading()) { <app-spinner />} @else { <p>Please log in.</p>}
<!-- Loops -->@for (item of items(); track item.id) { <div>{{ $index + 1 }}. {{ item.name }}</div>} @empty { <p>No items found.</p>}
<!-- Switch -->@switch (status()) { @case ('active') { <span class="badge green">Active</span> } @case ('inactive') { <span class="badge red">Inactive</span> } @default { <span class="badge gray">Unknown</span> }}
<!-- Local variable -->@let fullName = firstName() + ' ' + lastName();<p>{{ fullName }}</p>Bindings
Section titled βBindingsβ<!-- Property binding --><input [value]="name()" /><div [class.active]="isActive()">...</div><div [style.color]="textColor()">...</div><img [attr.aria-label]="description()" />
<!-- Event binding --><button (click)="handleClick()">Click</button><input (input)="onInput($event)" /><form (ngSubmit)="onSubmit()">...</form>
<!-- Two-way binding --><input [(ngModel)]="name" /><app-slider [(value)]="volume" />
<!-- Template reference --><input #nameInput /><button (click)="nameInput.focus()">Focus</button>Defer Blocks
Section titled βDefer Blocksβ@defer (on viewport) { <app-heavy-widget />} @placeholder { <div>Widget placeholder</div>} @loading (minimum 200ms) { <app-spinner />} @error { <p>Failed to load widget.</p>}
<!-- Other triggers -->@defer (on idle) { ... }@defer (on interaction) { ... }@defer (on hover) { ... }@defer (on timer(3s)) { ... }@defer (when condition()) { ... }π‘ Signals API
Section titled βπ‘ Signals APIβimport { signal, computed, effect, linkedSignal, untracked } from '@angular/core';
// Writable signalconst count = signal(0);count(); // read β 0count.set(5); // setcount.update(c => c + 1); // update based on current
// Computed (read-only, auto-tracked)const double = computed(() => count() * 2);
// Effect (side effects)effect(() => { console.log('Count is now:', count());});
// Linked signal (derived + writable)const selectedIndex = linkedSignal(() => { // Re-derives when items change return items().length > 0 ? 0 : -1;});selectedIndex.set(3); // can also be set manually
// Untracked reads (don't create dependency)effect(() => { const c = count(); const name = untracked(() => userName()); console.log(c, name);});
// Resource API (async data)const usersResource = resource({ request: () => ({ page: currentPage() }), loader: async ({ request }) => { const res = await fetch(`/api/users?page=${request.page}`); return res.json(); },});usersResource.value(); // the datausersResource.isLoading(); // loading stateusersResource.error(); // error stateusersResource.reload(); // trigger reloadπ Dependency Injection
Section titled βπ Dependency Injectionβ// Injectable service@Injectable({ providedIn: 'root' })export class MyService { private readonly http = inject(HttpClient);}
// Injection tokenconst API_URL = new InjectionToken<string>('API_URL');
// inject() optionsinject(MyService); // requiredinject(MyService, { optional: true }); // nullableinject(MyService, { self: true }); // only from selfinject(MyService, { skipSelf: true }); // skip self, check parent
// Providing valuesconst appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideHttpClient(withInterceptors([authInterceptor])), provideAnimations(), { provide: API_URL, useValue: 'https://api.example.com' }, { provide: Logger, useClass: ConsoleLogger }, { provide: Config, useFactory: () => loadConfig() }, { provide: Token, useExisting: OtherToken }, ],};π§ Routing
Section titled βπ§ Routingβ// Route configurationexport const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: 'products/:id', component: ProductDetailComponent, resolve: { product: productResolver }, }, { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes), canActivate: [authGuard], }, { path: 'dashboard', loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent), }, { path: '**', component: NotFoundComponent },];
// Functional guardexport const authGuard: CanActivateFn = (route, state) => { const auth = inject(AuthService); const router = inject(Router);
return auth.isLoggedIn() ? true : router.createUrlTree(['/login']);};
// Functional resolverexport const productResolver: ResolveFn<Product> = (route) => { return inject(ProductService).getById(route.paramMap.get('id')!);};<!-- Template --><a routerLink="/products" routerLinkActive="active">Products</a><a [routerLink]="['/products', product.id]">{{ product.name }}</a><router-outlet />π Forms
Section titled βπ FormsβReactive Forms
Section titled βReactive Formsβ@Component({ /* ... */ })export class LoginComponent { private readonly fb = inject(FormBuilder);
form = this.fb.group({ email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]], rememberMe: [false], });
onSubmit() { if (this.form.valid) { console.log(this.form.value); } }}<form [formGroup]="form" (ngSubmit)="onSubmit()"> <input formControlName="email" type="email" /> @if (form.controls.email.errors?.['required']) { <span class="error">Email is required</span> }
<input formControlName="password" type="password" /> <label> <input formControlName="rememberMe" type="checkbox" /> Remember me </label>
<button type="submit" [disabled]="form.invalid">Login</button></form>π HTTP
Section titled βπ HTTPβ// Using HttpClient@Injectable({ providedIn: 'root' })export class ApiService { private readonly http = inject(HttpClient);
getUsers(): Observable<User[]> { return this.http.get<User[]>('/api/users'); }
createUser(user: CreateUserDto): Observable<User> { return this.http.post<User>('/api/users', user); }
updateUser(id: string, data: Partial<User>): Observable<User> { return this.http.put<User>(`/api/users/${id}`, data); }
deleteUser(id: string): Observable<void> { return this.http.delete<void>(`/api/users/${id}`); }}
// Using httpResource (Angular 21)const usersResource = httpResource<User[]>('/api/users');usersResource.value(); // User[] | undefinedusersResource.isLoading(); // booleanusersResource.error(); // Error | undefined
// With reactive URLconst userResource = httpResource<User>(() => `/api/users/${userId()}`);
// Functional interceptorexport const authInterceptor: HttpInterceptorFn = (req, next) => { const token = inject(AuthService).token(); if (token) { req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next(req);};π Lifecycle Hooks
Section titled βπ Lifecycle Hooksβexport class MyComponent implements OnInit, OnDestroy { // In order of execution: // constructor β DI resolution // ngOnChanges β when input bindings change // ngOnInit β after first ngOnChanges // ngDoCheck β every change detection run // ngAfterContentInit β after content projection // ngAfterContentChecked // ngAfterViewInit β after view initialization // ngAfterViewChecked // ngOnDestroy β before component is destroyed
ngOnInit() { /* one-time setup */ } ngOnDestroy() { /* cleanup */ }}
// Modern alternative β DestroyRefexport class MyComponent { private readonly destroyRef = inject(DestroyRef);
constructor() { someObservable$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(val => { /* ... */ }); }}π§ Built-in Pipes
Section titled βπ§ Built-in Pipesβ<!-- Text transforms -->{{ 'hello' | uppercase }} <!-- HELLO -->{{ 'HELLO' | lowercase }} <!-- hello -->{{ 'hello world' | titlecase }} <!-- Hello World -->
<!-- Numbers -->{{ 3.14159 | number:'1.2-2' }} <!-- 3.14 -->{{ 0.85 | percent }} <!-- 85% -->{{ 99.99 | currency:'USD' }} <!-- $99.99 -->
<!-- Dates -->{{ today | date:'short' }} <!-- 1/15/25, 3:30 PM -->{{ today | date:'fullDate' }} <!-- Wednesday, January 15, 2025 -->{{ today | date:'yyyy-MM-dd' }} <!-- 2025-01-15 -->
<!-- Objects -->{{ myObject | json }} <!-- JSON string -->{{ myObject | keyvalue }} <!-- key-value pairs for @for -->
<!-- Async -->{{ observable$ | async }} <!-- unwrap observable -->
<!-- Array -->{{ items | slice:0:5 }} <!-- first 5 items -->π§ͺ Testing Quick Reference
Section titled βπ§ͺ Testing Quick Referenceβ// Component test setupdescribe('MyComponent', () => { let fixture: ComponentFixture<MyComponent>; let component: MyComponent;
beforeEach(async () => { await TestBed.configureTestingModule({ imports: [MyComponent], providers: [ { provide: MyService, useValue: mockService }, ], }).compileComponents();
fixture = TestBed.createComponent(MyComponent); component = fixture.componentInstance; fixture.detectChanges(); });
it('should render title', () => { const el: HTMLElement = fixture.nativeElement; expect(el.querySelector('h1')?.textContent).toContain('Hello'); });});
// Service testdescribe('AuthService', () => { let service: AuthService; let httpMock: HttpTestingController;
beforeEach(() => { TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()], }); service = TestBed.inject(AuthService); httpMock = TestBed.inject(HttpTestingController); });
it('should login', () => { service.login({ email: 'a@b.com', password: '123' }).subscribe(user => { expect(user.email).toBe('a@b.com'); }); const req = httpMock.expectOne('/api/auth/login'); req.flush({ email: 'a@b.com', name: 'Test' }); });});