Viet Tran a38d04c3d5
feat(search): overhaul relevance and curated design data
Overhaul BM25 relevance, reasoning and data-quality contracts; refresh UI styles and framework guidance; add resilient text, chip, badge and micro-interaction guidance; strengthen release, provenance and catalog refresh gates; update bilingual documentation.
2026-08-14 00:08:23 +07:00

19 KiB

1NoCategoryGuidelineDescriptionDoDon'tCode GoodCode BadSeverityDocs URLApplies ToStatusVerified At
21ComponentsUse standalone componentsStandalone components are the default in current AngularStandalone components for all new codeNgModule-based components for new projects@Component({ imports: [CommonModule] })@NgModule({ declarations: [MyComp] })Highhttps://angular.dev/guide/components/importingangular 22.xactive2026-08-13
32ComponentsUse signals for stateSignals are Angular's reactive primitive for fine-grained reactivitySignals for component state over class propertiesMutable class properties without signalscount = signal(0); increment() { this.count.update(v => v + 1) }count = 0; increment() { this.count++ }Highhttps://angular.dev/guide/signalsangular 22.xactive2026-08-13
43ComponentsUse @if/@for/@switch control flowBuilt-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>Highhttps://angular.dev/guide/templates/control-flowangular 22.xactive2026-08-13
54ComponentsUse input() and output() component APIsUse signal inputs and the output function for typed component contractsinput() and output() for new component APIsDecorator-based inputs and outputs in new componentsname = input<string>(); clicked = output<void>()@Input() name: string; @Output() clicked = new EventEmitter()Highhttps://angular.dev/guide/components/outputsangular 22.xactive2026-08-13
65ComponentsUse content projectionng-content for flexible component compositionng-content with select for named slotsRigid templates that can't be customized<ng-content select="[header]" /> <ng-content /><div class="header">{{ title }}</div>Mediumhttps://angular.dev/guide/components/content-projectionangular 22.xactive2026-08-13
76ComponentsKeep components smallSingle responsibility; components should do one thingExtract sub-components when template exceeds 50 linesMonolithic components handling multiple concerns<UserAvatar /> <UserDetails /> <UserActions />One 300-line component templateMediumhttps://angular.dev/guide/componentsangular 22.xactive2026-08-13
87ComponentsUse OnPush where subtree skipping fitsReduces checks for compatible component subtrees while signals still notify consumersOnPush for stable input-driven componentsApplying OnPush without understanding mutable input or event boundarieschangeDetection: ChangeDetectionStrategy.OnPushitems.push(nextItem) // same input referenceHighhttps://angular.dev/best-practices/skipping-subtreesangular 22.xactive2026-08-13
98ComponentsAvoid direct DOM manipulationUse renderer or ElementRef sparingly; prefer template bindingsTemplate bindings and Angular directivesDirect document.querySelector or innerHTML[class.active]="isActive"this.el.nativeElement.classList.add('active')Highhttps://angular.dev/guide/components/host-elementsangular 22.xactive2026-08-13
109RoutingLazy load feature routesLoad route chunks on demand to reduce initial bundleloadComponent() for all feature routesEager-loaded routes in app config{ path: 'admin' loadComponent: () => import('./admin/admin.component') }{ path: 'admin' component: AdminComponent }Highhttps://angular.dev/guide/routing/lazy-loadingangular 22.xactive2026-08-13
1110RoutingUse route guards with functional APIProtect routes with canActivate/canMatch functional guardsFunctional guards returning boolean or UrlTreeClass-based guards with CanActivate interfacecanActivate: [() => inject(AuthService).isLoggedIn()]canActivate: [AuthGuard]Highhttps://angular.dev/guide/routing/common-router-tasks#preventing-unauthorized-accessangular 22.xactive2026-08-13
1211RoutingUse route resolvers for dataPre-fetch data before route activation using resolveResolveFn for route dataFetching data in ngOnInit causing flash of empty stateresolve: { user: () => inject(UserService).getUser() }Fetch in ngOnInit with loading state flickeringMediumhttps://angular.dev/guide/routing/common-router-tasks#resolveangular 22.xactive2026-08-13
1312RoutingType route params with injectUse inject(ActivatedRoute) with signals or toSignalTyped route params via ActivatedRouteUntyped route.snapshot.params string accessconst id = toSignal(route.paramMap.pipe(map(p => p.get('id'))))const id = this.route.snapshot.params['id']Mediumhttps://angular.dev/api/router/ActivatedRouteangular 22.xactive2026-08-13
1413RoutingUse nested routes for layoutsCompose shared layouts using router-outlet nestingNested routes with shared layout componentsDuplicating layout code across routes{ path: 'app' component: ShellComponent children: [...] }Duplicate header/sidebar in each route componentMediumhttps://angular.dev/guide/routing/router-tutorial-toh#child-route-configurationangular 22.xactive2026-08-13
1514RoutingConfigure preloading strategiesPreload lazy modules in background after initial loadPreloadAllModules or custom strategyNo preloading causing delayed navigationprovideRouter(routes withPreloading(PreloadAllModules))provideRouter(routes)Lowhttps://angular.dev/api/router/PreloadAllModulesangular 22.xactive2026-08-13
1615StateUse signals for local stateSignals provide synchronous reactive state without RxJS overheadsignal() for component-local reactive stateBehaviorSubject for simple local stateconst items = signal<Item[]>([]); addItem(i: Item) { this.items.update(arr => [...arr i]) }items$ = new BehaviorSubject<Item[]>([])Highhttps://angular.dev/guide/signalsangular 22.xactive2026-08-13
1716StateUse computed() for derived stateLazily evaluated derived values that update when dependencies changecomputed() for values derived from other signalsDuplicated state or manual syncreadonly total = computed(() => this.items().reduce((s i) => s + i.price 0))this.total = this.items.reduce(...) // called manuallyHighhttps://angular.dev/guide/signals#computed-signalsangular 22.xactive2026-08-13
1817StateUse effect() carefullyEffects run side effects when signals change; avoid overuseeffect() for side effects like logging or localStorage synceffect() for deriving state (use computed instead)effect(() => localStorage.setItem('cart' JSON.stringify(this.cart())))effect(() => { this.total.set(this.items().length) })Mediumhttps://angular.dev/guide/signals#effectsangular 22.xactive2026-08-13
1918StateUse NgRx Signal Store for complex stateNgRx Signal Store is the modern lightweight state management for Angular@ngrx/signals SignalStore for feature stateFull NgRx reducer/action/effect boilerplate for simple stateconst Store = signalStore(withState({ count: 0 }) withMethods(s => ({ increment: () => patchState(s { count: s.count() + 1 }) })))createReducer(on(increment state => ({ ...state count: state.count + 1 })))Mediumhttps://ngrx.io/guide/signalsangular 22.xactive2026-08-13
2019StateInject services for shared stateServices with signals share state across components without a storeInjectable service with signals for cross-component stateProp drilling or @Input chains for shared state@Injectable({ providedIn: 'root' }) class CartService { items = signal<Item[]>([]) }@Input() cartItems passed through 4 component levelsMediumhttps://angular.dev/guide/di/creating-injectable-serviceangular 22.xactive2026-08-13
2120StateAvoid mixing RxJS and signals unnecessarilyUse toSignal() to bridge RxJS into signal world at the boundarytoSignal() to convert observable to signal at component edgeSubscribing in components and storing in signal manuallyreadonly user = toSignal(this.userService.user$)this.userService.user$.subscribe(u => this.user.set(u))Mediumhttps://angular.dev/guide/rxjs-interopangular 22.xactive2026-08-13
2221FormsUse typed reactive formsFormGroup/FormControl with explicit generics for compile-time safetyFormBuilder with typed controlsUntyped FormControl or any castsfb.group<LoginForm>({ email: fb.control('') password: fb.control('') })new FormGroup({ email: new FormControl(null) })Highhttps://angular.dev/guide/forms/typed-formsangular 22.xactive2026-08-13
2322FormsUse reactive forms over template-drivenReactive forms scale better and are fully testableReactiveFormsModule for all non-trivial formsFormsModule with ngModel for complex forms<input [formControl]="emailControl" /><input [(ngModel)]="email" />Mediumhttps://angular.dev/guide/forms/reactive-formsangular 22.xactive2026-08-13
2423FormsWrite custom validators as functionsFunctional validators are composable and tree-shakeableValidatorFn functions for custom validationClass-based validators implementing Validator interfaceconst noSpaces: ValidatorFn = ctrl => ctrl.value?.includes(' ') ? { noSpaces: true } : nullclass NoSpacesValidator implements Validator { validate(c) {} }Mediumhttps://angular.dev/guide/forms/form-validation#custom-validatorsangular 22.xactive2026-08-13
2524FormsUse updateOn for performanceControl when validation runs to avoid per-keystroke validation overheadupdateOn: 'blur' or 'submit' for expensive validatorsDefault updateOn: 'change' for async validatorsfb.control('' { updateOn: 'blur' validators: [Validators.email] })fb.control('' [Validators.email]) // validates on every keyLowhttps://angular.dev/api/forms/AbstractControl#updateOnangular 22.xactive2026-08-13
2625FormsUse FormArray for dynamic fieldsFormArray manages variable-length lists of controlsFormArray for add/remove field scenariosManually tracking index-based controlsget items(): FormArray { return this.form.get('items') as FormArray }items: [FormControl] managed outside formMediumhttps://angular.dev/guide/forms/reactive-forms#using-the-formarray-classangular 22.xactive2026-08-13
2726FormsDisplay validation errors clearlyUse form control touched and dirty states to show errors at the right timeShow errors after field is touchedShow all errors on page load@if (email.invalid && email.touched) { <span>Invalid email</span> }@if (email.invalid) { <span>Invalid email</span> }Mediumhttps://angular.dev/guide/forms/form-validationangular 22.xactive2026-08-13
2827PerformancePrepare components for zoneless change detectionUse signals markForCheck AsyncPipe and bound listeners so Angular receives change notificationsNotification-driven state updatesDepending on ZoneJS to notice arbitrary state mutationcount.set(count() + 1)this.count++ // outside a notifying Angular boundaryHighhttps://angular.dev/guide/zonelessangular 22.xactive2026-08-13
2928PerformanceUse trackBy in @for blocksStable identity for list items prevents full DOM re-creation on changetrack item.id in @fortrack $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> }Highhttps://angular.dev/guide/templates/control-flow#track-and-identityangular 22.xactive2026-08-13
3029PerformanceUse @defer for below-the-fold contentDefer blocks lazy-load components when they enter the viewport@defer with on viewport for non-critical UIEagerly loading all components at startup@defer (on viewport) { <HeavyChart /> } @placeholder { <Skeleton /> }<HeavyChart /> loaded at startupHighhttps://angular.dev/guide/deferangular 22.xactive2026-08-13
3130PerformanceUse NgOptimizedImageEnforces image best practices: lazy loading LCP hints and proper sizingNgOptimizedImage for all img tagsPlain img tags for CMS or user content<img ngSrc="/hero.jpg" width="800" height="400" priority /><img src="/hero.jpg" />Highhttps://angular.dev/guide/image-optimizationangular 22.xactive2026-08-13
3231PerformanceTree-shake unused Angular featuresImport only what you use from Angular packagesImport specific Angular modules neededImport BrowserAnimationsModule when not using animationsimport { NgOptimizedImage } from '@angular/common'import { CommonModule } from '@angular/common' // entire moduleMediumhttps://angular.dev/tools/cli/buildangular 22.xactive2026-08-13
3332PerformanceAvoid subscribe in componentsSubscriptions leak and cause bugs; prefer async pipe or toSignaltoSignal() or async pipe instead of manual subscribeManual subscribe without unsubscribe in ngOnDestroyreadonly data = toSignal(this.service.data$)this.service.data$.subscribe(d => this.data = d)Highhttps://angular.dev/guide/rxjs-interopangular 22.xactive2026-08-13
3433PerformanceUse @angular/ssr for hybrid renderingServer rendering and prerendering improve LCP and SEO for public routesConfigure SSR or prerender per routePure CSR for SEO-critical pagesng add @angular/ssr// no SSR, client renders empty shellMediumhttps://angular.dev/guide/ssrangular 22.xactive2026-08-13
3534PerformanceMinimize bundle with standalone APIsStandalone components + provideRouter() eliminate dead NgModule codeprovideRouter() and provideHttpClient() in app.configRoot AppModule with all importsprovideRouter(routes) in app.config.ts@NgModule({ imports: [RouterModule.forRoot(routes)] })Mediumhttps://angular.dev/guide/routing/standaloneangular 22.xactive2026-08-13
3635TestingUse TestBed for component testsTestBed sets up Angular DI for realistic component testingTestBed.configureTestingModule for component testsInstantiate components with new keywordTestBed.configureTestingModule({ imports: [MyComponent] })const comp = new MyComponent()Highhttps://angular.dev/guide/testing/components-basicsangular 22.xactive2026-08-13
3736TestingUse Angular CDK component harnessesHarnesses provide a stable testing API that survives template refactorsMatButtonHarness and custom HarnessLoaderDirect native element queries that break on template changesconst btn = await loader.getHarness(MatButtonHarness)fixture.debugElement.query(By.css('button'))Mediumhttps://material.angular.io/cdk/test-harnesses/overviewangular 22.xactive2026-08-13
3837TestingUse Spectator for less boilerplateSpectator wraps TestBed with a cleaner API reducing test setup noiseSpectator for unit testsRaw TestBed for every testconst spectator = createComponentFactory(MyComponent)TestBed.configureTestingModule({ declarations: [MyComponent] providers: [...] })Lowhttps://github.com/ngneat/spectatorangular 22.xactive2026-08-13
3938TestingMock services with jasmine.createSpyObjIsolate unit tests by providing mock implementations of dependenciesSpyObj or jest.fn() mocks for servicesReal HTTP calls in unit testsconst spy = jasmine.createSpyObj('UserService' ['getUser']); spy.getUser.and.returnValue(of(user))providers: [UserService] // real service in unit testHighhttps://angular.dev/guide/testing/servicesangular 22.xactive2026-08-13
4039TestingWrite integration tests for routesTest full route navigation including guards and resolversRouterTestingHarness for route integration testsMock all routing behavior in unit testsconst harness = await RouterTestingHarness.create(); await harness.navigateByUrl('/home')// manually calling route guard methodsMediumhttps://angular.dev/api/router/testing/RouterTestingHarnessangular 22.xactive2026-08-13
4140TestingTest signal-based componentsSignals update synchronously; no async flush needed in most casesRead signal value directly in test assertionsTestBed.tick() or fakeAsync for signal readscomponent.count.set(5); expect(component.double()).toBe(10)fakeAsync(() => { component.count.set(5); tick(); expect(component.double()).toBe(10) })Mediumhttps://angular.dev/guide/testingangular 22.xactive2026-08-13
4241StylingUse ViewEncapsulation.EmulatedDefault emulation scopes styles to component preventing global leaksEmulated or None for intentional global stylesViewEncapsulation.None for component-specific stylesViewEncapsulation.Emulated (default)ViewEncapsulation.None on feature componentsMediumhttps://angular.dev/guide/components/styling#style-scopingangular 22.xactive2026-08-13
4342StylingUse :host selectorStyle the component's host element using :host pseudo-class:host for host element stylesAdding wrapper div just for styling:host { display: block; padding: 1rem }<div class="wrapper">...</div> + .wrapper { padding: 1rem }Mediumhttps://angular.dev/guide/components/styling#host-elementangular 22.xactive2026-08-13
4443StylingUse CSS custom properties for themingCSS variables work across component boundaries and enable dynamic themingCSS custom properties for colors and spacingHardcoded hex values in component styles:root { --primary: #6200ee } button { background: var(--primary) }button { background: #6200ee }Mediumhttps://angular.dev/guide/components/stylingangular 22.xactive2026-08-13
4544StylingIntegrate Tailwind with AngularTailwind utilities work alongside Angular's ViewEncapsulation via global stylesheetAdd Tailwind in styles.css and use utility classes in templatesCustom 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 } */Lowhttps://tailwindcss.com/docs/guides/angularangular 22.xactive2026-08-13
4645StylingUse Angular Material theming tokensMaterial 3 uses design tokens for systematic themingM3 token-based theming for Angular MaterialOverriding Angular Material CSS with deep selectors@include mat.button-theme($my-theme)::ng-deep .mat-button { background: red }Mediumhttps://material.angular.io/guide/themingangular 22.xactive2026-08-13
4746ArchitectureUse injection tokens for configProvide configuration via InjectionToken for testability and flexibilityInjectionToken for environment-specific valuesImporting environment.ts directly in servicesconst API_URL = new InjectionToken<string>('apiUrl'); provide: [{ provide: API_URL useValue: env.apiUrl }]constructor(private env: Environment) { this.url = env.apiUrl }Mediumhttps://angular.dev/guide/di/dependency-injection-providers#using-an-injectiontoken-objectangular 22.xactive2026-08-13
4847ArchitectureUse HTTP interceptorsIntercept requests for auth headers error handling and loggingFunctional interceptors with withInterceptors()Service-level header management in every requestwithInterceptors([authInterceptor errorInterceptor])httpClient.get(url { headers: { Authorization: token } }) in every callHighhttps://angular.dev/guide/http/interceptorsangular 22.xactive2026-08-13
4948ArchitectureOrganize by feature not typeFeature-based folder structure scales better than type-basedFeature folders with collocated component service and routesFlat folders: all-components/ all-services/src/features/checkout/checkout.component.ts checkout.service.ts checkout.routes.tssrc/components/checkout.component.ts src/services/checkout.service.tsMediumhttps://angular.dev/style-guide#folders-by-feature-structureangular 22.xactive2026-08-13
5049ArchitectureUse environment configurationsSeparate environment values for dev staging and prod via Angular build configsangular.json fileReplacements for env configsHardcoded API URLs or feature flags in sourcefileReplacements: [{ replace: environment.ts with: environment.prod.ts }]const API = 'https://api.example.com' // hardcoded in serviceHighhttps://angular.dev/tools/cli/environmentsangular 22.xactive2026-08-13
5150ArchitecturePrefer inject() over constructor DIinject() function is composable and works in more contexts than constructor injectioninject() for dependency injectionConstructor parameters for new codereadonly http = inject(HttpClient); readonly router = inject(Router)constructor(private http: HttpClient private router: Router) {}Mediumhttps://angular.dev/api/core/injectangular 22.xactive2026-08-13