Przejdź do treści
Frontend

Angular - Kompletny framework frontend 2025

Opublikowano:
·
Zaktualizowano:
·4 min czytania·Autor: MDS Software Solutions Group

Angular Kompletny framework

frontend

Angular - Kompletny Framework Frontend do Budowy Nowoczesnych Aplikacji

Angular to jeden z najpopularniejszych frameworkow frontendowych na swiecie, rozwijany i wspierany przez Google. Od momentu premiery Angular 2 w 2016 roku, framework przeszedl ogromna ewolucje - od monolitycznej architektury opartej na modulach, az po nowoczesne podejscie ze standalone components, signals i defer blocks w wersjach 17 i 18. W tym artykule przyjrzymy sie wszystkim kluczowym aspektom Angulara, od podstaw po zaawansowane techniki optymalizacji.

Dlaczego Angular w 2025 roku?#

Angular wyroznaia sie na tle konkurencji kilkoma kluczowymi cechami:

  • Kompletnosc - Angular dostarcza wszystko out-of-the-box: routing, formularze, HTTP client, dependency injection, testowanie
  • TypeScript first - Angular od poczatku byl budowany z mysla o TypeScript, co zapewnia silne typowanie i lepsze narzedzia deweloperskie
  • Skalowalnosc - sprawdza sie zarowno w malych projektach, jak i w duzych aplikacjach korporacyjnych
  • Ekosystem - Angular CLI, Angular Material, Angular Universal, Angular CDK
  • Stabilnosc - przewidywalny cykl wydawniczy i dlugoterminowe wsparcie (LTS)
  • Nowe API - Signals, standalone components i defer blocks to rewolucja w DX

Nowosci w Angular 17/18#

Standalone Components#

Od Angular 17 standalone components staly sie domyslnym sposobem tworzenia komponentow. Eliminuja potrzebe deklarowania modulow NgModule dla prostych przypadkow uzycia:

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-hero',
  standalone: true,
  imports: [CommonModule, RouterLink],
  template: `
    <section class="hero">
      <h1>{{ title }}</h1>
      <p>{{ description }}</p>
      <a routerLink="/contact" class="cta-button">Skontaktuj sie</a>
    </section>
  `,
  styles: [`
    .hero {
      padding: 4rem 2rem;
      text-align: center;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
    }
    .cta-button {
      display: inline-block;
      padding: 12px 32px;
      background: white;
      color: #667eea;
      border-radius: 8px;
      text-decoration: none;
      font-weight: 600;
    }
  `]
})
export class HeroComponent {
  title = 'Witamy w naszej aplikacji';
  description = 'Zbudowana z Angular 18 i standalone components';
}

Signals - Nowy System Reaktywnosci#

Signals to nowy prymityw reaktywnosci w Angular, ktory pozwala na bardziej granularne sledzenie zmian i lepsza wydajnosc niz tradycyjne podejscie oparte na Zone.js:

import { Component, signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <div class="counter">
      <h2>Licznik: {{ count() }}</h2>
      <p>Podwojona wartosc: {{ doubleCount() }}</p>
      <button (click)="increment()">+1</button>
      <button (click)="decrement()">-1</button>
      <button (click)="reset()">Reset</button>
    </div>
  `
})
export class CounterComponent {
  // Signal - reaktywna wartosc
  count = signal(0);

  // Computed signal - automatycznie przeliczany
  doubleCount = computed(() => this.count() * 2);

  constructor() {
    // Effect - reaguje na zmiany signals
    effect(() => {
      console.log(`Aktualna wartosc licznika: ${this.count()}`);
    });
  }

  increment() {
    this.count.update(value => value + 1);
  }

  decrement() {
    this.count.update(value => value - 1);
  }

  reset() {
    this.count.set(0);
  }
}

Signals wspieraja rowniez integracje z RxJS dzieki funkcjom toSignal() i toObservable():

import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-clock',
  standalone: true,
  template: `<p>Czas: {{ time() }}</p>`
})
export class ClockComponent {
  // Konwersja Observable na Signal
  time = toSignal(
    interval(1000).pipe(
      map(() => new Date().toLocaleTimeString())
    ),
    { initialValue: new Date().toLocaleTimeString() }
  );
}

Defer Blocks - Leniwe Ladowanie Komponentow#

Defer blocks to deklaratywny sposob na leniwe ladowanie czesci szablonu, co znaczaco poprawia czas pierwszego ladowania strony:

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [HeavyChartComponent, DataTableComponent],
  template: `
    <h1>Dashboard</h1>

    <!-- Laduj wykres po 2 sekundach -->
    @defer (on timer(2s)) {
      <app-heavy-chart [data]="chartData" />
    } @placeholder {
      <div class="skeleton">Ladowanie wykresu...</div>
    } @loading (minimum 500ms) {
      <div class="spinner">Wczytywanie komponentu...</div>
    } @error {
      <p>Nie udalo sie zaladowac wykresu</p>
    }

    <!-- Laduj tabele gdy jest widoczna w viewport -->
    @defer (on viewport) {
      <app-data-table [data]="tableData" />
    } @placeholder {
      <div class="skeleton-table">Przewin, aby zobaczyc tabele</div>
    }
  `
})
export class DashboardComponent {
  chartData = signal<ChartData[]>([]);
  tableData = signal<TableRow[]>([]);
}

Komponenty i Szablony#

Angular wykorzystuje dekoratory TypeScript do definiowania komponentow. Kazdy komponent sklada sie z klasy TypeScript, szablonu HTML i opcjonalnych stylow:

import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
  inStock: boolean;
}

@Component({
  selector: 'app-product-card',
  standalone: true,
  imports: [CommonModule],
  template: `
    <div class="product-card" [class.out-of-stock]="!product.inStock">
      <h3>{{ product.name }}</h3>
      <p class="price">{{ product.price | currency:'PLN':'symbol':'1.2-2' }}</p>
      <p>{{ product.description }}</p>

      @if (product.inStock) {
        <button (click)="onAddToCart()" class="btn-primary">
          Dodaj do koszyka
        </button>
      } @else {
        <span class="badge-unavailable">Niedostepny</span>
      }
    </div>
  `
})
export class ProductCardComponent {
  @Input({ required: true }) product!: Product;
  @Output() addToCart = new EventEmitter<Product>();

  onAddToCart() {
    this.addToCart.emit(this.product);
  }
}

Nowa Skladnia Control Flow#

Angular 17 wprowadzil nowa wbudowana skladnie control flow, ktora zastepuje dyrektywy strukturalne:

<!-- Nowa skladnia @if -->
@if (users().length > 0) {
  <ul>
    @for (user of users(); track user.id) {
      <li>{{ user.name }} - {{ user.email }}</li>
    } @empty {
      <li>Brak uzytkownikow do wyswietlenia</li>
    }
  </ul>
} @else {
  <p>Ladowanie danych...</p>
}

<!-- Nowa skladnia @switch -->
@switch (status()) {
  @case ('active') {
    <span class="badge-success">Aktywny</span>
  }
  @case ('inactive') {
    <span class="badge-warning">Nieaktywny</span>
  }
  @default {
    <span class="badge-info">Nieznany</span>
  }
}

Dependency Injection#

System dependency injection (DI) w Angular to jeden z jego najwazniejszych filarow. Pozwala na luznie powiazane komponenty i latwiejsze testowanie:

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, tap } from 'rxjs/operators';

interface User {
  id: number;
  name: string;
  email: string;
  role: string;
}

@Injectable({
  providedIn: 'root'  // Singleton na poziomie calej aplikacji
})
export class UserService {
  private http = inject(HttpClient);
  private apiUrl = '/api/users';

  private usersSubject = new BehaviorSubject<User[]>([]);
  users$ = this.usersSubject.asObservable();

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl).pipe(
      tap(users => this.usersSubject.next(users))
    );
  }

  getUserById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user).pipe(
      tap(newUser => {
        const current = this.usersSubject.getValue();
        this.usersSubject.next([...current, newUser]);
      })
    );
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
      tap(() => {
        const current = this.usersSubject.getValue();
        this.usersSubject.next(current.filter(u => u.id !== id));
      })
    );
  }
}

Funkcja inject() to nowoczesna alternatywa dla wstrzykiwania przez konstruktor:

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [CommonModule],
  template: `
    @for (user of users(); track user.id) {
      <div class="user-card">
        <h3>{{ user.name }}</h3>
        <p>{{ user.email }}</p>
      </div>
    }
  `
})
export class UserListComponent {
  private userService = inject(UserService);
  users = toSignal(this.userService.users$, { initialValue: [] });
}

RxJS i Observables#

RxJS jest integralnym elementem Angulara. Observables sa uzywane w HTTP requests, formularzach reaktywnych, routerze i wielu innych miejscach:

import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import {
  Subject, debounceTime, distinctUntilChanged,
  switchMap, takeUntil, catchError, of
} from 'rxjs';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [ReactiveFormsModule, CommonModule],
  template: `
    <div class="search-container">
      <input
        [formControl]="searchControl"
        placeholder="Szukaj produktow..."
        class="search-input"
      />

      @if (isLoading()) {
        <div class="loading-spinner"></div>
      }

      <div class="results">
        @for (result of results(); track result.id) {
          <div class="result-item">
            <h4>{{ result.name }}</h4>
            <p>{{ result.description }}</p>
          </div>
        }
      </div>
    </div>
  `
})
export class SearchComponent implements OnInit, OnDestroy {
  private searchService = inject(SearchService);
  private destroy$ = new Subject<void>();

  searchControl = new FormControl('');
  results = signal<SearchResult[]>([]);
  isLoading = signal(false);

  ngOnInit() {
    this.searchControl.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged(),
      tap(() => this.isLoading.set(true)),
      switchMap(query =>
        this.searchService.search(query ?? '').pipe(
          catchError(() => of([]))
        )
      ),
      takeUntil(this.destroy$)
    ).subscribe(results => {
      this.results.set(results);
      this.isLoading.set(false);
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Angular Router#

Angular Router to potezny system nawigacji z obsluga lazy loading, guards, resolvers i nested routes:

import { Routes } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from './services/auth.service';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./pages/home/home.component')
        .then(m => m.HomeComponent),
    title: 'Strona glowna'
  },
  {
    path: 'products',
    loadChildren: () =>
      import('./pages/products/product.routes')
        .then(m => m.PRODUCT_ROUTES),
    title: 'Produkty'
  },
  {
    path: 'admin',
    loadChildren: () =>
      import('./pages/admin/admin.routes')
        .then(m => m.ADMIN_ROUTES),
    canActivate: [() => inject(AuthService).isAuthenticated()],
    title: 'Panel administracyjny'
  },
  {
    path: '**',
    loadComponent: () =>
      import('./pages/not-found/not-found.component')
        .then(m => m.NotFoundComponent),
    title: 'Strona nie znaleziona'
  }
];

Przyklad nested routes z resolverami:

// product.routes.ts
export const PRODUCT_ROUTES: Routes = [
  {
    path: '',
    loadComponent: () =>
      import('./product-list.component')
        .then(m => m.ProductListComponent)
  },
  {
    path: ':id',
    loadComponent: () =>
      import('./product-detail.component')
        .then(m => m.ProductDetailComponent),
    resolve: {
      product: (route: ActivatedRouteSnapshot) => {
        const productService = inject(ProductService);
        return productService.getById(Number(route.paramMap.get('id')));
      }
    }
  }
];

Formularze Reaktywne#

Formularze reaktywne w Angular oferuja pelna kontrole nad walidacja i stanem formularza:

import { Component, inject } from '@angular/core';
import {
  FormBuilder, FormGroup, Validators,
  ReactiveFormsModule, AbstractControl
} from '@angular/forms';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-registration',
  standalone: true,
  imports: [ReactiveFormsModule, CommonModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label for="name">Imie i nazwisko</label>
        <input id="name" formControlName="name" />
        @if (form.get('name')?.hasError('required') &&
             form.get('name')?.touched) {
          <span class="error">Imie jest wymagane</span>
        }
      </div>

      <div class="form-group">
        <label for="email">Email</label>
        <input id="email" type="email" formControlName="email" />
        @if (form.get('email')?.hasError('email') &&
             form.get('email')?.touched) {
          <span class="error">Podaj poprawny adres email</span>
        }
      </div>

      <div class="form-group">
        <label for="password">Haslo</label>
        <input id="password" type="password" formControlName="password" />
        @if (form.get('password')?.hasError('minlength') &&
             form.get('password')?.touched) {
          <span class="error">Haslo musi miec minimum 8 znakow</span>
        }
      </div>

      <div class="form-group">
        <label for="confirmPassword">Potwierdz haslo</label>
        <input id="confirmPassword" type="password"
               formControlName="confirmPassword" />
        @if (form.hasError('passwordsMismatch') &&
             form.get('confirmPassword')?.touched) {
          <span class="error">Hasla musza byc identyczne</span>
        }
      </div>

      <button type="submit" [disabled]="form.invalid">
        Zarejestruj sie
      </button>
    </form>
  `
})
export class RegistrationComponent {
  private fb = inject(FormBuilder);

  form: FormGroup = this.fb.group({
    name: ['', [Validators.required, Validators.minLength(2)]],
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
    confirmPassword: ['', Validators.required]
  }, {
    validators: this.passwordMatchValidator
  });

  passwordMatchValidator(control: AbstractControl) {
    const password = control.get('password')?.value;
    const confirmPassword = control.get('confirmPassword')?.value;
    return password === confirmPassword ? null : { passwordsMismatch: true };
  }

  onSubmit() {
    if (this.form.valid) {
      console.log('Formularz wysłany:', this.form.value);
    }
  }
}

HttpClient i Komunikacja z API#

Angular HttpClient to potezne narzedzie do komunikacji z API, ktore zwraca Observables i wspiera interceptory:

// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();

  if (token) {
    const cloned = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${token}`)
    });
    return next(cloned);
  }

  return next(req);
};

// error.interceptor.ts
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        inject(AuthService).logout();
        inject(Router).navigate(['/login']);
      }
      return throwError(() => error);
    })
  );
};

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(
      withInterceptors([authInterceptor, errorInterceptor])
    )
  ]
};

Angular CLI#

Angular CLI to niezbedne narzedzie do pracy z Angularem. Przyspiesza tworzenie projektow, komponentow, serwisow i wiele wiecej:

# Tworzenie nowego projektu
ng new my-app --style=scss --routing --strict

# Generowanie komponentow i serwisow
ng generate component features/dashboard --standalone
ng generate service services/api
ng generate pipe pipes/currency-format
ng generate guard guards/auth --functional
ng generate interceptor interceptors/auth --functional

# Budowanie i uruchamianie
ng serve --open
ng build --configuration=production
ng test
ng e2e

# Aktualizacja Angular
ng update @angular/core @angular/cli

Testowanie z Jasmine/Karma i Jest#

Angular ma wbudowane wsparcie dla testowania. Tradycyjnie uzywa Jasmine i Karma, ale coraz wiecej zespolow przechodzi na Jest:

// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import {
  HttpClientTestingModule,
  HttpTestingController
} from '@angular/common/http/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [UserService]
    });

    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('powinien pobrac liste uzytkownikow', () => {
    const mockUsers = [
      { id: 1, name: 'Jan Kowalski', email: 'jan@example.com', role: 'admin' },
      { id: 2, name: 'Anna Nowak', email: 'anna@example.com', role: 'user' }
    ];

    service.getUsers().subscribe(users => {
      expect(users.length).toBe(2);
      expect(users[0].name).toBe('Jan Kowalski');
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });

  it('powinien utworzyc nowego uzytkownika', () => {
    const newUser = { name: 'Piotr Wisniewski', email: 'piotr@example.com', role: 'user' };
    const createdUser = { id: 3, ...newUser };

    service.createUser(newUser).subscribe(user => {
      expect(user.id).toBe(3);
      expect(user.name).toBe('Piotr Wisniewski');
    });

    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('POST');
    req.flush(createdUser);
  });
});

Testowanie komponentow:

// counter.component.spec.ts
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]
    }).compileComponents();

    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('powinien rozpoczac od wartosci 0', () => {
    expect(component.count()).toBe(0);
  });

  it('powinien zwiekszyc wartosc po kliknieciu +1', () => {
    component.increment();
    expect(component.count()).toBe(1);
    expect(component.doubleCount()).toBe(2);
  });

  it('powinien zresetowac wartosc', () => {
    component.increment();
    component.increment();
    component.reset();
    expect(component.count()).toBe(0);
  });
});

Angular Material#

Angular Material to oficjalna biblioteka komponentow UI zbudowana zgodnie z wytycznymi Material Design:

import { Component, inject } from '@angular/core';
import { MatTableModule, MatTableDataSource } from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';

@Component({
  selector: 'app-user-table',
  standalone: true,
  imports: [
    MatTableModule, MatPaginatorModule, MatSortModule,
    MatInputModule, MatFormFieldModule, MatButtonModule,
    MatIconModule, MatDialogModule
  ],
  template: `
    <mat-form-field appearance="outline" class="filter-field">
      <mat-label>Filtruj</mat-label>
      <input matInput (keyup)="applyFilter($event)" placeholder="Szukaj..." />
      <mat-icon matSuffix>search</mat-icon>
    </mat-form-field>

    <table mat-table [dataSource]="dataSource" matSort>
      <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>Imie</th>
        <td mat-cell *matCellDef="let user">{{ user.name }}</td>
      </ng-container>

      <ng-container matColumnDef="email">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
        <td mat-cell *matCellDef="let user">{{ user.email }}</td>
      </ng-container>

      <ng-container matColumnDef="actions">
        <th mat-header-cell *matHeaderCellDef>Akcje</th>
        <td mat-cell *matCellDef="let user">
          <button mat-icon-button color="primary" (click)="editUser(user)">
            <mat-icon>edit</mat-icon>
          </button>
          <button mat-icon-button color="warn" (click)="deleteUser(user)">
            <mat-icon>delete</mat-icon>
          </button>
        </td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
    </table>

    <mat-paginator [pageSizeOptions]="[5, 10, 25]" showFirstLastButtons />
  `
})
export class UserTableComponent {
  private dialog = inject(MatDialog);
  displayedColumns = ['name', 'email', 'actions'];
  dataSource = new MatTableDataSource<User>();

  applyFilter(event: Event) {
    const filterValue = (event.target as HTMLInputElement).value;
    this.dataSource.filter = filterValue.trim().toLowerCase();
  }

  editUser(user: User) {
    // Logika edycji uzytkownika
  }

  deleteUser(user: User) {
    // Logika usuwania uzytkownika
  }
}

SSR z Angular Universal#

Server-Side Rendering (SSR) w Angular poprawia SEO i czas pierwszego ladowania strony. W Angular 17+ konfiguracja SSR jest znacznie prostsza:

# Dodanie SSR do istniejacego projektu
ng add @angular/ssr
// app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

Angular 17+ wspiera rowniez hydracje po stronie klienta, co eliminuje migotanie strony:

// app.config.ts
import { provideClientHydration } from '@angular/platform-browser';

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

Optymalizacja Wydajnosci#

1. Change Detection Strategy#

Uzywaj OnPush change detection w polaczeniu z Signals:

@Component({
  selector: 'app-product-list',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @for (product of products(); track product.id) {
      <app-product-card [product]="product" />
    }
  `
})
export class ProductListComponent {
  products = input.required<Product[]>();
}

2. Lazy Loading i Preloading#

// Konfiguracja preloadingu
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(
      routes,
      withPreloading(PreloadAllModules),
      withComponentInputBinding(),
      withViewTransitions()
    )
  ]
};

3. TrackBy w Petlach#

Nowa skladnia @for wymaga uzycia track, co zapewnia optymalna wydajnosc renderowania list:

@for (item of items(); track item.id) {
  <app-item [data]="item" />
}

4. Image Optimization#

Angular dostarcza dyrektywe NgOptimizedImage do optymalizacji obrazow:

import { NgOptimizedImage } from '@angular/common';

@Component({
  standalone: true,
  imports: [NgOptimizedImage],
  template: `
    <img ngSrc="/images/hero.webp"
         width="800"
         height="400"
         priority
         placeholder />
  `
})
export class HeroImageComponent {}

Podsumowanie#

Angular w wersjach 17 i 18 to nowoczesny, dojrzaly framework, ktory lacze najlepsze wzorce projektowe z nowoczesnymi API. Standalone components, signals i defer blocks znaczaco upraszczaja tworzenie aplikacji, a bogaty ekosystem narzedzi - od Angular CLI po Angular Material - przyspiesza prace zespolow deweloperskich na kazdym etapie projektu.

Kluczowe zalety Angulara to kompletnosc rozwiazania, silne typowanie dzieki TypeScript, wydajny system dependency injection oraz wbudowane wsparcie dla testowania, SSR i optymalizacji wydajnosci. Framework doskonale sprawdza sie w duzych aplikacjach korporacyjnych, platformach SaaS i wszedzie tam, gdzie wymagana jest skalowalnosc i latwoscia utrzymania kodu.

Potrzebujesz pomocy z projektem Angular?#

W MDS Software Solutions Group specjalizujemy sie w budowaniu nowoczesnych aplikacji webowych z wykorzystaniem Angular. Oferujemy:

  • Tworzenie aplikacji Angular od podstaw
  • Migracje ze starszych wersji Angular i AngularJS
  • Optymalizacje wydajnosci istniejacych aplikacji
  • Wdrozenie SSR i prerendering
  • Integracje z backendami .NET, Node.js i innymi

Skontaktuj sie z nami, aby omowic Twoj projekt!

Autor
MDS Software Solutions Group

Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.

Angular - Kompletny framework frontend 2025 | MDS Software Solutions Group | MDS Software Solutions Group