Angular 21 coding standards and best practices for any Angular application — new or existing. Covers standalone components, dependency injection with inject(), OnPush change detection, RxJS reactive patterns, signals, routing, reactive forms, Jest testing, naming conventions, and i18n with Transloco. Use this skill whenever working on Angular TypeScript or HTML files, whether creating a new app from scratch, adding features to an existing one, or reviewing Angular code. Also carries MIND CTI company-specific conventions for the MindBill Angular frontends.
General Angular 21 best practices, with MIND CTI company-specific conventions noted where applicable.
This skill applies to any Angular application — both new projects and existing ones. It targets Angular 21 but the principles apply from Angular 17+ (standalone, signals, takeUntilDestroyed()).
MIND CTI currently runs four Angular 21 frontends where this skill is specifically calibrated:
inject() over constructor injection — Use the inject() function in the class body; never use constructor parameter injection in Angular code.ChangeDetectionStrategy.OnPush on every component. This is the default for new code.takeUntilDestroyed() — Never leave subscriptions dangling; use the takeUntilDestroyed() operator.Observables for HTTP and event streams; Angular signals for local component/service state.*Adapter classes/services to map API responses to typed view models.*BlService services hold complex business rules.inject() in class body — private http = inject(HttpClient) — never constructor(private http: HttpClient).standalone: true (implicit in Angular 19+) and a self-contained imports array.ChangeDetectionStrategy.OnPush — Required on every component. Use cdr.detectChanges() only when imperative updates are needed.takeUntilDestroyed() — Pipe all subscriptions through takeUntilDestroyed() (or takeUntilDestroyed(this.destroyRef) in ngOnInit/lifecycle hooks).@Injectable({ providedIn: 'root' }) — Default scope for all services. Use component-level providers only when truly needed.SomeAdapter or SomeAdapterService maps raw API JSON → typed model. Keep adapters providedIn: 'root' and @Injectable.*-bl.service.ts files, separate from HTTP services.input() and output() signals, not @Input()/@Output() decorators.async pipe or toSignal() over subscribing inside ngOnInit.ReactiveFormsModule for all forms. Template-driven forms only for trivial single-field cases.async pipe or toSignal() — Bind observables to templates via | async or convert with toSignal() — never subscribe just to set a component property.jest-preset-angular. No Karma/Jasmine.@ngneat/spectator — Use createComponentFactory / createServiceFactory for test setup..ts and .html files must pass ng lint and prettier --check.provideHttpClient(withInterceptorsFromDi()) — Register interceptors via DI providers, not the deprecated HTTP_INTERCEPTORS token directly.inject(), service scoping, BL pattern, adapter pattern → services-and-di.mdAppConfigService initialized via APP_INITIALIZER.provideHttpClient(withInterceptorsFromDi()) + DI providers. Keep interceptors in a providers/ or interceptors/ directory.strict: true in tsconfig.json. Avoid any — use unknown + type guards at API boundaries.package.json under "engines".@if, @for, @switch over *ngIf, *ngFor, *ngSwitch in new code.These conventions apply specifically to MIND CTI MindBill Angular projects. For general Angular projects, adapt to your own project's conventions.
dm- (DrawerManagement), pos- (POS), drs- (DRS), ord- (Orders). The root component is always app-root.*Adapter / *AdapterService classes. Do not introduce mapping libraries.*-bl.service.ts, separate from HTTP data services.LogService wrapper (LogService.getInstance('ClassName')). Do not call console.log directly in production code.--runInBand).