📦 deps(skills): sync thirdparty skills

This commit is contained in:
ci[bot]
2026-08-14 17:16:06 +08:00
parent 5d73964db8
commit 7233646398
68 changed files with 66265 additions and 4343 deletions
+51 -51
View File
@@ -1,51 +1,51 @@
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL
1,Components,Use standalone components,Angular 17+ default; no NgModule needed,Standalone components for all new code,NgModule-based components for new projects,@Component({ standalone: true imports: [CommonModule] }),@NgModule({ declarations: [MyComp] }),High,https://angular.dev/guide/components/importing
2,Components,Use signals for state,Signals are Angular's reactive primitive for fine-grained reactivity,Signals for component state over class properties,Mutable class properties without signals,count = signal(0); increment() { this.count.update(v => v + 1) },count = 0; increment() { this.count++ },High,https://angular.dev/guide/signals
3,Components,Use @if/@for/@switch control flow,Built-in control flow syntax replaces *ngIf/*ngFor directives,@if and @for in templates,*ngIf and *ngFor structural directives,@if (isLoggedIn) { <Dashboard /> } @else { <Login /> },"<div *ngIf=""isLoggedIn""><Dashboard /></div>",High,https://angular.dev/guide/templates/control-flow
4,Components,Use input() and output() signals,Signal-based inputs/outputs replace @Input()/@Output() decorators,input() and output() for component API,@Input() and @Output() decorators,name = input<string>(); clicked = output<void>(),@Input() name: string; @Output() clicked = new EventEmitter(),High,https://angular.dev/guide/components/inputs
5,Components,Use content projection,ng-content for flexible component composition,ng-content with select for named slots,Rigid templates that can't be customized,"<ng-content select=""[header]"" /> <ng-content />","<div class=""header"">{{ title }}</div>",Medium,https://angular.dev/guide/components/content-projection
6,Components,Keep components small,Single responsibility; components should do one thing,Extract sub-components when template exceeds 50 lines,Monolithic components handling multiple concerns,<UserAvatar /> <UserDetails /> <UserActions />,One 300-line component template,Medium,https://angular.dev/guide/components
7,Components,Use OnPush change detection,Reduces re-renders by only checking on input changes or signal updates,OnPush for all components,Default change detection strategy,changeDetection: ChangeDetectionStrategy.OnPush,changeDetection: ChangeDetectionStrategy.Default,High,https://angular.dev/guide/components/lifecycle
8,Components,Avoid direct DOM manipulation,Use renderer or ElementRef sparingly; prefer template bindings,Template bindings and Angular directives,Direct document.querySelector or innerHTML,"[class.active]=""isActive""",this.el.nativeElement.classList.add('active'),High,https://angular.dev/guide/components/host-elements
9,Routing,Lazy load feature routes,Load route chunks on demand to reduce initial bundle,loadComponent() for all feature routes,Eager-loaded routes in app config,{ path: 'admin' loadComponent: () => import('./admin/admin.component') },{ path: 'admin' component: AdminComponent },High,https://angular.dev/guide/routing/lazy-loading
10,Routing,Use route guards with functional API,Protect routes with canActivate/canMatch functional guards,Functional guards returning boolean or UrlTree,Class-based guards with CanActivate interface,canActivate: [() => inject(AuthService).isLoggedIn()],canActivate: [AuthGuard],High,https://angular.dev/guide/routing/common-router-tasks#preventing-unauthorized-access
11,Routing,Use route resolvers for data,Pre-fetch data before route activation using resolve,ResolveFn for route data,Fetching data in ngOnInit causing flash of empty state,resolve: { user: () => inject(UserService).getUser() },Fetch in ngOnInit with loading state flickering,Medium,https://angular.dev/guide/routing/common-router-tasks#resolve
12,Routing,Type route params with inject,Use inject(ActivatedRoute) with signals or toSignal,Typed route params via ActivatedRoute,Untyped route.snapshot.params string access,const id = toSignal(route.paramMap.pipe(map(p => p.get('id')))),const id = this.route.snapshot.params['id'],Medium,https://angular.dev/api/router/ActivatedRoute
13,Routing,Use nested routes for layouts,Compose shared layouts using router-outlet nesting,Nested routes with shared layout components,Duplicating layout code across routes,{ path: 'app' component: ShellComponent children: [...] },Duplicate header/sidebar in each route component,Medium,https://angular.dev/guide/routing/router-tutorial-toh#child-route-configuration
14,Routing,Configure preloading strategies,Preload lazy modules in background after initial load,PreloadAllModules or custom strategy,No preloading causing delayed navigation,provideRouter(routes withPreloading(PreloadAllModules)),provideRouter(routes),Low,https://angular.dev/api/router/PreloadAllModules
15,State,Use signals for local state,Signals provide synchronous reactive state without RxJS overhead,signal() for component-local reactive state,BehaviorSubject for simple local state,const items = signal<Item[]>([]); addItem(i: Item) { this.items.update(arr => [...arr i]) },items$ = new BehaviorSubject<Item[]>([]),High,https://angular.dev/guide/signals
16,State,Use computed() for derived state,Lazily evaluated derived values that update when dependencies change,computed() for values derived from other signals,Duplicated state or manual sync,readonly total = computed(() => this.items().reduce((s i) => s + i.price 0)),this.total = this.items.reduce(...) // called manually,High,https://angular.dev/guide/signals#computed-signals
17,State,Use effect() carefully,Effects run side effects when signals change; avoid overuse,effect() for side effects like logging or localStorage sync,effect() for deriving state (use computed instead),effect(() => localStorage.setItem('cart' JSON.stringify(this.cart()))),effect(() => { this.total.set(this.items().length) }),Medium,https://angular.dev/guide/signals#effects
18,State,Use NgRx Signal Store for complex state,NgRx Signal Store is the modern lightweight state management for Angular,@ngrx/signals SignalStore for feature state,Full NgRx reducer/action/effect boilerplate for simple state,const Store = signalStore(withState({ count: 0 }) withMethods(s => ({ increment: () => patchState(s { count: s.count() + 1 }) }))),createReducer(on(increment state => ({ ...state count: state.count + 1 }))),Medium,https://ngrx.io/guide/signals
19,State,Inject services for shared state,Services with signals share state across components without a store,Injectable service with signals for cross-component state,Prop drilling or @Input chains for shared state,@Injectable({ providedIn: 'root' }) class CartService { items = signal<Item[]>([]) },@Input() cartItems passed through 4 component levels,Medium,https://angular.dev/guide/di/creating-injectable-service
20,State,Avoid mixing RxJS and signals unnecessarily,Use toSignal() to bridge RxJS into signal world at the boundary,toSignal() to convert observable to signal at component edge,Subscribing in components and storing in signal manually,readonly user = toSignal(this.userService.user$),this.userService.user$.subscribe(u => this.user.set(u)),Medium,https://angular.dev/guide/rxjs-interop
21,Forms,Use typed reactive forms,FormGroup/FormControl with explicit generics for compile-time safety,FormBuilder with typed controls,Untyped FormControl or any casts,fb.group<LoginForm>({ email: fb.control('') password: fb.control('') }),new FormGroup({ email: new FormControl(null) }),High,https://angular.dev/guide/forms/typed-forms
22,Forms,Use reactive forms over template-driven,Reactive forms scale better and are fully testable,ReactiveFormsModule for all non-trivial forms,FormsModule with ngModel for complex forms,"<input [formControl]=""emailControl"" />","<input [(ngModel)]=""email"" />",Medium,https://angular.dev/guide/forms/reactive-forms
23,Forms,Write custom validators as functions,Functional validators are composable and tree-shakeable,ValidatorFn functions for custom validation,Class-based validators implementing Validator interface,const noSpaces: ValidatorFn = ctrl => ctrl.value?.includes(' ') ? { noSpaces: true } : null,class NoSpacesValidator implements Validator { validate(c) {} },Medium,https://angular.dev/guide/forms/form-validation#custom-validators
24,Forms,Use updateOn for performance,Control when validation runs to avoid per-keystroke validation overhead,updateOn: 'blur' or 'submit' for expensive validators,Default updateOn: 'change' for async validators,fb.control('' { updateOn: 'blur' validators: [Validators.email] }),fb.control('' [Validators.email]) // validates on every key,Low,https://angular.dev/api/forms/AbstractControl#updateOn
25,Forms,Use FormArray for dynamic fields,FormArray manages variable-length lists of controls,FormArray for add/remove field scenarios,Manually tracking index-based controls,get items(): FormArray { return this.form.get('items') as FormArray },items: [FormControl] managed outside form,Medium,https://angular.dev/guide/forms/reactive-forms#using-the-formarray-class
26,Forms,Display validation errors clearly,Use form control touched and dirty states to show errors at the right time,Show errors after field is touched,Show all errors on page load,@if (email.invalid && email.touched) { <span>Invalid email</span> },@if (email.invalid) { <span>Invalid email</span> },Medium,https://angular.dev/guide/forms/form-validation
27,Performance,Apply OnPush to all components,OnPush + signals eliminates most unnecessary change detection cycles,OnPush change detection everywhere,Default strategy which checks entire tree on every event,changeDetection: ChangeDetectionStrategy.OnPush,changeDetection: ChangeDetectionStrategy.Default,High,https://angular.dev/best-practices/skipping-component-subtrees
28,Performance,Use trackBy in @for blocks,Stable identity for list items prevents full DOM re-creation on change,track item.id in @for,track $index for dynamic data,@for (item of items; track item.id) { <li>{{ item.name }}</li> },@for (item of items; track $index) { <li>{{ item.name }}</li> },High,https://angular.dev/guide/templates/control-flow#track-and-identity
29,Performance,Use @defer for below-the-fold content,Defer blocks lazy-load components when they enter the viewport,@defer with on viewport for non-critical UI,Eagerly loading all components at startup,@defer (on viewport) { <HeavyChart /> } @placeholder { <Skeleton /> },<HeavyChart /> loaded at startup,High,https://angular.dev/guide/defer
30,Performance,Use NgOptimizedImage,Enforces image best practices: lazy loading LCP hints and proper sizing,NgOptimizedImage for all img tags,Plain img tags for CMS or user content,"<img ngSrc=""/hero.jpg"" width=""800"" height=""400"" priority />","<img src=""/hero.jpg"" />",High,https://angular.dev/guide/image-optimization
31,Performance,Tree-shake unused Angular features,Import only what you use from Angular packages,Import specific Angular modules needed,Import BrowserAnimationsModule when not using animations,import { NgOptimizedImage } from '@angular/common',import { CommonModule } from '@angular/common' // entire module,Medium,https://angular.dev/tools/cli/build
32,Performance,Avoid subscribe in components,Subscriptions leak and cause bugs; prefer async pipe or toSignal,toSignal() or async pipe instead of manual subscribe,Manual subscribe without unsubscribe in ngOnDestroy,readonly data = toSignal(this.service.data$),this.service.data$.subscribe(d => this.data = d),High,https://angular.dev/guide/rxjs-interop
33,Performance,Use SSR with Angular Universal,Pre-render pages for faster LCP and better SEO,SSR or SSG for public-facing routes,Pure CSR for SEO-critical pages,ng add @angular/ssr,"// no SSR, client renders empty shell",Medium,https://angular.dev/guide/ssr
34,Performance,Minimize bundle with standalone APIs,Standalone components + provideRouter() eliminate dead NgModule code,provideRouter() and provideHttpClient() in app.config,Root AppModule with all imports,provideRouter(routes) in app.config.ts,@NgModule({ imports: [RouterModule.forRoot(routes)] }),Medium,https://angular.dev/guide/routing/standalone
35,Testing,Use TestBed for component tests,TestBed sets up Angular DI for realistic component testing,TestBed.configureTestingModule for component tests,Instantiate components with new keyword,TestBed.configureTestingModule({ imports: [MyComponent] }),const comp = new MyComponent(),High,https://angular.dev/guide/testing/components-basics
36,Testing,Use Angular CDK component harnesses,Harnesses provide a stable testing API that survives template refactors,MatButtonHarness and custom HarnessLoader,Direct native element queries that break on template changes,const btn = await loader.getHarness(MatButtonHarness),fixture.debugElement.query(By.css('button')),Medium,https://material.angular.io/cdk/test-harnesses/overview
37,Testing,Use Spectator for less boilerplate,Spectator wraps TestBed with a cleaner API reducing test setup noise,Spectator for unit tests,Raw TestBed for every test,const spectator = createComponentFactory(MyComponent),TestBed.configureTestingModule({ declarations: [MyComponent] providers: [...] }),Low,https://github.com/ngneat/spectator
38,Testing,Mock services with jasmine.createSpyObj,Isolate unit tests by providing mock implementations of dependencies,SpyObj or jest.fn() mocks for services,Real HTTP calls in unit tests,const spy = jasmine.createSpyObj('UserService' ['getUser']); spy.getUser.and.returnValue(of(user)),providers: [UserService] // real service in unit test,High,https://angular.dev/guide/testing/services
39,Testing,Write integration tests for routes,Test full route navigation including guards and resolvers,RouterTestingHarness for route integration tests,Mock all routing behavior in unit tests,const harness = await RouterTestingHarness.create(); await harness.navigateByUrl('/home'),// manually calling route guard methods,Medium,https://angular.dev/api/router/testing/RouterTestingHarness
40,Testing,Test signal-based components,Signals update synchronously; no async flush needed in most cases,Read signal value directly in test assertions,TestBed.tick() or fakeAsync for signal reads,component.count.set(5); expect(component.double()).toBe(10),fakeAsync(() => { component.count.set(5); tick(); expect(component.double()).toBe(10) }),Medium,https://angular.dev/guide/testing
41,Styling,Use ViewEncapsulation.Emulated,Default emulation scopes styles to component preventing global leaks,Emulated or None for intentional global styles,ViewEncapsulation.None for component-specific styles,ViewEncapsulation.Emulated (default),ViewEncapsulation.None on feature components,Medium,https://angular.dev/guide/components/styling#style-scoping
42,Styling,Use :host selector,Style the component's host element using :host pseudo-class,:host for host element styles,Adding wrapper div just for styling,:host { display: block; padding: 1rem },"<div class=""wrapper"">...</div> + .wrapper { padding: 1rem }",Medium,https://angular.dev/guide/components/styling#host-element
43,Styling,Use CSS custom properties for theming,CSS variables work across component boundaries and enable dynamic theming,CSS custom properties for colors and spacing,Hardcoded hex values in component styles,:root { --primary: #6200ee } button { background: var(--primary) },button { background: #6200ee },Medium,https://angular.dev/guide/components/styling
44,Styling,Integrate Tailwind with Angular,Tailwind utilities work alongside Angular's ViewEncapsulation via global stylesheet,Add Tailwind in styles.css and use utility classes in templates,Custom CSS for layout that Tailwind already handles,"<div class=""flex items-center gap-4 p-6"">","<div class=""my-custom-flex""> /* .my-custom-flex { display: flex } */",Low,https://tailwindcss.com/docs/guides/angular
45,Styling,Use Angular Material theming tokens,Material 3 uses design tokens for systematic theming,M3 token-based theming for Angular Material,Overriding Angular Material CSS with deep selectors,@include mat.button-theme($my-theme),::ng-deep .mat-button { background: red },Medium,https://material.angular.io/guide/theming
46,Architecture,Use injection tokens for config,Provide configuration via InjectionToken for testability and flexibility,InjectionToken for environment-specific values,Importing environment.ts directly in services,const API_URL = new InjectionToken<string>('apiUrl'); provide: [{ provide: API_URL useValue: env.apiUrl }],constructor(private env: Environment) { this.url = env.apiUrl },Medium,https://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-object
47,Architecture,Use HTTP interceptors,Intercept requests for auth headers error handling and logging,Functional interceptors with withInterceptors(),Service-level header management in every request,withInterceptors([authInterceptor errorInterceptor]),httpClient.get(url { headers: { Authorization: token } }) in every call,High,https://angular.dev/guide/http/interceptors
48,Architecture,Organize by feature not type,Feature-based folder structure scales better than type-based,Feature folders with collocated component service and routes,Flat folders: all-components/ all-services/,src/features/checkout/checkout.component.ts checkout.service.ts checkout.routes.ts,src/components/checkout.component.ts src/services/checkout.service.ts,Medium,https://angular.dev/style-guide#folders-by-feature-structure
49,Architecture,Use environment configurations,Separate environment values for dev staging and prod via Angular build configs,angular.json fileReplacements for env configs,Hardcoded API URLs or feature flags in source,fileReplacements: [{ replace: environment.ts with: environment.prod.ts }],const API = 'https://api.example.com' // hardcoded in service,High,https://angular.dev/tools/cli/environments
50,Architecture,Prefer inject() over constructor DI,inject() function is composable and works in more contexts than constructor injection,inject() for dependency injection,Constructor parameters for new code,readonly http = inject(HttpClient); readonly router = inject(Router),constructor(private http: HttpClient private router: Router) {},Medium,https://angular.dev/api/core/inject
No,Category,Guideline,Description,Do,Don't,Code Good,Code Bad,Severity,Docs URL,Applies To,Status,Verified At
1,Components,Use standalone components,Standalone components are the default in current Angular,Standalone components for all new code,NgModule-based components for new projects,@Component({ imports: [CommonModule] }),@NgModule({ declarations: [MyComp] }),High,https://angular.dev/guide/components/importing,angular 22.x,active,2026-08-13
2,Components,Use signals for state,Signals are Angular's reactive primitive for fine-grained reactivity,Signals for component state over class properties,Mutable class properties without signals,count = signal(0); increment() { this.count.update(v => v + 1) },count = 0; increment() { this.count++ },High,https://angular.dev/guide/signals,angular 22.x,active,2026-08-13
3,Components,Use @if/@for/@switch control flow,Built-in control flow syntax replaces *ngIf/*ngFor directives,@if and @for in templates,*ngIf and *ngFor structural directives,@if (isLoggedIn) { <Dashboard /> } @else { <Login /> },"<div *ngIf=""isLoggedIn""><Dashboard /></div>",High,https://angular.dev/guide/templates/control-flow,angular 22.x,active,2026-08-13
4,Components,Use input() and output() component APIs,Use signal inputs and the output function for typed component contracts,input() and output() for new component APIs,Decorator-based inputs and outputs in new components,name = input<string>(); clicked = output<void>(),@Input() name: string; @Output() clicked = new EventEmitter(),High,https://angular.dev/guide/components/outputs,angular 22.x,active,2026-08-13
5,Components,Use content projection,ng-content for flexible component composition,ng-content with select for named slots,Rigid templates that can't be customized,"<ng-content select=""[header]"" /> <ng-content />","<div class=""header"">{{ title }}</div>",Medium,https://angular.dev/guide/components/content-projection,angular 22.x,active,2026-08-13
6,Components,Keep components small,Single responsibility; components should do one thing,Extract sub-components when template exceeds 50 lines,Monolithic components handling multiple concerns,<UserAvatar /> <UserDetails /> <UserActions />,One 300-line component template,Medium,https://angular.dev/guide/components,angular 22.x,active,2026-08-13
7,Components,Use OnPush where subtree skipping fits,Reduces checks for compatible component subtrees while signals still notify consumers,OnPush for stable input-driven components,Applying OnPush without understanding mutable input or event boundaries,changeDetection: ChangeDetectionStrategy.OnPush,items.push(nextItem) // same input reference,High,https://angular.dev/best-practices/skipping-subtrees,angular 22.x,active,2026-08-13
8,Components,Avoid direct DOM manipulation,Use renderer or ElementRef sparingly; prefer template bindings,Template bindings and Angular directives,Direct document.querySelector or innerHTML,"[class.active]=""isActive""",this.el.nativeElement.classList.add('active'),High,https://angular.dev/guide/components/host-elements,angular 22.x,active,2026-08-13
9,Routing,Lazy load feature routes,Load route chunks on demand to reduce initial bundle,loadComponent() for all feature routes,Eager-loaded routes in app config,{ path: 'admin' loadComponent: () => import('./admin/admin.component') },{ path: 'admin' component: AdminComponent },High,https://angular.dev/guide/routing/lazy-loading,angular 22.x,active,2026-08-13
10,Routing,Use route guards with functional API,Protect routes with canActivate/canMatch functional guards,Functional guards returning boolean or UrlTree,Class-based guards with CanActivate interface,canActivate: [() => inject(AuthService).isLoggedIn()],canActivate: [AuthGuard],High,https://angular.dev/guide/routing/common-router-tasks#preventing-unauthorized-access,angular 22.x,active,2026-08-13
11,Routing,Use route resolvers for data,Pre-fetch data before route activation using resolve,ResolveFn for route data,Fetching data in ngOnInit causing flash of empty state,resolve: { user: () => inject(UserService).getUser() },Fetch in ngOnInit with loading state flickering,Medium,https://angular.dev/guide/routing/common-router-tasks#resolve,angular 22.x,active,2026-08-13
12,Routing,Type route params with inject,Use inject(ActivatedRoute) with signals or toSignal,Typed route params via ActivatedRoute,Untyped route.snapshot.params string access,const id = toSignal(route.paramMap.pipe(map(p => p.get('id')))),const id = this.route.snapshot.params['id'],Medium,https://angular.dev/api/router/ActivatedRoute,angular 22.x,active,2026-08-13
13,Routing,Use nested routes for layouts,Compose shared layouts using router-outlet nesting,Nested routes with shared layout components,Duplicating layout code across routes,{ path: 'app' component: ShellComponent children: [...] },Duplicate header/sidebar in each route component,Medium,https://angular.dev/guide/routing/router-tutorial-toh#child-route-configuration,angular 22.x,active,2026-08-13
14,Routing,Configure preloading strategies,Preload lazy modules in background after initial load,PreloadAllModules or custom strategy,No preloading causing delayed navigation,provideRouter(routes withPreloading(PreloadAllModules)),provideRouter(routes),Low,https://angular.dev/api/router/PreloadAllModules,angular 22.x,active,2026-08-13
15,State,Use signals for local state,Signals provide synchronous reactive state without RxJS overhead,signal() for component-local reactive state,BehaviorSubject for simple local state,const items = signal<Item[]>([]); addItem(i: Item) { this.items.update(arr => [...arr i]) },items$ = new BehaviorSubject<Item[]>([]),High,https://angular.dev/guide/signals,angular 22.x,active,2026-08-13
16,State,Use computed() for derived state,Lazily evaluated derived values that update when dependencies change,computed() for values derived from other signals,Duplicated state or manual sync,readonly total = computed(() => this.items().reduce((s i) => s + i.price 0)),this.total = this.items.reduce(...) // called manually,High,https://angular.dev/guide/signals#computed-signals,angular 22.x,active,2026-08-13
17,State,Use effect() carefully,Effects run side effects when signals change; avoid overuse,effect() for side effects like logging or localStorage sync,effect() for deriving state (use computed instead),effect(() => localStorage.setItem('cart' JSON.stringify(this.cart()))),effect(() => { this.total.set(this.items().length) }),Medium,https://angular.dev/guide/signals#effects,angular 22.x,active,2026-08-13
18,State,Use NgRx Signal Store for complex state,NgRx Signal Store is the modern lightweight state management for Angular,@ngrx/signals SignalStore for feature state,Full NgRx reducer/action/effect boilerplate for simple state,const Store = signalStore(withState({ count: 0 }) withMethods(s => ({ increment: () => patchState(s { count: s.count() + 1 }) }))),createReducer(on(increment state => ({ ...state count: state.count + 1 }))),Medium,https://ngrx.io/guide/signals,angular 22.x,active,2026-08-13
19,State,Inject services for shared state,Services with signals share state across components without a store,Injectable service with signals for cross-component state,Prop drilling or @Input chains for shared state,@Injectable({ providedIn: 'root' }) class CartService { items = signal<Item[]>([]) },@Input() cartItems passed through 4 component levels,Medium,https://angular.dev/guide/di/creating-injectable-service,angular 22.x,active,2026-08-13
20,State,Avoid mixing RxJS and signals unnecessarily,Use toSignal() to bridge RxJS into signal world at the boundary,toSignal() to convert observable to signal at component edge,Subscribing in components and storing in signal manually,readonly user = toSignal(this.userService.user$),this.userService.user$.subscribe(u => this.user.set(u)),Medium,https://angular.dev/guide/rxjs-interop,angular 22.x,active,2026-08-13
21,Forms,Use typed reactive forms,FormGroup/FormControl with explicit generics for compile-time safety,FormBuilder with typed controls,Untyped FormControl or any casts,fb.group<LoginForm>({ email: fb.control('') password: fb.control('') }),new FormGroup({ email: new FormControl(null) }),High,https://angular.dev/guide/forms/typed-forms,angular 22.x,active,2026-08-13
22,Forms,Use reactive forms over template-driven,Reactive forms scale better and are fully testable,ReactiveFormsModule for all non-trivial forms,FormsModule with ngModel for complex forms,"<input [formControl]=""emailControl"" />","<input [(ngModel)]=""email"" />",Medium,https://angular.dev/guide/forms/reactive-forms,angular 22.x,active,2026-08-13
23,Forms,Write custom validators as functions,Functional validators are composable and tree-shakeable,ValidatorFn functions for custom validation,Class-based validators implementing Validator interface,const noSpaces: ValidatorFn = ctrl => ctrl.value?.includes(' ') ? { noSpaces: true } : null,class NoSpacesValidator implements Validator { validate(c) {} },Medium,https://angular.dev/guide/forms/form-validation#custom-validators,angular 22.x,active,2026-08-13
24,Forms,Use updateOn for performance,Control when validation runs to avoid per-keystroke validation overhead,updateOn: 'blur' or 'submit' for expensive validators,Default updateOn: 'change' for async validators,fb.control('' { updateOn: 'blur' validators: [Validators.email] }),fb.control('' [Validators.email]) // validates on every key,Low,https://angular.dev/api/forms/AbstractControl#updateOn,angular 22.x,active,2026-08-13
25,Forms,Use FormArray for dynamic fields,FormArray manages variable-length lists of controls,FormArray for add/remove field scenarios,Manually tracking index-based controls,get items(): FormArray { return this.form.get('items') as FormArray },items: [FormControl] managed outside form,Medium,https://angular.dev/guide/forms/reactive-forms#using-the-formarray-class,angular 22.x,active,2026-08-13
26,Forms,Display validation errors clearly,Use form control touched and dirty states to show errors at the right time,Show errors after field is touched,Show all errors on page load,@if (email.invalid && email.touched) { <span>Invalid email</span> },@if (email.invalid) { <span>Invalid email</span> },Medium,https://angular.dev/guide/forms/form-validation,angular 22.x,active,2026-08-13
27,Performance,Prepare components for zoneless change detection,Use signals markForCheck AsyncPipe and bound listeners so Angular receives change notifications,Notification-driven state updates,Depending on ZoneJS to notice arbitrary state mutation,count.set(count() + 1),this.count++ // outside a notifying Angular boundary,High,https://angular.dev/guide/zoneless,angular 22.x,active,2026-08-13
28,Performance,Use trackBy in @for blocks,Stable identity for list items prevents full DOM re-creation on change,track item.id in @for,track $index for dynamic data,@for (item of items; track item.id) { <li>{{ item.name }}</li> },@for (item of items; track $index) { <li>{{ item.name }}</li> },High,https://angular.dev/guide/templates/control-flow#track-and-identity,angular 22.x,active,2026-08-13
29,Performance,Use @defer for below-the-fold content,Defer blocks lazy-load components when they enter the viewport,@defer with on viewport for non-critical UI,Eagerly loading all components at startup,@defer (on viewport) { <HeavyChart /> } @placeholder { <Skeleton /> },<HeavyChart /> loaded at startup,High,https://angular.dev/guide/defer,angular 22.x,active,2026-08-13
30,Performance,Use NgOptimizedImage,Enforces image best practices: lazy loading LCP hints and proper sizing,NgOptimizedImage for all img tags,Plain img tags for CMS or user content,"<img ngSrc=""/hero.jpg"" width=""800"" height=""400"" priority />","<img src=""/hero.jpg"" />",High,https://angular.dev/guide/image-optimization,angular 22.x,active,2026-08-13
31,Performance,Tree-shake unused Angular features,Import only what you use from Angular packages,Import specific Angular modules needed,Import BrowserAnimationsModule when not using animations,import { NgOptimizedImage } from '@angular/common',import { CommonModule } from '@angular/common' // entire module,Medium,https://angular.dev/tools/cli/build,angular 22.x,active,2026-08-13
32,Performance,Avoid subscribe in components,Subscriptions leak and cause bugs; prefer async pipe or toSignal,toSignal() or async pipe instead of manual subscribe,Manual subscribe without unsubscribe in ngOnDestroy,readonly data = toSignal(this.service.data$),this.service.data$.subscribe(d => this.data = d),High,https://angular.dev/guide/rxjs-interop,angular 22.x,active,2026-08-13
33,Performance,Use @angular/ssr for hybrid rendering,Server rendering and prerendering improve LCP and SEO for public routes,Configure SSR or prerender per route,Pure CSR for SEO-critical pages,ng add @angular/ssr,"// no SSR, client renders empty shell",Medium,https://angular.dev/guide/ssr,angular 22.x,active,2026-08-13
34,Performance,Minimize bundle with standalone APIs,Standalone components + provideRouter() eliminate dead NgModule code,provideRouter() and provideHttpClient() in app.config,Root AppModule with all imports,provideRouter(routes) in app.config.ts,@NgModule({ imports: [RouterModule.forRoot(routes)] }),Medium,https://angular.dev/guide/routing/standalone,angular 22.x,active,2026-08-13
35,Testing,Use TestBed for component tests,TestBed sets up Angular DI for realistic component testing,TestBed.configureTestingModule for component tests,Instantiate components with new keyword,TestBed.configureTestingModule({ imports: [MyComponent] }),const comp = new MyComponent(),High,https://angular.dev/guide/testing/components-basics,angular 22.x,active,2026-08-13
36,Testing,Use Angular CDK component harnesses,Harnesses provide a stable testing API that survives template refactors,MatButtonHarness and custom HarnessLoader,Direct native element queries that break on template changes,const btn = await loader.getHarness(MatButtonHarness),fixture.debugElement.query(By.css('button')),Medium,https://material.angular.io/cdk/test-harnesses/overview,angular 22.x,active,2026-08-13
37,Testing,Use Spectator for less boilerplate,Spectator wraps TestBed with a cleaner API reducing test setup noise,Spectator for unit tests,Raw TestBed for every test,const spectator = createComponentFactory(MyComponent),TestBed.configureTestingModule({ declarations: [MyComponent] providers: [...] }),Low,https://github.com/ngneat/spectator,angular 22.x,active,2026-08-13
38,Testing,Mock services with jasmine.createSpyObj,Isolate unit tests by providing mock implementations of dependencies,SpyObj or jest.fn() mocks for services,Real HTTP calls in unit tests,const spy = jasmine.createSpyObj('UserService' ['getUser']); spy.getUser.and.returnValue(of(user)),providers: [UserService] // real service in unit test,High,https://angular.dev/guide/testing/services,angular 22.x,active,2026-08-13
39,Testing,Write integration tests for routes,Test full route navigation including guards and resolvers,RouterTestingHarness for route integration tests,Mock all routing behavior in unit tests,const harness = await RouterTestingHarness.create(); await harness.navigateByUrl('/home'),// manually calling route guard methods,Medium,https://angular.dev/api/router/testing/RouterTestingHarness,angular 22.x,active,2026-08-13
40,Testing,Test signal-based components,Signals update synchronously; no async flush needed in most cases,Read signal value directly in test assertions,TestBed.tick() or fakeAsync for signal reads,component.count.set(5); expect(component.double()).toBe(10),fakeAsync(() => { component.count.set(5); tick(); expect(component.double()).toBe(10) }),Medium,https://angular.dev/guide/testing,angular 22.x,active,2026-08-13
41,Styling,Use ViewEncapsulation.Emulated,Default emulation scopes styles to component preventing global leaks,Emulated or None for intentional global styles,ViewEncapsulation.None for component-specific styles,ViewEncapsulation.Emulated (default),ViewEncapsulation.None on feature components,Medium,https://angular.dev/guide/components/styling#style-scoping,angular 22.x,active,2026-08-13
42,Styling,Use :host selector,Style the component's host element using :host pseudo-class,:host for host element styles,Adding wrapper div just for styling,:host { display: block; padding: 1rem },"<div class=""wrapper"">...</div> + .wrapper { padding: 1rem }",Medium,https://angular.dev/guide/components/styling#host-element,angular 22.x,active,2026-08-13
43,Styling,Use CSS custom properties for theming,CSS variables work across component boundaries and enable dynamic theming,CSS custom properties for colors and spacing,Hardcoded hex values in component styles,:root { --primary: #6200ee } button { background: var(--primary) },button { background: #6200ee },Medium,https://angular.dev/guide/components/styling,angular 22.x,active,2026-08-13
44,Styling,Integrate Tailwind with Angular,Tailwind utilities work alongside Angular's ViewEncapsulation via global stylesheet,Add Tailwind in styles.css and use utility classes in templates,Custom CSS for layout that Tailwind already handles,"<div class=""flex items-center gap-4 p-6"">","<div class=""my-custom-flex""> /* .my-custom-flex { display: flex } */",Low,https://tailwindcss.com/docs/guides/angular,angular 22.x,active,2026-08-13
45,Styling,Use Angular Material theming tokens,Material 3 uses design tokens for systematic theming,M3 token-based theming for Angular Material,Overriding Angular Material CSS with deep selectors,@include mat.button-theme($my-theme),::ng-deep .mat-button { background: red },Medium,https://material.angular.io/guide/theming,angular 22.x,active,2026-08-13
46,Architecture,Use injection tokens for config,Provide configuration via InjectionToken for testability and flexibility,InjectionToken for environment-specific values,Importing environment.ts directly in services,const API_URL = new InjectionToken<string>('apiUrl'); provide: [{ provide: API_URL useValue: env.apiUrl }],constructor(private env: Environment) { this.url = env.apiUrl },Medium,https://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-object,angular 22.x,active,2026-08-13
47,Architecture,Use HTTP interceptors,Intercept requests for auth headers error handling and logging,Functional interceptors with withInterceptors(),Service-level header management in every request,withInterceptors([authInterceptor errorInterceptor]),httpClient.get(url { headers: { Authorization: token } }) in every call,High,https://angular.dev/guide/http/interceptors,angular 22.x,active,2026-08-13
48,Architecture,Organize by feature not type,Feature-based folder structure scales better than type-based,Feature folders with collocated component service and routes,Flat folders: all-components/ all-services/,src/features/checkout/checkout.component.ts checkout.service.ts checkout.routes.ts,src/components/checkout.component.ts src/services/checkout.service.ts,Medium,https://angular.dev/style-guide#folders-by-feature-structure,angular 22.x,active,2026-08-13
49,Architecture,Use environment configurations,Separate environment values for dev staging and prod via Angular build configs,angular.json fileReplacements for env configs,Hardcoded API URLs or feature flags in source,fileReplacements: [{ replace: environment.ts with: environment.prod.ts }],const API = 'https://api.example.com' // hardcoded in service,High,https://angular.dev/tools/cli/environments,angular 22.x,active,2026-08-13
50,Architecture,Prefer inject() over constructor DI,inject() function is composable and works in more contexts than constructor injection,inject() for dependency injection,Constructor parameters for new code,readonly http = inject(HttpClient); readonly router = inject(Router),constructor(private http: HttpClient private router: Router) {},Medium,https://angular.dev/api/core/inject,angular 22.x,active,2026-08-13
1 No Category Guideline Description Do Don't Code Good Code Bad Severity Docs URL Applies To Status Verified At
2 1 Components Use standalone components Angular 17+ default; no NgModule needed Standalone components are the default in current Angular Standalone components for all new code NgModule-based components for new projects @Component({ standalone: true imports: [CommonModule] }) @Component({ imports: [CommonModule] }) @NgModule({ declarations: [MyComp] }) High https://angular.dev/guide/components/importing angular 22.x active 2026-08-13
3 2 Components Use signals for state Signals are Angular's reactive primitive for fine-grained reactivity Signals for component state over class properties Mutable class properties without signals count = signal(0); increment() { this.count.update(v => v + 1) } count = 0; increment() { this.count++ } High https://angular.dev/guide/signals angular 22.x active 2026-08-13
4 3 Components Use @if/@for/@switch control flow Built-in control flow syntax replaces *ngIf/*ngFor directives @if and @for in templates *ngIf and *ngFor structural directives @if (isLoggedIn) { <Dashboard /> } @else { <Login /> } <div *ngIf="isLoggedIn"><Dashboard /></div> High https://angular.dev/guide/templates/control-flow angular 22.x active 2026-08-13
5 4 Components Use input() and output() signals Use input() and output() component APIs Signal-based inputs/outputs replace @Input()/@Output() decorators Use signal inputs and the output function for typed component contracts input() and output() for component API input() and output() for new component APIs @Input() and @Output() decorators Decorator-based inputs and outputs in new components name = input<string>(); clicked = output<void>() @Input() name: string; @Output() clicked = new EventEmitter() High https://angular.dev/guide/components/inputs https://angular.dev/guide/components/outputs angular 22.x active 2026-08-13
6 5 Components Use content projection ng-content for flexible component composition ng-content with select for named slots Rigid templates that can't be customized <ng-content select="[header]" /> <ng-content /> <div class="header">{{ title }}</div> Medium https://angular.dev/guide/components/content-projection angular 22.x active 2026-08-13
7 6 Components Keep components small Single responsibility; components should do one thing Extract sub-components when template exceeds 50 lines Monolithic components handling multiple concerns <UserAvatar /> <UserDetails /> <UserActions /> One 300-line component template Medium https://angular.dev/guide/components angular 22.x active 2026-08-13
8 7 Components Use OnPush change detection Use OnPush where subtree skipping fits Reduces re-renders by only checking on input changes or signal updates Reduces checks for compatible component subtrees while signals still notify consumers OnPush for all components OnPush for stable input-driven components Default change detection strategy Applying OnPush without understanding mutable input or event boundaries changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.Default items.push(nextItem) // same input reference High https://angular.dev/guide/components/lifecycle https://angular.dev/best-practices/skipping-subtrees angular 22.x active 2026-08-13
9 8 Components Avoid direct DOM manipulation Use renderer or ElementRef sparingly; prefer template bindings Template bindings and Angular directives Direct document.querySelector or innerHTML [class.active]="isActive" this.el.nativeElement.classList.add('active') High https://angular.dev/guide/components/host-elements angular 22.x active 2026-08-13
10 9 Routing Lazy load feature routes Load route chunks on demand to reduce initial bundle loadComponent() for all feature routes Eager-loaded routes in app config { path: 'admin' loadComponent: () => import('./admin/admin.component') } { path: 'admin' component: AdminComponent } High https://angular.dev/guide/routing/lazy-loading angular 22.x active 2026-08-13
11 10 Routing Use route guards with functional API Protect routes with canActivate/canMatch functional guards Functional guards returning boolean or UrlTree Class-based guards with CanActivate interface canActivate: [() => inject(AuthService).isLoggedIn()] canActivate: [AuthGuard] High https://angular.dev/guide/routing/common-router-tasks#preventing-unauthorized-access angular 22.x active 2026-08-13
12 11 Routing Use route resolvers for data Pre-fetch data before route activation using resolve ResolveFn for route data Fetching data in ngOnInit causing flash of empty state resolve: { user: () => inject(UserService).getUser() } Fetch in ngOnInit with loading state flickering Medium https://angular.dev/guide/routing/common-router-tasks#resolve angular 22.x active 2026-08-13
13 12 Routing Type route params with inject Use inject(ActivatedRoute) with signals or toSignal Typed route params via ActivatedRoute Untyped route.snapshot.params string access const id = toSignal(route.paramMap.pipe(map(p => p.get('id')))) const id = this.route.snapshot.params['id'] Medium https://angular.dev/api/router/ActivatedRoute angular 22.x active 2026-08-13
14 13 Routing Use nested routes for layouts Compose shared layouts using router-outlet nesting Nested routes with shared layout components Duplicating layout code across routes { path: 'app' component: ShellComponent children: [...] } Duplicate header/sidebar in each route component Medium https://angular.dev/guide/routing/router-tutorial-toh#child-route-configuration angular 22.x active 2026-08-13
15 14 Routing Configure preloading strategies Preload lazy modules in background after initial load PreloadAllModules or custom strategy No preloading causing delayed navigation provideRouter(routes withPreloading(PreloadAllModules)) provideRouter(routes) Low https://angular.dev/api/router/PreloadAllModules angular 22.x active 2026-08-13
16 15 State Use signals for local state Signals provide synchronous reactive state without RxJS overhead signal() for component-local reactive state BehaviorSubject for simple local state const items = signal<Item[]>([]); addItem(i: Item) { this.items.update(arr => [...arr i]) } items$ = new BehaviorSubject<Item[]>([]) High https://angular.dev/guide/signals angular 22.x active 2026-08-13
17 16 State Use computed() for derived state Lazily evaluated derived values that update when dependencies change computed() for values derived from other signals Duplicated state or manual sync readonly total = computed(() => this.items().reduce((s i) => s + i.price 0)) this.total = this.items.reduce(...) // called manually High https://angular.dev/guide/signals#computed-signals angular 22.x active 2026-08-13
18 17 State Use effect() carefully Effects run side effects when signals change; avoid overuse effect() for side effects like logging or localStorage sync effect() for deriving state (use computed instead) effect(() => localStorage.setItem('cart' JSON.stringify(this.cart()))) effect(() => { this.total.set(this.items().length) }) Medium https://angular.dev/guide/signals#effects angular 22.x active 2026-08-13
19 18 State Use NgRx Signal Store for complex state NgRx Signal Store is the modern lightweight state management for Angular @ngrx/signals SignalStore for feature state Full NgRx reducer/action/effect boilerplate for simple state const Store = signalStore(withState({ count: 0 }) withMethods(s => ({ increment: () => patchState(s { count: s.count() + 1 }) }))) createReducer(on(increment state => ({ ...state count: state.count + 1 }))) Medium https://ngrx.io/guide/signals angular 22.x active 2026-08-13
20 19 State Inject services for shared state Services with signals share state across components without a store Injectable service with signals for cross-component state Prop drilling or @Input chains for shared state @Injectable({ providedIn: 'root' }) class CartService { items = signal<Item[]>([]) } @Input() cartItems passed through 4 component levels Medium https://angular.dev/guide/di/creating-injectable-service angular 22.x active 2026-08-13
21 20 State Avoid mixing RxJS and signals unnecessarily Use toSignal() to bridge RxJS into signal world at the boundary toSignal() to convert observable to signal at component edge Subscribing in components and storing in signal manually readonly user = toSignal(this.userService.user$) this.userService.user$.subscribe(u => this.user.set(u)) Medium https://angular.dev/guide/rxjs-interop angular 22.x active 2026-08-13
22 21 Forms Use typed reactive forms FormGroup/FormControl with explicit generics for compile-time safety FormBuilder with typed controls Untyped FormControl or any casts fb.group<LoginForm>({ email: fb.control('') password: fb.control('') }) new FormGroup({ email: new FormControl(null) }) High https://angular.dev/guide/forms/typed-forms angular 22.x active 2026-08-13
23 22 Forms Use reactive forms over template-driven Reactive forms scale better and are fully testable ReactiveFormsModule for all non-trivial forms FormsModule with ngModel for complex forms <input [formControl]="emailControl" /> <input [(ngModel)]="email" /> Medium https://angular.dev/guide/forms/reactive-forms angular 22.x active 2026-08-13
24 23 Forms Write custom validators as functions Functional validators are composable and tree-shakeable ValidatorFn functions for custom validation Class-based validators implementing Validator interface const noSpaces: ValidatorFn = ctrl => ctrl.value?.includes(' ') ? { noSpaces: true } : null class NoSpacesValidator implements Validator { validate(c) {} } Medium https://angular.dev/guide/forms/form-validation#custom-validators angular 22.x active 2026-08-13
25 24 Forms Use updateOn for performance Control when validation runs to avoid per-keystroke validation overhead updateOn: 'blur' or 'submit' for expensive validators Default updateOn: 'change' for async validators fb.control('' { updateOn: 'blur' validators: [Validators.email] }) fb.control('' [Validators.email]) // validates on every key Low https://angular.dev/api/forms/AbstractControl#updateOn angular 22.x active 2026-08-13
26 25 Forms Use FormArray for dynamic fields FormArray manages variable-length lists of controls FormArray for add/remove field scenarios Manually tracking index-based controls get items(): FormArray { return this.form.get('items') as FormArray } items: [FormControl] managed outside form Medium https://angular.dev/guide/forms/reactive-forms#using-the-formarray-class angular 22.x active 2026-08-13
27 26 Forms Display validation errors clearly Use form control touched and dirty states to show errors at the right time Show errors after field is touched Show all errors on page load @if (email.invalid && email.touched) { <span>Invalid email</span> } @if (email.invalid) { <span>Invalid email</span> } Medium https://angular.dev/guide/forms/form-validation angular 22.x active 2026-08-13
28 27 Performance Apply OnPush to all components Prepare components for zoneless change detection OnPush + signals eliminates most unnecessary change detection cycles Use signals markForCheck AsyncPipe and bound listeners so Angular receives change notifications OnPush change detection everywhere Notification-driven state updates Default strategy which checks entire tree on every event Depending on ZoneJS to notice arbitrary state mutation changeDetection: ChangeDetectionStrategy.OnPush count.set(count() + 1) changeDetection: ChangeDetectionStrategy.Default this.count++ // outside a notifying Angular boundary High https://angular.dev/best-practices/skipping-component-subtrees https://angular.dev/guide/zoneless angular 22.x active 2026-08-13
29 28 Performance Use trackBy in @for blocks Stable identity for list items prevents full DOM re-creation on change track item.id in @for track $index for dynamic data @for (item of items; track item.id) { <li>{{ item.name }}</li> } @for (item of items; track $index) { <li>{{ item.name }}</li> } High https://angular.dev/guide/templates/control-flow#track-and-identity angular 22.x active 2026-08-13
30 29 Performance Use @defer for below-the-fold content Defer blocks lazy-load components when they enter the viewport @defer with on viewport for non-critical UI Eagerly loading all components at startup @defer (on viewport) { <HeavyChart /> } @placeholder { <Skeleton /> } <HeavyChart /> loaded at startup High https://angular.dev/guide/defer angular 22.x active 2026-08-13
31 30 Performance Use NgOptimizedImage Enforces image best practices: lazy loading LCP hints and proper sizing NgOptimizedImage for all img tags Plain img tags for CMS or user content <img ngSrc="/hero.jpg" width="800" height="400" priority /> <img src="/hero.jpg" /> High https://angular.dev/guide/image-optimization angular 22.x active 2026-08-13
32 31 Performance Tree-shake unused Angular features Import only what you use from Angular packages Import specific Angular modules needed Import BrowserAnimationsModule when not using animations import { NgOptimizedImage } from '@angular/common' import { CommonModule } from '@angular/common' // entire module Medium https://angular.dev/tools/cli/build angular 22.x active 2026-08-13
33 32 Performance Avoid subscribe in components Subscriptions leak and cause bugs; prefer async pipe or toSignal toSignal() or async pipe instead of manual subscribe Manual subscribe without unsubscribe in ngOnDestroy readonly data = toSignal(this.service.data$) this.service.data$.subscribe(d => this.data = d) High https://angular.dev/guide/rxjs-interop angular 22.x active 2026-08-13
34 33 Performance Use SSR with Angular Universal Use @angular/ssr for hybrid rendering Pre-render pages for faster LCP and better SEO Server rendering and prerendering improve LCP and SEO for public routes SSR or SSG for public-facing routes Configure SSR or prerender per route Pure CSR for SEO-critical pages ng add @angular/ssr // no SSR, client renders empty shell Medium https://angular.dev/guide/ssr angular 22.x active 2026-08-13
35 34 Performance Minimize bundle with standalone APIs Standalone components + provideRouter() eliminate dead NgModule code provideRouter() and provideHttpClient() in app.config Root AppModule with all imports provideRouter(routes) in app.config.ts @NgModule({ imports: [RouterModule.forRoot(routes)] }) Medium https://angular.dev/guide/routing/standalone angular 22.x active 2026-08-13
36 35 Testing Use TestBed for component tests TestBed sets up Angular DI for realistic component testing TestBed.configureTestingModule for component tests Instantiate components with new keyword TestBed.configureTestingModule({ imports: [MyComponent] }) const comp = new MyComponent() High https://angular.dev/guide/testing/components-basics angular 22.x active 2026-08-13
37 36 Testing Use Angular CDK component harnesses Harnesses provide a stable testing API that survives template refactors MatButtonHarness and custom HarnessLoader Direct native element queries that break on template changes const btn = await loader.getHarness(MatButtonHarness) fixture.debugElement.query(By.css('button')) Medium https://material.angular.io/cdk/test-harnesses/overview angular 22.x active 2026-08-13
38 37 Testing Use Spectator for less boilerplate Spectator wraps TestBed with a cleaner API reducing test setup noise Spectator for unit tests Raw TestBed for every test const spectator = createComponentFactory(MyComponent) TestBed.configureTestingModule({ declarations: [MyComponent] providers: [...] }) Low https://github.com/ngneat/spectator angular 22.x active 2026-08-13
39 38 Testing Mock services with jasmine.createSpyObj Isolate unit tests by providing mock implementations of dependencies SpyObj or jest.fn() mocks for services Real HTTP calls in unit tests const spy = jasmine.createSpyObj('UserService' ['getUser']); spy.getUser.and.returnValue(of(user)) providers: [UserService] // real service in unit test High https://angular.dev/guide/testing/services angular 22.x active 2026-08-13
40 39 Testing Write integration tests for routes Test full route navigation including guards and resolvers RouterTestingHarness for route integration tests Mock all routing behavior in unit tests const harness = await RouterTestingHarness.create(); await harness.navigateByUrl('/home') // manually calling route guard methods Medium https://angular.dev/api/router/testing/RouterTestingHarness angular 22.x active 2026-08-13
41 40 Testing Test signal-based components Signals update synchronously; no async flush needed in most cases Read signal value directly in test assertions TestBed.tick() or fakeAsync for signal reads component.count.set(5); expect(component.double()).toBe(10) fakeAsync(() => { component.count.set(5); tick(); expect(component.double()).toBe(10) }) Medium https://angular.dev/guide/testing angular 22.x active 2026-08-13
42 41 Styling Use ViewEncapsulation.Emulated Default emulation scopes styles to component preventing global leaks Emulated or None for intentional global styles ViewEncapsulation.None for component-specific styles ViewEncapsulation.Emulated (default) ViewEncapsulation.None on feature components Medium https://angular.dev/guide/components/styling#style-scoping angular 22.x active 2026-08-13
43 42 Styling Use :host selector Style the component's host element using :host pseudo-class :host for host element styles Adding wrapper div just for styling :host { display: block; padding: 1rem } <div class="wrapper">...</div> + .wrapper { padding: 1rem } Medium https://angular.dev/guide/components/styling#host-element angular 22.x active 2026-08-13
44 43 Styling Use CSS custom properties for theming CSS variables work across component boundaries and enable dynamic theming CSS custom properties for colors and spacing Hardcoded hex values in component styles :root { --primary: #6200ee } button { background: var(--primary) } button { background: #6200ee } Medium https://angular.dev/guide/components/styling angular 22.x active 2026-08-13
45 44 Styling Integrate Tailwind with Angular Tailwind utilities work alongside Angular's ViewEncapsulation via global stylesheet Add Tailwind in styles.css and use utility classes in templates Custom CSS for layout that Tailwind already handles <div class="flex items-center gap-4 p-6"> <div class="my-custom-flex"> /* .my-custom-flex { display: flex } */ Low https://tailwindcss.com/docs/guides/angular angular 22.x active 2026-08-13
46 45 Styling Use Angular Material theming tokens Material 3 uses design tokens for systematic theming M3 token-based theming for Angular Material Overriding Angular Material CSS with deep selectors @include mat.button-theme($my-theme) ::ng-deep .mat-button { background: red } Medium https://material.angular.io/guide/theming angular 22.x active 2026-08-13
47 46 Architecture Use injection tokens for config Provide configuration via InjectionToken for testability and flexibility InjectionToken for environment-specific values Importing environment.ts directly in services const API_URL = new InjectionToken<string>('apiUrl'); provide: [{ provide: API_URL useValue: env.apiUrl }] constructor(private env: Environment) { this.url = env.apiUrl } Medium https://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-object angular 22.x active 2026-08-13
48 47 Architecture Use HTTP interceptors Intercept requests for auth headers error handling and logging Functional interceptors with withInterceptors() Service-level header management in every request withInterceptors([authInterceptor errorInterceptor]) httpClient.get(url { headers: { Authorization: token } }) in every call High https://angular.dev/guide/http/interceptors angular 22.x active 2026-08-13
49 48 Architecture Organize by feature not type Feature-based folder structure scales better than type-based Feature folders with collocated component service and routes Flat folders: all-components/ all-services/ src/features/checkout/checkout.component.ts checkout.service.ts checkout.routes.ts src/components/checkout.component.ts src/services/checkout.service.ts Medium https://angular.dev/style-guide#folders-by-feature-structure angular 22.x active 2026-08-13
50 49 Architecture Use environment configurations Separate environment values for dev staging and prod via Angular build configs angular.json fileReplacements for env configs Hardcoded API URLs or feature flags in source fileReplacements: [{ replace: environment.ts with: environment.prod.ts }] const API = 'https://api.example.com' // hardcoded in service High https://angular.dev/tools/cli/environments angular 22.x active 2026-08-13
51 50 Architecture Prefer inject() over constructor DI inject() function is composable and works in more contexts than constructor injection inject() for dependency injection Constructor parameters for new code readonly http = inject(HttpClient); readonly router = inject(Router) constructor(private http: HttpClient private router: Router) {} Medium https://angular.dev/api/core/inject angular 22.x active 2026-08-13