Angular 19 Zoneless: Zone.js ์—†์ด ๊ตฌํ˜„ํ•˜๋Š” ์„ฑ๋Šฅ๊ณผ ๋ณ€๊ฒฝ ๊ฐ์ง€

Angular์˜ zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€๋Š” Zone.js๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์—ฌ ๋” ์ž‘์€ ๋ฒˆ๋“ค ํฌ๊ธฐ, ๋น ๋ฅธ ๋ Œ๋”๋ง, ๊ทธ๋ฆฌ๊ณ  ์‹œ๊ทธ๋„์„ ํ†ตํ•œ ๋ช…์‹œ์  ๋ฐ˜์‘์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹ฌ์ธต ๊ฐ€์ด๋“œ๋Š” Angular 19์˜ provideExperimentalZonelessChangeDetection๋ถ€ํ„ฐ Angular 20+ ์•ˆ์ •ํ™” API๊นŒ์ง€ Zone.js์—์„œ zoneless Angular๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์‹œ๊ทธ๋„๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” Angular zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ

Angular์˜ zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€๋Š” standalone ์ปดํฌ๋„ŒํŠธ ๋„์ž… ์ดํ›„ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์•„ํ‚คํ…์ฒ˜ ๋ณ€ํ™”๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. Zone.js๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•จ์œผ๋กœ์จ Angular ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋” ์ž‘์€ ๋ฒˆ๋“ค ํฌ๊ธฐ(์•ฝ 33KB ๊ฐ์†Œ), ๋ถˆํ•„์š”ํ•œ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‚ฌ์ดํด 30-40% ๊ฐ์†Œ, ๊ทธ๋ฆฌ๊ณ  Zone ๊ด€๋ จ ๋…ธ์ด์ฆˆ๊ฐ€ ์—†๋Š” ๊น”๋”ํ•œ ์Šคํƒ ํŠธ๋ ˆ์ด์Šค๋ฅผ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Zoneless ํƒ€์ž„๋ผ์ธ

Angular 18์—์„œ zoneless๊ฐ€ ์‹คํ—˜์ ์œผ๋กœ ๋„์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Angular 19๋Š” provideExperimentalZonelessChangeDetection()์œผ๋กœ ์‹คํ—˜์  API๋ฅผ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. Angular 20์€ provideZonelessChangeDetection()์œผ๋กœ ์•ˆ์ •ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Angular 21์€ ์ƒˆ ํ”„๋กœ์ ํŠธ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ zoneless๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

Zone.js ๋ณ€๊ฒฝ ๊ฐ์ง€์˜ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ

zoneless๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์ „์— Zone.js ๋ฉ”์ปค๋‹ˆ์ฆ˜์— ๋Œ€ํ•œ ๋ช…ํ™•ํ•œ ์„ค๋ช…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Zone.js๋Š” ๋ชจ๋“  ๋น„๋™๊ธฐ ๋ธŒ๋ผ์šฐ์ € API๋ฅผ ๋ชฝํ‚ค ํŒจ์นญํ•ฉ๋‹ˆ๋‹ค: setTimeout, setInterval, Promise.then, addEventListener, XMLHttpRequest ๋“ฑ ์ˆ˜์‹ญ ๊ฐœ์˜ API๋ฅผ ๊ฐ€๋กœ์ฑ•๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ API ์ค‘ ํ•˜๋‚˜๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๋งˆ๋‹ค Zone.js๋Š” Angular์— ์•Œ๋ฆฌ๊ณ , Angular๋Š” ์ „์ฒด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์—์„œ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์—๋Š” ๊ทผ๋ณธ์ ์ธ ๊ฒฐํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค: Zone.js๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ๊ฐ€ ์‹ค์ œ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€์— ๋Œ€ํ•œ ํ†ต์ฐฐ๋ ฅ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํƒ€์ด๋ฐ์„ ์œ„ํ•ด ์‚ฌ์šฉ๋œ setTimeout๋„ ์—ฌ์ „ํžˆ ์ „์ฒด ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‚ฌ์ดํด์„ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ์ˆ˜๋ฐฑ ๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š” ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ด๋Ÿฌํ•œ ์˜ค๋ฒ„ํ—ค๋“œ๋Š” ์ธก์ • ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

app.config.ts - ์ „ํ†ต์ ์ธ Zone.js ์„ค์ • (Angular 18-19)typescript
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    // Zone.js๋Š” ์•ฝ 130๊ฐœ ์ด์ƒ์˜ ๋ธŒ๋ผ์šฐ์ € API๋ฅผ ํŒจ์น˜ํ•ฉ๋‹ˆ๋‹ค
    // ๋ชจ๋“  ๋น„๋™๊ธฐ ์ฝœ๋ฐฑ์ด ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
  ]
};

์ด๋ฒคํŠธ ๋ณ‘ํ•ฉ(Angular 14์—์„œ ๋„์ž…)์€ ์—ฌ๋Ÿฌ ์ด๋ฒคํŠธ๋ฅผ ๋‹จ์ผ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‚ฌ์ดํด๋กœ ๋ฐฐ์น˜ํ•˜์—ฌ ์ผ๋ถ€ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ด์ง€๋งŒ, ํ•ต์‹ฌ ๋ฌธ์ œ๋Š” ์—ฌ์ „ํžˆ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค: ๋ณ€๊ฒฝ ๊ฐ์ง€๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๋” ์ž์ฃผ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

Angular 19์™€ 20์—์„œ Zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€ ํ™œ์„ฑํ™”ํ•˜๊ธฐ

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ๋Š” Angular ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. Angular 19๋Š” ์‹คํ—˜์  API๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , Angular 20์€ ์•ˆ์ •ํ™”๋œ ๋ฒ„์ „์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

app.config.ts - Angular 19 (์‹คํ—˜์ )typescript
import { ApplicationConfig } from '@angular/core';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideExperimentalZonelessChangeDetection(),
    // ๋” ์ด์ƒ Zone.js ํŒจ์นญ์ด ์—†์Šต๋‹ˆ๋‹ค
  ]
};
app.config.ts - Angular 20+ (์•ˆ์ •ํ™”)typescript
import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZonelessChangeDetection(),
  ]
};

ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ „ํ™˜ํ•œ ํ›„, angular.json์˜ ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ ํƒ€๊ฒŸ ๋ชจ๋‘์—์„œ polyfills ๋ฐฐ์—ด์—์„œ zone.js๋ฅผ ์ œ๊ฑฐํ•œ ๋‹ค์Œ ํŒจํ‚ค์ง€๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค:

bash
# angular.json์˜ ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ ํƒ€๊ฒŸ์—์„œ zone.js polyfill ์ œ๊ฑฐ
# ๊ทธ๋Ÿฐ ๋‹ค์Œ ํŒจํ‚ค์ง€ ์ œ๊ฑฐ
npm uninstall zone.js

๋ฒˆ๋“ค ํฌ๊ธฐ ๊ฐ์†Œ๋Š” ์ฆ‰๊ฐ์ ์ž…๋‹ˆ๋‹ค: Zone.js๋Š” ์ฆ‰์‹œ ๋กœ๋“œ๋˜๋Š” ์ฝ”๋“œ์˜ ์•ฝ 33KB ์›๋ณธ(10KB gzip)์„ ์ฐจ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Zoneless ๋ชจ๋“œ์—์„œ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฒƒ

Zone.js๊ฐ€ ๋ชจ๋“  ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ฐ€๋กœ์ฑ„์ง€ ์•Š์œผ๋ฉด Angular๋Š” ๋ช…์‹œ์  ์•Œ๋ฆผ์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๋‹ค์Œ ์กฐ๊ฑด ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค:

  • ํ…œํ”Œ๋ฆฟ์—์„œ ์ฝ์€ ์‹œ๊ทธ๋„์ด ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•จ
  • ChangeDetectorRef.markForCheck()๊ฐ€ ํ˜ธ์ถœ๋จ (AsyncPipe์— ์˜ํ•ด ์ž๋™์œผ๋กœ)
  • ComponentRef.setInput()์„ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ ์ž…๋ ฅ์ด ๋ณ€๊ฒฝ๋จ
  • ํ…œํ”Œ๋ฆฟ ๋˜๋Š” ํ˜ธ์ŠคํŠธ ๋ฆฌ์Šค๋„ˆ ์ฝœ๋ฐฑ์ด ์‹คํ–‰๋จ (ํด๋ฆญ, ์ž…๋ ฅ ๋“ฑ)
  • ์ด์ „์— ๋”ํ‹ฐํ–ˆ๋˜ ๋ทฐ๊ฐ€ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์— ์ฒจ๋ถ€๋จ
counter.component.ts - ์‹œ๊ทธ๋„ ๊ธฐ๋ฐ˜ ๋ณ€๊ฒฝ ๊ฐ์ง€typescript
import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <div class="counter">
      <button (click)="decrement()">-</button>
      <span>{{ count() }}</span>
      <button (click)="increment()">+</button>
      <p>Double: {{ doubled() }}</p>
    </div>
  `
})
export class CounterComponent {
  // ์‹œ๊ทธ๋„ ์—…๋ฐ์ดํŠธ๋Š” ํ…œํ”Œ๋ฆฟ์— ์ž๋™์œผ๋กœ ์•Œ๋ฆฝ๋‹ˆ๋‹ค
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(v => v + 1);
    // markForCheck() ํ•„์š” ์—†์Œ - ์‹œ๊ทธ๋„์ด ์•Œ๋ฆผ ์ฒ˜๋ฆฌ
  }

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

์‹œ๊ทธ๋„์€ zoneless ๋ชจ๋“œ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋™๋ฐ˜์ž์ž…๋‹ˆ๋‹ค. ์‹œ๊ทธ๋„ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋ฉด Angular๋Š” ์ •ํ™•ํžˆ ์–ด๋–ค ํ…œํ”Œ๋ฆฟ์ด ์ด์— ์˜์กดํ•˜๋Š”์ง€ ์•Œ๊ณ  ํ•ด๋‹น ๋ทฐ์— ๋Œ€ํ•ด์„œ๋งŒ ํƒ€๊ฒŸ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

OnPush๋Š” ๊ถŒ์žฅ๋˜์ง€๋งŒ ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค

ChangeDetectionStrategy.OnPush๋ฅผ ์ฑ„ํƒํ•˜๋Š” ๊ฒƒ์€ zoneless ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ๊ถŒ์žฅ ๋‹จ๊ณ„์ด์ง€๋งŒ ์—„๊ฒฉํ•˜๊ฒŒ ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์ „๋žต์€ ์—ฌ์ „ํžˆ zoneless ๋ชจ๋“œ์—์„œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ OnPush๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž…๋ ฅ์ด ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ markForCheck()๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ๋งŒ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜๋„๋ก ๋ณด์žฅํ•˜์—ฌ zoneless ๋ฉ˜ํƒˆ ๋ชจ๋ธ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: ์ผ๋ฐ˜์ ์ธ ํ•จ์ •

Zone.js์—์„œ zoneless๋กœ์˜ ์ „ํ™˜์€ ๊ธฐ์กด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด ๊ฑฐ์˜ ํ•œ ์ค„ ๋ณ€๊ฒฝ์ด ์•„๋‹™๋‹ˆ๋‹ค. Zone.js์˜ ์•”๋ฌต์  ๋™์ž‘์— ์˜์กดํ–ˆ๋˜ ์—ฌ๋Ÿฌ ํŒจํ„ด์€ ๋ช…์‹œ์  ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

setTimeout๊ณผ setInterval์€ ๋” ์ด์ƒ ์—…๋ฐ์ดํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค

Zone.js ๋ชจ๋“œ์—์„œ setTimeout ์ฝœ๋ฐฑ์€ ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. zoneless ๋ชจ๋“œ์—์„œ๋Š” ๊ทธ๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

user-status.component.ts - ์ด์ „: Zone.js์— ์˜์กดtypescript
@Component({
  selector: 'app-user-status',
  template: `<span>{{ statusMessage }}</span>`
})
export class UserStatusComponent {
  statusMessage = 'Loading...';

  ngOnInit() {
    setTimeout(() => {
      // Zone.js๋Š” ์—ฌ๊ธฐ์„œ CD๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค - zoneless๋Š” ์•„๋‹™๋‹ˆ๋‹ค
      this.statusMessage = 'Ready';
    }, 2000);
  }
}
user-status.component.ts - ์ดํ›„: ์‹œ๊ทธ๋„ ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ๋ฐฉ์‹typescript
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-user-status',
  template: `<span>{{ statusMessage() }}</span>`
})
export class UserStatusComponent {
  statusMessage = signal('Loading...');

  ngOnInit() {
    setTimeout(() => {
      // ์‹œ๊ทธ๋„ ์—…๋ฐ์ดํŠธ๋Š” Angular์— ์ž๋™์œผ๋กœ ์•Œ๋ฆฝ๋‹ˆ๋‹ค
      this.statusMessage.set('Ready');
    }, 2000);
  }
}

Reactive Forms๋Š” ๋ช…์‹œ์  ์•Œ๋ฆผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

FormControl.setValue() ๋˜๋Š” patchValue()๋ฅผ ํ†ตํ•œ ํผ ์ƒํƒœ ๋ณ€๊ฒฝ์€ zoneless ๋ชจ๋“œ์—์„œ ์ž๋™์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ์ง€๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์ด๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค: ํผ ์˜ต์ €๋ฒ„๋ธ”์„ markForCheck()์— ์—ฐ๊ฒฐํ•˜๊ฑฐ๋‚˜ ์‹œ๊ทธ๋„์„ ํ†ตํ•ด ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

search.component.ts - zoneless์™€ ํ•จ๊ป˜ํ•˜๋Š” Reactive formstypescript
import { Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged } from 'rxjs';

@Component({
  selector: 'app-search',
  imports: [ReactiveFormsModule],
  template: `
    <input [formControl]="searchControl" placeholder="Search..." />
    <p>Results for: {{ searchTerm() }}</p>
  `
})
export class SearchComponent {
  searchControl = new FormControl('');

  // ์˜ต์ €๋ฒ„๋ธ”์„ ์‹œ๊ทธ๋„๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ž๋™ ํ…œํ”Œ๋ฆฟ ์—…๋ฐ์ดํŠธ
  searchTerm = toSignal(
    this.searchControl.valueChanges.pipe(
      debounceTime(300),
      distinctUntilChanged()
    ),
    { initialValue: '' }
  );
}

Angular ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

Zone.js ์—†๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง

zoneless Angular๋ฅผ ์‚ฌ์šฉํ•œ SSR์€ ํŠน๋ณ„ํ•œ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Zone.js๋Š” ์ด์ „์— Angular๊ฐ€ ์ง๋ ฌํ™”์— ์ ํ•ฉํ•œ "์•ˆ์ •์ ์ธ" ์ƒํƒœ์— ๋„๋‹ฌํ–ˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์—†์œผ๋ฉด PendingTasks ์„œ๋น„์Šค๊ฐ€ ๊ทธ ์—ญํ• ์„ ์ฑ„์›๋‹ˆ๋‹ค.

data-loader.component.ts - SSR ํ˜ธํ™˜ ๋น„๋™๊ธฐ ๋กœ๋”ฉtypescript
import { Component, inject, signal } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Component({
  selector: 'app-data-loader',
  template: `
    @if (data()) {
      <div>{{ data()!.title }}</div>
    } @else {
      <div>Loading...</div>
    }
  `
})
export class DataLoaderComponent {
  private http = inject(HttpClient);
  private pendingTasks = inject(PendingTasks);
  data = signal<{ title: string } | null>(null);

  ngOnInit() {
    // PendingTasks.run()์€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ SSR ์ง๋ ฌํ™”๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
    this.pendingTasks.run(async () => {
      const result = await firstValueFrom(
        this.http.get<{ title: string }>('/api/data')
      );
      this.data.set(result);
    });
  }
}

PendingTasks ์—†์ด Angular๋Š” ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— ํŽ˜์ด์ง€๋ฅผ ์ง๋ ฌํ™”ํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ๋นˆ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ: Zone.js vs Zoneless

Zone.js ์ œ๊ฑฐ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ํ–ฅ์ƒ์€ ์„ธ ๊ฐ€์ง€ ๋ฒ”์ฃผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค:

| ์ง€ํ‘œ | Zone.js | Zoneless | ๊ฐœ์„  | |--------|---------|----------|-------------| | ์ดˆ๊ธฐ ๋ฒˆ๋“ค ํฌ๊ธฐ | +33KB ์›๋ณธ / +10KB gzip | 0KB ์˜ค๋ฒ„ํ—ค๋“œ | 100% ๊ฐ์†Œ | | ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‚ฌ์ดํด (์ผ๋ฐ˜์ ์ธ ์•ฑ) | ์ƒํ˜ธ์ž‘์šฉ๋‹น 150-300ํšŒ | ์ƒํ˜ธ์ž‘์šฉ๋‹น 5-15ํšŒ | 80-95% ๊ฐ์†Œ | | ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ๊นŠ์ด | 8-12๊ฐœ์˜ ์ถ”๊ฐ€ Zone ํ”„๋ ˆ์ž„ | ๊น”๋”ํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ํŠธ๋ ˆ์ด์Šค | ์ฆ‰๊ฐ์ ์ธ ๋ช…ํ™•์„ฑ | | Time to Interactive (TTI) | ๊ธฐ์ค€์„  | 15-25% ๋” ๋น ๋ฆ„ | Zone ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ œ๊ฑฐ |

๊ฐ€์žฅ ๊ทน์ ์ธ ๊ฐœ์„ ์€ ๋ฌด๊ฑฐ์šด ๋น„๋™๊ธฐ ํ™œ๋™์ด ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค: HTTP ํด๋ง, WebSocket ์—ฐ๊ฒฐ, ํƒ€์ด๋จธ, ๋ณต์žกํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ. ์ด๋“ค ๊ฐ๊ฐ์€ ์ด์ „์— zoneless ๋ชจ๋“œ๊ฐ€ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‚ฌ์ดํด์„ ํŠธ๋ฆฌ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜ธํ™˜์„ฑ

์ผ๋ถ€ ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์—ฌ์ „ํžˆ ๋‚ด๋ถ€์ ์œผ๋กœ Zone.js์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‹ฌ ๋Œ€ํ™” ์ƒ์ž, ํŠน์ • Web Component ๋ž˜ํผ, ์ผ๋ถ€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” zoneless ๋ชจ๋“œ์—์„œ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜์— zoneless๋ฅผ ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ํ•ญ์ƒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ†ตํ•ฉ์„ ์ฒ ์ €ํžˆ ํ…Œ์ŠคํŠธํ•˜์‹ญ์‹œ์˜ค.

์ „ํ™˜์—์„œ ์‚ด์•„๋‚จ๋Š” NgZone API

์ผ๋ฐ˜์ ์ธ ์˜คํ•ด: Zone.js๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ๋ชจ๋“  NgZone ์ฐธ์กฐ๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค. NgZone.run()๊ณผ NgZone.runOutsideAngular()๋Š” zoneless ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ํ˜ธํ™˜๋˜๋ฉฐ ๊ณต์œ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋“ค์„ ์ œ๊ฑฐํ•˜๋ฉด ์—ฌ์ „ํžˆ Zone.js๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์„ธ ๊ฐ€์ง€ NgZone ์˜ต์ €๋ฒ„๋ธ”์€ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • NgZone.onMicrotaskEmpty - zoneless ๋ชจ๋“œ์—์„œ ๋ฐฉ์ถœ๋˜์ง€ ์•Š์Œ
  • NgZone.onUnstable - zoneless ๋ชจ๋“œ์—์„œ ๋ฐฉ์ถœ๋˜์ง€ ์•Š์Œ
  • NgZone.onStable - zoneless ๋ชจ๋“œ์—์„œ ๋ฐฉ์ถœ๋˜์ง€ ์•Š์Œ

์ด๋Ÿฌํ•œ ์˜ต์ €๋ฒ„๋ธ”์„ ์‚ฌ์šฉํ•œ ํƒ€์ด๋ฐ ์ข…์† ๋กœ์ง์„ @angular/core์˜ afterNextRender() ๋˜๋Š” afterEveryRender()๋กœ ๊ต์ฒดํ•˜์‹ญ์‹œ์˜ค.

Angular 19 ์‹คํ—˜์ ์—์„œ Angular 21 ๊ธฐ๋ณธ๊ฐ’๊นŒ์ง€

zoneless API ์ง„ํ™”๋Š” ๋ช…ํ™•ํ•œ ์•ˆ์ •ํ™” ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค:

  • Angular 18.1: provideExperimentalZonelessChangeDetection()์ด ์‹คํ—˜์ ์œผ๋กœ ๋„์ž…๋จ
  • Angular 19: ์‹คํ—˜์  API ๊ฐœ์„ , ๊ด‘๋ฒ”์œ„ํ•œ ์ƒํƒœ๊ณ„ ํ…Œ์ŠคํŠธ
  • Angular 20: provideZonelessChangeDetection()์œผ๋กœ ์ด๋ฆ„ ๋ณ€๊ฒฝ, ์•ˆ์ •ํ™”๋กœ ์Šน๊ฒฉ
  • Angular 20.2: API ์™„์ „ํžˆ ์•ˆ์ •ํ™”๋˜์–ด ๋™์ž‘ ๋ณ€๊ฒฝ ์˜ˆ์ƒ ์—†์Œ
  • Angular 21: zoneless๊ฐ€ ng new ํ”„๋กœ์ ํŠธ์˜ ๊ธฐ๋ณธ๊ฐ’์ด ๋˜๋ฉฐ ํ”„๋กœ๋ฐ”์ด๋” ํ˜ธ์ถœ ๋ถˆํ•„์š”

Angular 19๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŒ€์˜ ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฒฝ๋กœ๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค: Angular 20์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ณ , provideExperimentalZonelessChangeDetection์„ provideZonelessChangeDetection์œผ๋กœ ๊ต์ฒดํ•˜๊ณ , zone.js polyfill์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. Angular 19๋Š” 2026๋…„ 5์›” 19์ผ์— ์ง€์› ์ข…๋ฃŒ๋˜๋ฏ€๋กœ ์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์‹œ๊ฐ„์— ๋ฏผ๊ฐํ•ฉ๋‹ˆ๋‹ค.

Angular ๋ฉด์ ‘์„ ์ค€๋น„ํ•˜๊ณ  ๊ณ„์‹ ๊ฐ€์š”? SharpSkill์˜ Angular ๋ฉด์ ‘ ์งˆ๋ฌธ ๋ชจ๋“ˆ์€ ๋ฉด์ ‘๊ด€์ด ์ ์  ๋” ๋ฌป๋Š” zoneless ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํฌํ•จํ•œ ๋ณ€๊ฒฝ ๊ฐ์ง€ ํŒจํ„ด์„ ์‹ฌ๋„ ์žˆ๊ฒŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ๋” ๊ด‘๋ฒ”์œ„ํ•œ ๊ฐœ์š”๋Š” Angular ๋ฉด์ ‘ ์งˆ๋ฌธ ์ƒ์œ„ 25๊ฐœ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค. Angular ์‹œ๊ทธ๋„ ๋ชจ๋“ˆ์€ zoneless๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐ˜์‘์„ฑ ๋ชจ๋ธ๋„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

  • Zone.js๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด 33KB์˜ ๋ฒˆ๋“ค ๋ฌด๊ฒŒ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  130๊ฐœ ์ด์ƒ์˜ ๋ธŒ๋ผ์šฐ์ € API๋ฅผ ๊ฐ€๋กœ์ฑ„๋˜ ๋ชฝํ‚ค ํŒจ์นญ ๋ ˆ์ด์–ด๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
  • Angular ์‹œ๊ทธ๋„์€ zoneless๋ฅผ ์‹ค์šฉ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ช…์‹œ์  ๋ฐ˜์‘์„ฑ ๋ชจ๋ธ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํ…œํ”Œ๋ฆฟ์— ์ž๋™์œผ๋กœ ์•Œ๋ฆฝ๋‹ˆ๋‹ค
  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ setTimeout/setInterval ํŒจํ„ด, reactive forms, ํƒ€์ด๋ฐ ์ข…์† ๋กœ์ง์„ ์‹œ๊ทธ๋„ ๊ธฐ๋ฐ˜ ๋˜๋Š” markForCheck() ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • SSR ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Zone.js ์•ˆ์ •์„ฑ ๊ฐ์ง€๋ฅผ ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•ด PendingTasks๋ฅผ ์ฑ„ํƒํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • NgZone.run()๊ณผ NgZone.runOutsideAngular()๋Š” ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ๊ณต์œ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋ณด์กด๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • Angular 19์˜ ์‹คํ—˜์  API(provideExperimentalZonelessChangeDetection)๋Š” Angular 20์˜ ์•ˆ์ •ํ™”๋œ provideZonelessChangeDetection()์— ์ง์ ‘ ๋งคํ•‘๋˜์–ด ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์ด๋ฆ„ ๋ณ€๊ฒฝ ์ž‘์—…์ด ๋ฉ๋‹ˆ๋‹ค
  • ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ˜ธํ™˜์„ฑ์€ ์—ฌ์ „ํžˆ ์ฃผ์š” ์œ„ํ—˜ ์š”์†Œ์ด๋ฉฐ ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ „์— ์ฒ ์ €ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#angular
#zoneless
#change-detection
#performance
#signals
#zone-js

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Angular @defer ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ ๊ฐ€์ด๋“œ

Angular @defer ์™„์ „ ๊ฐ€์ด๋“œ 2026: ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ๋ฉด์ ‘ ์งˆ๋ฌธ

Angular @defer ๋ธ”๋ก์„ ํ™œ์šฉํ•œ ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ์˜ ์ž‘๋™ ์›๋ฆฌ, ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•, ํ”„๋ฆฌํŽ˜์น˜ ์ „๋žต, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜, ๋ฉด์ ‘ ์งˆ๋ฌธ์„ ์ƒ์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Angular 20 Resource API and httpResource tutorial

Angular 20 Resource API ํ•ต์‹ฌ ์ •๋ฆฌ: httpResource, rxResource ํ™œ์šฉ๋ฒ•๊ณผ ์‹ค์ „ ๋ฉด์ ‘ ๋Œ€๋น„

Angular 20์—์„œ ๋„์ž…๋œ Resource API์˜ ํ•ต์‹ฌ ๊ฐœ๋…์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. httpResource์™€ rxResource๋ฅผ ํ™œ์šฉํ•œ ์‹œ๊ทธ๋„ ๊ธฐ๋ฐ˜ ๋ฐ์ดํ„ฐ ํŽ˜์นญ, Zod ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ, ResourceStatus ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด, HttpClient ๊ตฌ๋… ํŒจํ„ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „ ๊ธฐ์ˆ  ๋ฉด์ ‘ ์งˆ๋ฌธ๊นŒ์ง€ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ์ƒ์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, SSR, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜

Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, SSR๊ณผ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๊ฐœ๋…

๊ฐ€์žฅ ์ž์ฃผ ๋‚˜์˜ค๋Š” Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜, zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€, ์ƒˆ๋กœ์šด ๋ฆฌ์•กํ‹ฐ๋ธŒ API. ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๊ธฐ๋Œ€๋˜๋Š” ๋‹ต๋ณ€๊นŒ์ง€ ํ•จ๊ป˜ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.