Files
fog/.agents/skills/frontend-angular/reference.md
Maurycy e8523d270e Refactor API and enhance Angular integration
- Removed `ciTargetName` from `nx.json`.
- Updated `package.json` to include new dependencies: `@types/seedrandom`, `fast-check`, `happy-dom`, and `@nestjs/schedule`.
- Modified `pnpm-lock.yaml` to reflect the addition of new packages and their versions.
- Improved project documentation in `PROJECT_CONTEXT.md` to clarify the use of Zod schemas and Angular framework decisions.
- Introduced new Angular components and patterns in the `.agents/skills/frontend-angular` directory, including examples and reference materials for Angular 21+ features.
2026-05-07 14:26:16 +00:00

3.9 KiB

Angular Reference

Detailed patterns and component library guidance for Angular 21+.

Zoneless Change Detection (Default in v21)

// No Zone.js needed - signals trigger updates automatically
bootstrapApplication(AppComponent, {
  providers: [
    // Zoneless is default in Angular 21+
    // Only add this if you need Zone.js for legacy code:
    // provideZoneChangeDetection()
  ]
});

Benefits:

  • Smaller bundle (no Zone.js ~100KB)
  • Predictable change detection
  • Better debugging (no Zone.js stack traces)
  • Signals automatically schedule updates

Dependency Injection

// Modern inject() function (preferred)
@Component({...})
export class UserService {
  private http = inject(HttpClient);
  private router = inject(Router);
}

// Constructor injection (still valid)
constructor(private http: HttpClient) {}

HTTP Client Patterns

// Functional interceptors (Angular 21+)
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).getToken();

  if (token) {
    req = req.clone({
      setHeaders: { Authorization: `Bearer ${token}` }
    });
  }

  return next(req);
};

// Registration
provideHttpClient(
  withInterceptors([authInterceptor, loggingInterceptor])
)

Resource API (Async Data)

// Signal-based async data loading
@Component({...})
export class UsersComponent {
  private usersService = inject(UsersService);

  searchQuery = signal('');

  usersResource = resource({
    request: () => ({ query: this.searchQuery() }),
    loader: async ({ request }) => {
      return this.usersService.search(request.query);
    }
  });

  // In template:
  // @if (usersResource.isLoading()) { ... }
  // @if (usersResource.hasValue()) { ... }
  // @if (usersResource.error()) { ... }
}

Component Libraries Comparison

Library Cost Components Best For
PrimeNG Free (MIT) 80+ B2B dashboards, forms
Kendo UI $1,028+/dev/year 110+ Enterprise, data grids
Angular Material Free ~40 Material Design apps

Reactive Forms with Signals

import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input formControlName="email" />
      @if (form.controls.email.errors?.['required']) {
        <span class="error">Email is required</span>
      }
      <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>
  `
})
export class LoginComponent {
  private fb = inject(FormBuilder);

  form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]]
  });

  onSubmit() {
    if (this.form.valid) {
      console.log(this.form.value);
    }
  }
}

Router Patterns

// Route configuration
export const routes: Routes = [
  { path: '', component: HomeComponent },
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component')
      .then(m => m.DashboardComponent),
    canActivate: [authGuard]
  },
  { path: '**', component: NotFoundComponent }
];

// Functional guard
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 }
  });
};

Error Handling

// Global error handler
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  private snackBar = inject(MatSnackBar);

  handleError(error: Error) {
    console.error('Unhandled error:', error);
    this.snackBar.open('An error occurred', 'Dismiss', {
      duration: 5000
    });
  }
}

// Provider
{ provide: ErrorHandler, useClass: GlobalErrorHandler }