angular

安装量: 256
排名: #8233

安装

npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill angular

Angular Expert Master modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns. When to Use This Skill Building new Angular applications (v20+) Implementing Signals-based reactive patterns Creating Standalone Components and migrating from NgModules Configuring Zoneless Angular applications Implementing SSR, prerendering, and hydration Optimizing Angular performance Adopting modern Angular patterns and best practices Do Not Use This Skill When Migrating from AngularJS (1.x) → use angular-migration skill Working with legacy Angular apps that cannot upgrade General TypeScript issues → use typescript-expert skill Instructions Assess the Angular version and project structure Apply modern patterns (Signals, Standalone, Zoneless) Implement with proper typing and reactivity Validate with build and tests Safety Always test changes in development before production Gradual migration for existing apps (don't big-bang refactor) Keep backward compatibility during transitions Angular Version Timeline Version Release Key Features Angular 20 Q2 2025 Signals stable, Zoneless stable, Incremental hydration Angular 21 Q4 2025 Signals-first default, Enhanced SSR Angular 22 Q2 2026 Signal Forms, Selectorless components 1. Signals: The New Reactive Primitive Signals are Angular's fine-grained reactivity system, replacing zone.js-based change detection. Core Concepts import { signal , computed , effect } from "@angular/core" ; // Writable signal const count = signal ( 0 ) ; // Read value console . log ( count ( ) ) ; // 0 // Update value count . set ( 5 ) ; // Direct set count . update ( ( v ) => v + 1 ) ; // Functional update // Computed (derived) signal const doubled = computed ( ( ) => count ( ) * 2 ) ; // Effect (side effects) effect ( ( ) => { console . log ( Count changed to: ${ count ( ) } ) ; } ) ; Signal-Based Inputs and Outputs import { Component , input , output , model } from "@angular/core" ; @ Component ( { selector : "app-user-card" , standalone : true , template : `

{{ name() }}

{{ role() }}

` , } ) export class UserCardComponent { // Signal inputs (read-only) id = input . required < string

( ) ; name = input . required < string

( ) ; role = input < string

( "User" ) ; // With default // Output select = output < string

( ) ; // Two-way binding (model) isSelected = model ( false ) ; } // Usage: // Signal Queries (ViewChild/ContentChild) import { Component , viewChild , viewChildren , contentChild , } from "@angular/core" ; @ Component ( { selector : "app-container" , standalone : true , template : <input #searchInput /> <app-item *ngFor="let item of items()" /> , } ) export class ContainerComponent { // Signal-based queries searchInput = viewChild < ElementRef

( "searchInput" ) ; items = viewChildren ( ItemComponent ) ; projectedContent = contentChild ( HeaderDirective ) ; focusSearch ( ) { this . searchInput ( ) ?. nativeElement . focus ( ) ; } } When to Use Signals vs RxJS Use Case Signals RxJS Local component state ✅ Preferred Overkill Derived/computed values ✅ computed() combineLatest works Side effects ✅ effect() tap operator HTTP requests ❌ ✅ HttpClient returns Observable Event streams ❌ ✅ fromEvent , operators Complex async flows ❌ ✅ switchMap , mergeMap 2. Standalone Components Standalone components are self-contained and don't require NgModule declarations. Creating Standalone Components import { Component } from "@angular/core" ; import { CommonModule } from "@angular/common" ; import { RouterLink } from "@angular/router" ; @ Component ( { selector : "app-header" , standalone : true , imports : [ CommonModule , RouterLink ] , // Direct imports template : `

Home About

, } ) export class HeaderComponent { } Bootstrapping Without NgModule // main.ts import { bootstrapApplication } from "@angular/platform-browser" ; import { provideRouter } from "@angular/router" ; import { provideHttpClient } from "@angular/common/http" ; import { AppComponent } from "./app/app.component" ; import { routes } from "./app/app.routes" ; bootstrapApplication ( AppComponent , { providers : [ provideRouter ( routes ) , provideHttpClient ( ) ] , } ) ; Lazy Loading Standalone Components // app.routes.ts import { Routes } from "@angular/router" ; export const routes : Routes = [ { path : "dashboard" , loadComponent : ( ) => import ( "./dashboard/dashboard.component" ) . then ( ( m ) => m . DashboardComponent , ) , } , { path : "admin" , loadChildren : ( ) => import ( "./admin/admin.routes" ) . then ( ( m ) => m . ADMIN_ROUTES ) , } , ] ; 3. Zoneless Angular Zoneless applications don't use zone.js, improving performance and debugging. Enabling Zoneless Mode // main.ts import { bootstrapApplication } from "@angular/platform-browser" ; import { provideZonelessChangeDetection } from "@angular/core" ; import { AppComponent } from "./app/app.component" ; bootstrapApplication ( AppComponent , { providers : [ provideZonelessChangeDetection ( ) ] , } ) ; Zoneless Component Patterns import { Component , signal , ChangeDetectionStrategy } from "@angular/core" ; @ Component ( { selector : "app-counter" , standalone : true , changeDetection : ChangeDetectionStrategy . OnPush , template :

Count: {{ count() }}
`
,
}
)
export
class
CounterComponent
{
count
=
signal
(
0
)
;
increment
(
)
{
this
.
count
.
update
(
(
v
)
=>
v
+
1
)
;
// No zone.js needed - Signal triggers change detection
}
}
Key Zoneless Benefits
Performance
No zone.js patches on async APIs
Debugging
Clean stack traces without zone wrappers
Bundle size
Smaller without zone.js (~15KB savings)
Interoperability
Better with Web Components and micro-frontends 4. Server-Side Rendering & Hydration SSR Setup with Angular CLI ng add @angular/ssr Hydration Configuration // app.config.ts import { ApplicationConfig } from "@angular/core" ; import { provideClientHydration , withEventReplay , } from "@angular/platform-browser" ; export const appConfig : ApplicationConfig = { providers : [ provideClientHydration ( withEventReplay ( ) ) ] , } ; Incremental Hydration (v20+) import { Component } from "@angular/core" ; @ Component ( { selector : "app-page" , standalone : true , template : <app-hero /> @defer (hydrate on viewport) { <app-comments /> } @defer (hydrate on interaction) { <app-chat-widget /> } , } ) export class PageComponent { } Hydration Triggers Trigger When to Use on idle Low-priority, hydrate when browser idle on viewport Hydrate when element enters viewport on interaction Hydrate on first user interaction on hover Hydrate when user hovers on timer(ms) Hydrate after specified delay 5. Modern Routing Patterns Functional Route Guards // auth.guard.ts import { inject } from "@angular/core" ; import { Router , CanActivateFn } from "@angular/router" ; import { AuthService } from "./auth.service" ; export const authGuard : CanActivateFn = ( route , state ) => { const auth = inject ( AuthService ) ; const router = inject ( Router ) ; if ( auth . isAuthenticated ( ) ) { return true ; } return router . createUrlTree ( [ "/login" ] , { queryParams : { returnUrl : state . url } , } ) ; } ; // Usage in routes export const routes : Routes = [ { path : "dashboard" , loadComponent : ( ) => import ( "./dashboard.component" ) , canActivate : [ authGuard ] , } , ] ; Route-Level Data Resolvers import { inject } from '@angular/core' ; import { ResolveFn } from '@angular/router' ; import { UserService } from './user.service' ; import { User } from './user.model' ; export const userResolver : ResolveFn < User

= ( route ) => { const userService = inject ( UserService ) ; return userService . getUser ( route . paramMap . get ( 'id' ) ! ) ; } ; // In routes { path : 'user/:id' , loadComponent : ( ) => import ( './user.component' ) , resolve : { user : userResolver } } // In component export class UserComponent { private route = inject ( ActivatedRoute ) ; user = toSignal ( this . route . data . pipe ( map ( d => d [ 'user' ] ) ) ) ; } 6. Dependency Injection Patterns Modern inject() Function import { Component , inject } from '@angular/core' ; import { HttpClient } from '@angular/common/http' ; import { UserService } from './user.service' ; @ Component ( { ... } ) export class UserComponent { // Modern inject() - no constructor needed private http = inject ( HttpClient ) ; private userService = inject ( UserService ) ; // Works in any injection context users = toSignal ( this . userService . getUsers ( ) ) ; } Injection Tokens for Configuration import { InjectionToken , inject } from "@angular/core" ; // Define token export const API_BASE_URL = new InjectionToken < string

( "API_BASE_URL" ) ; // Provide in config bootstrapApplication ( AppComponent , { providers : [ { provide : API_BASE_URL , useValue : "https://api.example.com" } ] , } ) ; // Inject in service @ Injectable ( { providedIn : "root" } ) export class ApiService { private baseUrl = inject ( API_BASE_URL ) ; get ( endpoint : string ) { return this . http . get ( ${ this . baseUrl } / ${ endpoint } ) ; } } 7. Component Composition & Reusability Content Projection (Slots) @ Component ( { selector : 'app-card' , template : `

` } ) export class CardComponent { } // Usage < app - card

< h3 card - header

Title < / h3

< p

Body content < / p

< / app - card

Host Directives (Composition) // Reusable behaviors without inheritance @ Directive ( { standalone : true , selector : '[appTooltip]' , inputs : [ 'tooltip' ] // Signal input alias } ) export class TooltipDirective { ... } @ Component ( { selector : 'app-button' , standalone : true , hostDirectives : [ { directive : TooltipDirective , inputs : [ 'tooltip: title' ] // Map input } ] , template : <ng-content /> } ) export class ButtonComponent { } 8. State Management Patterns Signal-Based State Service import { Injectable , signal , computed } from "@angular/core" ; interface AppState { user : User | null ; theme : "light" | "dark" ; notifications : Notification [ ] ; } @ Injectable ( { providedIn : "root" } ) export class StateService { // Private writable signals private _user = signal < User | null

( null ) ; private _theme = signal < "light" | "dark"

( "light" ) ; private _notifications = signal < Notification [ ]

( [ ] ) ; // Public read-only computed readonly user = computed ( ( ) => this . _user ( ) ) ; readonly theme = computed ( ( ) => this . _theme ( ) ) ; readonly notifications = computed ( ( ) => this . _notifications ( ) ) ; readonly unreadCount = computed ( ( ) => this . _notifications ( ) . filter ( ( n ) => ! n . read ) . length , ) ; // Actions setUser ( user : User | null ) { this . _user . set ( user ) ; } toggleTheme ( ) { this . _theme . update ( ( t ) => ( t === "light" ? "dark" : "light" ) ) ; } addNotification ( notification : Notification ) { this . _notifications . update ( ( n ) => [ ... n , notification ] ) ; } } Component Store Pattern with Signals import { Injectable , signal , computed , inject } from "@angular/core" ; import { HttpClient } from "@angular/common/http" ; import { toSignal } from "@angular/core/rxjs-interop" ; @ Injectable ( ) export class ProductStore { private http = inject ( HttpClient ) ; // State private _products = signal < Product [ ]

( [ ] ) ; private _loading = signal ( false ) ; private _filter = signal ( "" ) ; // Selectors readonly products = computed ( ( ) => this . _products ( ) ) ; readonly loading = computed ( ( ) => this . _loading ( ) ) ; readonly filteredProducts = computed ( ( ) => { const filter = this . _filter ( ) . toLowerCase ( ) ; return this . _products ( ) . filter ( ( p ) => p . name . toLowerCase ( ) . includes ( filter ) , ) ; } ) ; // Actions loadProducts ( ) { this . _loading . set ( true ) ; this . http . get < Product [ ]

( "/api/products" ) . subscribe ( { next : ( products ) => { this . _products . set ( products ) ; this . _loading . set ( false ) ; } , error : ( ) => this . _loading . set ( false ) , } ) ; } setFilter ( filter : string ) { this . _filter . set ( filter ) ; } } 9. Forms with Signals (Coming in v22+) Current Reactive Forms import { Component , inject } from "@angular/core" ; import { FormBuilder , Validators , ReactiveFormsModule } from "@angular/forms" ; @ Component ( { selector : "app-user-form" , standalone : true , imports : [ ReactiveFormsModule ] , template : `

` , } ) export class UserFormComponent { private fb = inject ( FormBuilder ) ; form = this . fb . group ( { name : [ "" , Validators . required ] , email : [ "" , [ Validators . required , Validators . email ] ] , } ) ; onSubmit ( ) { if ( this . form . valid ) { console . log ( this . form . value ) ; } } } Signal-Aware Form Patterns (Preview) // Future Signal Forms API (experimental) import { Component , signal } from '@angular/core' ; @ Component ( { ... } ) export class SignalFormComponent { name = signal ( '' ) ; email = signal ( '' ) ; // Computed validation isValid = computed ( ( ) => this . name ( ) . length

0 && this . email ( ) . includes ( '@' ) ) ; submit ( ) { if ( this . isValid ( ) ) { console . log ( { name : this . name ( ) , email : this . email ( ) } ) ; } } } 10. Performance Optimization Change Detection Strategies @ Component ( { changeDetection : ChangeDetectionStrategy . OnPush , // Only checks when: // 1. Input signal/reference changes // 2. Event handler runs // 3. Async pipe emits // 4. Signal value changes } ) Defer Blocks for Lazy Loading @ Component ( { template : `

@defer (on viewport) { } @placeholder {

} @loading (minimum 200ms) { } @error {

Failed to load chart

} } ) NgOptimizedImage import { NgOptimizedImage } from '@angular/common' ; @ Component ( { imports : [ NgOptimizedImage ] , template : ` } ) 11. Testing Modern Angular Testing Signal Components import { ComponentFixture , TestBed } from "@angular/core/testing" ; import { CounterComponent } from "./counter.component" ; describe ( "CounterComponent" , ( ) => { let component : CounterComponent ; let fixture : ComponentFixture < CounterComponent

; beforeEach ( async ( ) => { await TestBed . configureTestingModule ( { imports : [ CounterComponent ] , // Standalone import } ) . compileComponents ( ) ; fixture = TestBed . createComponent ( CounterComponent ) ; component = fixture . componentInstance ; fixture . detectChanges ( ) ; } ) ; it ( "should increment count" , ( ) => { expect ( component . count ( ) ) . toBe ( 0 ) ; component . increment ( ) ; expect ( component . count ( ) ) . toBe ( 1 ) ; } ) ; it ( "should update DOM on signal change" , ( ) => { component . count . set ( 5 ) ; fixture . detectChanges ( ) ; const el = fixture . nativeElement . querySelector ( ".count" ) ; expect ( el . textContent ) . toContain ( "5" ) ; } ) ; } ) ; Testing with Signal Inputs import { ComponentFixture , TestBed } from "@angular/core/testing" ; import { ComponentRef } from "@angular/core" ; import { UserCardComponent } from "./user-card.component" ; describe ( "UserCardComponent" , ( ) => { let fixture : ComponentFixture < UserCardComponent

; let componentRef : ComponentRef < UserCardComponent

; beforeEach ( async ( ) => { await TestBed . configureTestingModule ( { imports : [ UserCardComponent ] , } ) . compileComponents ( ) ; fixture = TestBed . createComponent ( UserCardComponent ) ; componentRef = fixture . componentRef ; // Set signal inputs via setInput componentRef . setInput ( "id" , "123" ) ; componentRef . setInput ( "name" , "John Doe" ) ; fixture . detectChanges ( ) ; } ) ; it ( "should display user name" , ( ) => { const el = fixture . nativeElement . querySelector ( "h3" ) ; expect ( el . textContent ) . toContain ( "John Doe" ) ; } ) ; } ) ; Best Practices Summary Pattern ✅ Do ❌ Don't State Use Signals for local state Overuse RxJS for simple state Components Standalone with direct imports Bloated SharedModules Change Detection OnPush + Signals Default CD everywhere Lazy Loading @defer and loadComponent Eager load everything DI inject() function Constructor injection (verbose) Inputs input() signal function @Input() decorator (legacy) Zoneless Enable for new projects Force on legacy without testing Resources Angular.dev Documentation Angular Signals Guide Angular SSR Guide Angular Update Guide Angular Blog Common Troubleshooting Issue Solution Signal not updating UI Ensure OnPush + call signal as function count() Hydration mismatch Check server/client content consistency Circular dependency Use inject() with forwardRef Zoneless not detecting changes Trigger via signal updates, not mutations SSR fetch fails Use TransferState or withFetch() Limitations Use this skill only when the task clearly matches the scope described above. Do not treat the output as a substitute for environment-specific validation, testing, or expert review. Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.

返回排行榜