심화 가이드
템플릿

이벤트 리스너 추가하기

Angular를 활용하면 템플릿에 있는 엘리먼트에서 발생하는 이벤트를 감지하고 이벤트가 발생할 때마다 실행되는 이벤트 리스너를 정의해서 연결할 수 있습니다.

기본 이벤트 감지하기

HTML 엘리먼트에서 발생하는 이벤트를 감지하려면, 이벤트 이름을 괄호((, ))로 감싸고 리스너 실행문을 연결하면 됩니다.

@Component({
  template: `
    <input type="text" (keyup)="updateField()" />
  `,
  ...
})
export class App{
  updateField(): void {
    console.log('Field is updated!');
  }
}

위 코드처럼 구현하면 <input> 엘리먼트에서 keyup 이벤트가 발생할 때마다 Angular는 updateField() 함수를 실행합니다.

click, keydown, mouseover 등 HTML이 기본으로 지원하는 이벤트는 이렇게 간단하게 이벤트 리스너와 연결할 수 있습니다. 더 자세한 내용은 엘리먼트에서 발생하는 이벤트 목록 문서를 참고하세요.

이벤트 객체 접근하기

템플릿에서 이벤트 리스너를 연결할 때 Angular가 제공하는 $event 변수를 사용하면 이벤트 객체를 참조할 수 있습니다.

@Component({
  template: `
    <input type="text" (keyup)="updateField($event)" />
  `,
  ...
})
export class App {
  updateField(event: KeyboardEvent): void {
    console.log(`The user pressed: ${event.key}`);
  }
}

특정 키에만 반응하기

키보드 이벤트 중 특정 키를 감지해야 할 때는 다음과 같이 구현하면 됩니다:

@Component({
  template: `
    <input type="text" (keyup)="updateField($event)" />
  `,
  ...
})
export class App {
  updateField(event: KeyboardEvent): void {
    if (event.key === 'Enter') {
      console.log('The user pressed enter in the text field.');
    }
  }
}

이 방식도 좋지만, Angular는 키보드 이벤트 이름에 마침표(.)를 붙인 후 원하는 키만 감지할 수도 있습니다. 그러면 코드가 더 간단해집니다:

@Component({
  template: `
    <input type="text" (keyup.enter)="updateField($event)" />
  `,
  ...
})
export class App{
  updateField(event: KeyboardEvent): void {
    console.log('The user pressed enter in the text field.');
  }
}

여러 키를 조합하는 경우는 이렇게 구현합니다:

<!-- shift 키와 엔터키 조합과 매칭됩니다. -->
<input type="text" (keyup.shift.enter)="updateField($event)" />

Angular는 alt, control, meta, shift 키조합도 지원합니다.

키보드 이벤트는 키 이름이나 코드를 사용해서 바인딩할 수 있습니다. 키 값이나 코드 값은 브라우저 키보드 이벤트 객체를 기반으로 하며, 키보드 이벤트의 키 값을 따른다고 가정합니다.

Angular에서는 code 접미사를 사용해서 키보드 이벤트의 코드 값을 활용할 수도 있습니다.

<!-- alt 키와 왼쪽 shift 키 조합과 매칭됩니다. -->
<input type="text" (keydown.code.alt.shift.shiftleft)="updateField($event)" />

이 방식은 운영체제와 관계없이 키보드 이벤트를 다뤄야 할 때 유용합니다. 예를 들면, MacOS 장치에서 Alt 키는 기능키가 아니라 다른 문자셋을 입력하는 것으로 동작합니다. 그래서 Alt + S 라고 지정하면 'ß' 문자를 감지합니다. 하지만 code 프로퍼티를 사용하는 경우에는 문자셋과 관계없이 실제로 눌린 키조합에 반응합니다.

전역 이벤트 감지하기

전역 이벤트는 이벤트 이름 앞에 접두사를 붙여서 감지할 수 있습니다. 지원하는 전역 대상은 window, document, body 입니다.

@Component({
  /* ... */
  host: {
    '(window:click)': 'onWindowClick()',
    '(document:click)': 'onDocumentClick()',
    '(body:click)': 'onBodyClick()',
  },
})
export class MyView {}

이벤트의 기본 동작 막기

이벤트 핸들러를 실행하면서 브라우저의 기본 동작을 중단해야 한다면 이벤트 객체의 preventDefault 메서드를 실행하면 됩니다:

@Component({
  template: `
    <a href="#overlay" (click)="showOverlay($event)">
  `,
  ...
})
export class App{
  showOverlay(event: PointerEvent): void {
    event.preventDefault();
    console.log('Show overlay without updating the URL!');
  }
}

Angular에서는 기본 이벤트 핸들러 어트리뷰트와 비슷하게, 이벤트 핸들러 실행문이 false로 평가되면 preventDefault()를 자동으로 실행합니다. preventDefault() 실행이 필요한 경우라면 반드시 명시적으로 이 메서드를 실행하세요. 그래야 코드의 의도가 명확해집니다.

이벤트 핸들링 확장하기

Angular 이벤트 처리 시스템은 커스텀 이벤트 플러그인을 EVENT_MANAGER_PLUGINS 의존성 토큰으로 등록해서 확장할 수 있습니다.

이벤트 플러그인 구현하기

커스텀 이벤트 플러그인을 만들려면 EventManagerPlugin 클래스를 상속받아 필수 메서드를 구현하면 됩니다.

import {Injectable} from '@angular/core';
import {EventManagerPlugin} from '@angular/platform-browser';

@Injectable()
export class DebounceEventPlugin extends EventManagerPlugin {
  constructor() {
    super(document);
  }

  // 플러그인이 처리할 이벤트를 지정합니다.
  override supports(eventName: string) {
    return /debounce/.test(eventName);
  }

  // 이벤트 리스너를 등록합니다.
  override addEventListener(element: HTMLElement, eventName: string, handler: Function) {
    // 이벤트를 파싱합니다:
    // "click.debounce.500" -> event: "click", delay: 500
    const [event, , delay = '300'] = eventName.split('.');

    let timeoutId: ReturnType<typeof setTimeout>;

    const listener = (e: Event) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        handler(e);
      }, Number(delay));
    };

    element.addEventListener(event, listener);

    // 정리 함수를 반환합니다.
    return () => {
      clearTimeout(timeoutId);
      element.removeEventListener(event, listener);
    };
  }
}

이후에는 애플리케이션의 프로바이더 목록에 EVENT_MANAGER_PLUGINS 토큰과 커스텀 플러그인을 등록하면 됩니다.

import {bootstrapApplication, EVENT_MANAGER_PLUGINS} from '@angular/platform-browser';
import {App} from './app';
import {DebounceEventPlugin} from './debounce-event-plugin';

bootstrapApplication(App, {
  providers: [
    {
      provide: EVENT_MANAGER_PLUGINS,
      useClass: DebounceEventPlugin,
      multi: true,
    },
  ],
});

이렇게 한 번 등록하고 나면, 템플릿에서 커스텀 이벤트 문법을 사용할 수 있으며, host 프로퍼티를 활용해도 됩니다:

@Component({
  template: `
    <input
      type="text"
      (input.debounce.500)="onSearch($event.target.value)"
      placeholder="Search..."
    />
  `,
  ...
})
export class Search {
 onSearch(query: string): void {
    console.log('Searching for:', query);
  }
}
@Component({
  ...,
  host: {
    '(click.debounce.500)': 'handleDebouncedClick()',
  },
})
export class AwesomeCard {
  handleDebouncedClick(): void {
   console.log('Debounced click!');
  }
}