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!');
}
}