Przejdź do treści
Biblioteka skilli
Programowanie · Budowanie i kod

angular-conventions

/angular-conventions

Opisuje konwencje projektów Angular 18-21: struktura, standalone czy NgModule, nowa składnia sterowania przepływem, dekoratory, inject(), cykl życia i potoki, dobierając wzorce do stylu projektu.

Repozytorium
claude-sdlc ↗
Licencja
MIT
Ostatnia zmiana repozytorium
26.10.2026

Ocena i bezpieczeństwo

100/ 100Klasa: dobry
Format25 / 25
Frontmatter z nazwą i opisem, poprawna nazwa, opis do 1024 znaków, nazwa zgodna z folderem.
Odkrywalność25 / 25
Opis mówi, kiedy użyć skilla, ma sensowną długość i jest w 3. osobie.
Treść35 / 35
Co najmniej 150 słów i do 500 linii, nagłówki, listy kroków, przykłady albo kod.
Higiena15 / 15
Bez zaślepek typu TODO, bez ścieżek Windows, z licencją.
Ryzykobrak sygnałów w skanie
Pliki w paczce
1
Skrypty
0
Komentarze HTML
0
Adresy URL w SKILL.md
1
Przestarzałość

Brak sygnałów. Repozytorium zmienione 26.10.2026, bez odwołań do wycofanych modeli i API.

Licencja

MIT, otwarta: można używać i zmieniać, z zachowaniem informacji o autorze.

Co robi i kiedy

Opisuje konwencje projektów Angular 18-21: struktura, standalone czy NgModule, nowa składnia sterowania przepływem, dekoratory, inject(), cykl życia i potoki, dobierając wzorce do stylu projektu.

Oryginalny opis (EN)

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy

Jak działa

  1. Project style detection
  2. Project structure
  3. Standalone bootstrap (Angular 17+)
  4. NgModule bootstrap (legacy)
  5. Standalone component
  6. NgModule component
  7. Decorators
  8. Dependency injection
  9. Modern control flow (Angular 17+)
  10. Legacy structural directives (NgModule projects, Angular ≤16)
Sekcje z nagłówków SKILL.md, w oryginale · 386 linii, 1 537 słów
Podgląd SKILL.md
---
name: angular-conventions
description: |
  Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer.

  Use this skill to:
  - Detect project style (standalone vs NgModule) and apply matching patterns.
  - Pick correct decorators and DI approach.
  - Use modern control flow (@if/@for/@switch) in Angular 17+ projects.
  - Apply `inject()` function over constructor injection where appropriate.
  - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy).

  Do NOT use this skill for:
  - State management (see angular-state-and-rx).
  - Routing (see angular-routing).
  - Forms (see angular-forms).
  - Testing (see angular-testing).
user-invocable: false
paths: ["src/**/*.ts", "src/**/*.html"]
---

# Angular Conventions

Modern Angular era (17+) with NgModule fallback for legacy projects. This skill covers structural patterns, decorators, DI, and template syntax.

## Project style detection

| Markers | Style |
|---|---|
| `bootstrapApplication(AppComponent, {...})` in `main.ts` + NO `*.module.ts` (or only `app-routing.module.ts`) | **Standalone-first** (Angular 17+ recommended) |
| `platformBrowserDynamic().bootstrapModule(AppModule)` + `app.module.ts` exists | **NgModule legacy** |
| Both bootstrap calls or partial migration | **Mixed/migrating** — mirror per area; prefer standalone for new code |

For new code in mixed projects, prefer standalone unless the team has a strict consistency rule.

## Project structure

```
src/
├── main.ts                          # bootstrap
├── index.html
├── styles.scss
├── app/
│   ├── app.component.ts             # root component
│   ├── app.component.html
│   ├── app.component.scss
│   ├── app.config.ts                # ApplicationConfig (standalone)
│   ├── app.routes.ts                # route definitions (standalone)
│   ├── app.module.ts                # AppModule (NgModule legacy)
│   ├── app-routing.module.ts        # routing module (NgModule legacy)
│   ├── core/                        # app-wide singletons
│   │   ├── auth/
│   │   │   ├── auth.service.ts
│   │   │   └── auth.guard.ts
│   │   └── http/
│   │       └── auth.interceptor.ts
│   ├── shared/                      # cross-feature components/pipes/directives
│   │   ├── components/
│   │   ├── pipes/
│   │   └── directives/
│   ├── features/                    # feature folders
│   │   ├── users/
│   │   │   ├── users.component.ts
│   │   │   ├── users.component.html
│   │   │   ├── users.component.scss
│   │   │   ├── users.service.ts
│   │   │   ├── users.routes.ts      # feature routes
│   │   │   └── user.model.ts
│   │   └── orders/
│   └── layout/                      # navbar, sidebar, app shell
├── assets/
└── environments/
    ├── environment.ts
    └── environment.prod.ts
```

Mirror project layout. Don't restructure as part of feature work.

## Standalone bootstrap (Angular 17+)

```ts
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { authInterceptor } from './app/core/http/auth.interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor])),
  ],
}).catch((err) => console.error(err));
```

Or split providers into `app.config.ts`:

```ts
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './core/http/auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor])),
  ],
};

// main.ts
bootstrapApplication(AppComponent, appConfig);
```

`provide*` functions are the standalone-era replacements for `*Module` imports. Examples: `provideRouter`, `provideHttpClient`, `provideAnimations`, `provideStore` (NgRx).

## NgModule bootstrap (legacy)

```ts
// main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

// app.module.ts
@NgModule({
  declarations: [AppComponent, UserListComponent, /* all components */],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule, ReactiveFormsModule],
  providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

Components and pipes must be declared in exactly one NgModule's `declarations` (NgModule legacy invariant).

## Standalone component

```ts
import { Component, signal, computed, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { UsersService } from './users.service';

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [CommonModule, RouterLink],
  template: `
    <h2>Users ({{ count() }})</h2>
    @if (loading()) {
      <p>Loading...</p>
    } @else {
      <ul>
        @for (user of users(); track user.id) {
          <li><a [routerLink]="['/users', user.id]">{{ user.name }}</a></li>
        }
      </ul>
    }
  `,
  styleUrl: './user-list.component.scss',
})
export class UserListComponent {
  private usersService = inject(UsersService);
  users = this.usersService.users;
  loading = this.usersService.loading;
  count = computed(() => this.users().length);
}
```

`standalone: true` + explicit `imports` array. The component declares its own template dependencies (CommonModule for pipes, RouterLink, other standalone components, etc.).

In Angular 19+, `standalone: true` is the default — you can omit it. For broad compatibility in 17/18, write it explicitly.

## NgModule component

```ts
@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrl: './user-list.component.scss',
})
export class UserListComponent { /* same body */ }

// user.module.ts
@NgModule({
  declarations: [UserListComponent],
  imports: [CommonModule, RouterModule],
  exports: [UserListComponent],
})
export class UserModule {}
```

For NgModule projects, declare every component, directive, pipe in some module.

## Decorators

```ts
@Component({ ... })          // a Component (standalone or in NgModule)
@Directive({ ... })           // a Directive (selector-based behavior)
@Pipe({ name: 'capitalize', standalone: true })
                              // a Pipe transform
@Injectable({ providedIn: 'root' })
                              // a service singleton at root injector
@NgModule({ ... })            // a module (legacy)

// Component members
@Input() user!: User;         // input property (decorator-based, legacy)
@Input({ required: true }) user!: User;  // required input (Angular 16+)
@Input({ transform: trim }) name!: string;  // input with transform
@Output() delete = new EventEmitter<string>();
@HostBinding('class.active') isActive = false;
@HostListener('click', ['$event']) onClick(e: Event) {}
@ViewChild('myRef') myRef!: ElementRef;
@ContentChild(MyComponent) projected!: MyComponent;
```

For new code in Angular 17.1+, prefer signal-based `input()` / `output()` / `viewChild()`:

```ts
import { input, output, viewChild } from '@angular/core';

user = input.required<User>();              // required input as InputSignal<User>
name = input('Anonymous');                   // optional with default
delete = output<string>();                   // OutputEmitterRef<string>
myRef = viewChild<ElementRef>('myRef');     // Signal<ElementRef | undefined>
```

Mirror existing project — if all components use `@Input()`/`@Output()` decorators, follow that. New greenfield code: prefer signal-based APIs.

## Dependency injection

### `inject()` function (Angular 14.1+)

```ts
import { inject, Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UsersService {
  private http = inject(HttpClient);
  private router = inject(Router);

  loadUsers() {
    return this.http.get<User[]>('/api/users');
  }
}
```

`inject()` works inside:
- `@Injectable` services (constructor or class field initializers).
- `@Component` constructor or class field initializers.
- Route guards / resolvers (functional API).
- Factory providers.
- `runInInjectionContext()` blocks.

### Constructor injection (legacy, still works)

```ts
@Injectable({ providedIn: 'root' })
export class UsersService {
  constructor(private http: HttpClient, private router: Router) {}
}
```

Both work. `inject()` is preferred for new code due to:
- Works outside constructors (route guards, factory functions).
- Better type narrowing with generic tokens.
- Less boilerplate.

### Custom providers

```ts
// Standalone (in providers array of bootstrapApplication or component)
{ provide: API_URL, useValue: 'https://api.example.com' }
{ provide: 'CONFIG', useFactory: () => ({ retries: 3 }) }
{ provide: AuditLog, useClass: ProductionAuditLog }

// InjectionToken for type safety
import { InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('API_URL');

// Inject
private apiUrl = inject(API_URL);
```

### Hierarchical injection

`providedIn: 'root'` — singleton at root injector (most services).
`providedIn: 'platform'` — shared across multiple Angular apps (rare).
Component-level providers in `@Component({ providers: [...] })` — new instance per component instance.

## Modern control flow (Angular 17+)

```html
@if (user(); as u) {
  <p>Welcome, {{ u.name }}</p>
} @else if (loading()) {
  <p>Loading...</p>
} @else {
  <a routerLink="/login">Log in</a>
}

@for (item of items(); track item.id; let i = $index, isFirst = $first) {
  <li [class.first]="isFirst">{{ i + 1 }}. {{ item.name }}</li>
} @empty {
  <li>No items</li>
}

@switch (status()) {
  @case ('loading') { <spinner /> }
  @case ('error') { <error-banner [error]="error()" /> }
  @default { <user-list [users]="users()" /> }
}
```

`track` is **mandatory** in `@for` — pick a stable identifier (entity ID), not index. Use `track $index` only when items have no stable identity.

`$index`, `$first`, `$last`, `$even`, `$odd`, `$count` are available local variables in `@for`.

## Legacy structural directives (NgModule projects, Angular ≤16)

```html
<p *ngIf="user; else noUser">Welcome, {{ user.name }}</p>
<ng-template #noUser>
  <a routerLink="/login">Log in</a>
</ng-template>

<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>

<div [ngSwitch]="status">
  <spinner *ngSwitchCase="'loading'"></spinner>
  <error-banner *ngSwitchCase="'error'" [error]="error"></error-banner>
  <user-list *ngSwitchDefault [users]="users"></user-list>
</div>
```

Requires `CommonModule` import.

## Lifecycle hooks

```ts
implements OnInit, OnDestroy, OnChanges, AfterViewInit
ngOnInit() { /* after first @Input() values bound, before view */ }
ngOnDestroy() { /* cleanup */ }
ngOnChanges(changes: SimpleChanges) { /* @Input changes — replaces by signal `effect` */ }
ngAfterViewInit() { /* DOM available */ }
```

Order:
1. Constructor (DI runs).
2. `ngOnChanges` (first call, with @Input values).
3. `ngOnInit`.
4. `ngDoCheck`.
5. `ngAfterContentInit`, `ngAfterContentChecked`.
6. `ngAfterViewInit`, `ngAfterViewChecked`.

For destruction: `ngOnDestroy`.

In standalone components with signals + `takeUntilDestroyed()`, manual `ngOnDestroy` is rarely needed.

## Pipes

```ts
// Built-in
{{ user.name | uppercase }}
{{ user.createdAt | date:'short' }}
{{ price | currency:'USD' }}
{{ data | json }}
{{ user$ | async }}                   // subscribe + unsubscribe automatic

// Custom standalone pipe
@Pipe({ name: 'truncate', standalone: true })
export class TruncatePipe implements PipeTransform {
  transform(value: string, max = 50): string {
    return value.length > max ? value.slice(0, max) + '...' : value;
  }
}

// Use
{{ description | truncate:100 }}
```

## Angular Universal (SSR) — pointer only

For Angular 17+: `@angular/ssr` package + `provideClientHydration()` + `provideServerRendering()`. Out of scope for v0.0.1 of this plugin (SPA-focused). If BA spec requires SSR, flag in BLOCKERS — needs separate spec or follow-up plugin.

For legacy: `@nguniversal/express-engine` — older approach.

## Anti-patterns

- ❌ `*ngIf`/`*ngFor`/`*ngSwitch` in Angular 17+ standalone projects — use `@if`/`@for`/`@switch`.
- ❌ Forgetting `track` in `@for` — Angular throws compile error in 17+.
- ❌ Using index as `track` for reorderable lists — defeats the optimization.
- ❌ Mixing standalone + NgModule arbitrarily — pick one per area.
- ❌ `new MyService()` outside test files — bypasses DI.
- ❌ Mutating `@Input()` / `input()` values directly — emit event for parent updates.
- ❌ `subscribe()` without unsubscription strategy — leaks.
- ❌ Logic in templates beyond simple expressions — extract to `computed()` or method.
- ❌ Calling signal without parens (`*ngIf="users"` instead of `*ngIf="users()"`) — common bug.
- ❌ `bypassSecurityTrustHtml` without justified upstream sanitization.
- ❌ Reading `process.env` in components — use `environment.ts` (still PUBLIC after build, but consistent).

Wersje

W bazie nie ma innych skilli o nazwie angular-conventions.

Migawka bazy z 28.10.2026 · wersja paczki 3ea37d9039cc (skrót SHA-256 całej paczki)

Zgłoś problem z tym skillem: szkodliwa treść, błędny opis albo prośba autora o usunięcie.

Podobne skille

  • 90angular-architecture
    Układa strukturę aplikacji Angular 17 lub nowszej: komponenty standalone, organizację według funkcji, leniwe ładowanie i sygnały dla stanu.
  • 100angular-dependency-injection
    Konfiguruje wstrzykiwanie zależności w Angularze, użycie inject() i providerów.
  • 80Angular Control Flow v21 Migration
    Wprowadza wbudowaną składnię sterowania przepływem Angulara (@if, @for, @switch) w ograniczonych wycinkach po aktualizacji do wersji 21.
  • 80Angular Control Flow v20 Migration
    Wprowadza wbudowaną składnię sterowania przepływem Angulara (@if, @for, @switch) w ograniczonych wycinkach po aktualizacji do wersji 20.
  • 100angular-routing
    Pomaga konfigurować Angular Router: trasy dla projektów standalone i NgModule, funkcyjne guardy, leniwe ładowanie, resolvery, typowane parametry, nawigację programową i dane trasy.
  • 100ionic-angular
    Prowadzi przez wzorce Angulara w aplikacjach Ionic: struktura projektu, standalone czy NgModule, nawigacja z routerem, lazy loading, cykl życia stron, formularze reaktywne, guardy i testy.

Uwagi i zgłoszenia

Wiadomość trafi do Bartosza Sokolińskiego. Adres e-mail jest potrzebny tylko do odpowiedzi. Szczegóły w polityce prywatności.