동적으로 이벤트 리스너 추가


143

방금 Angular 2를 엉망으로 만들기 시작했으며 누군가 요소에서 이벤트 리스너를 동적으로 추가하고 제거하는 가장 좋은 방법을 말해 줄 수 있는지 궁금합니다.

구성 요소를 설정했습니다. 템플릿의 특정 요소를 클릭하면 mousemove동일한 템플릿의 다른 요소에 리스너를 추가하고 싶습니다 . 그런 다음 세 번째 요소를 클릭하면이 리스너를 제거하고 싶습니다.

나는 보통 Javascript를 사용하여 요소를 잡고 표준을 호출하는 방식 으로이 작업을 수행 했지만addEventListener() 더 많은 " Angular2.0 "방법 으로이 작업을 수행 해야하는지 궁금해 했습니다.

답변:


262

렌더러는 Angular 4.0.0-rc.1에서 더 이상 사용되지 않습니다. 아래 업데이트를 읽으십시오

angular2 방법은 사용하는 것 listen또는 listenGlobal에서 렌더러

예를 들어 클릭 이벤트를 구성 요소에 추가하려면 렌더러 및 ElementRef를 사용해야합니다 (이렇게하면 ViewChild를 사용하는 옵션 또는을 검색하는 모든 항목이 제공됨 nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

    // Listen to click events in the component
    renderer.listen(elementRef.nativeElement, 'click', (event) => {
      // Do something with 'event'
    })
);

당신은 사용할 수 있습니다 listenGlobal당신에게 액세스 권한을 부여 할 것을 document, body

renderer.listenGlobal('document', 'click', (event) => {
  // Do something with 'event'
});

beta.2 이후 모두 참고 listen하고 listenGlobal(참조 리스너를 제거하는 기능을 반환 변경 깨는 beta.2에 대한 변경 로그에서 절). 이는 큰 응용 프로그램에서 메모리 누수를 방지하기위한 것입니다 ( # 6686 참조 ).

따라서 동적으로 추가 한 리스너를 제거하려면 반환 된 함수를 보유 할 변수 listen또는 listenGlobal변수를 할당 한 다음 실행해야합니다.

// listenFunc will hold the function returned by "renderer.listen"
listenFunc: Function;

// globalListenFunc will hold the function returned by "renderer.listenGlobal"
globalListenFunc: Function;

constructor(elementRef: ElementRef, renderer: Renderer) {
    
    // We cache the function "listen" returns
    this.listenFunc = renderer.listen(elementRef.nativeElement, 'click', (event) => {
        // Do something with 'event'
    });

    // We cache the function "listenGlobal" returns
    this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
        // Do something with 'event'
    });
}

ngOnDestroy() {
    // We execute both functions to remove the respectives listeners

    // Removes "listen" listener
    this.listenFunc();
    
    // Removs "listenGlobal" listener
    this.globalListenFunc();
}

다음 은 예제가 작동 하는 plnkr 입니다. 이 예는의 사용을 포함 listen하고 listenGlobal.

Angular 4.0.0-rc.1 +와 함께 RendererV2 사용 (4.0.0-rc.3 이후의 Renderer2)

  • 25/02/2017 : Renderer더 이상 사용되지 않으므로 이제 사용해야합니다 RendererV2(아래 줄 참조). 커밋을 참조하십시오 .

  • 10/03/2017 : RendererV2로 이름이 변경되었습니다 Renderer2. 주요 변경 사항을 참조하십시오 .

RendererV2listenGlobal글로벌 이벤트 (문서, 본문, 창) 에는 더 이상 기능 이 없습니다 . 그것은 listen두 기능을 모두 달성 하는 기능 만을 가지고 있습니다.

참고로 DOM 렌더러 구현 의 소스 코드 를 복사하여 붙여 넣을 수 있습니다 (예 : 각도입니다).

listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
      () => void {
    if (typeof target === 'string') {
      return <() => void>this.eventManager.addGlobalEventListener(
          target, event, decoratePreventDefault(callback));
    }
    return <() => void>this.eventManager.addEventListener(
               target, event, decoratePreventDefault(callback)) as() => void;
  }

보시다시피 이제 문자열 (문서, 본문 또는 창)을 전달하는지 확인합니다.이 경우 내부 addGlobalEventListener함수를 사용합니다 . 다른 경우에는 요소 (nativeElement)를 전달하면 간단한addEventListener

리스너를 제거하려면 Renderer각도 2.x에서 와 동일합니다 . listen함수를 반환 한 다음 해당 함수를 호출합니다.

// Add listeners
let global = this.renderer.listen('document', 'click', (evt) => {
  console.log('Clicking the document', evt);
})

let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
  console.log('Clicking the button', evt);
});

// Remove listeners
global();
simple();

plnkr각도 4.0.0-rc.1 사용 RendererV2을

plnkr각도 4.0.0-rc.3 사용 Renderer2을


이것은 Angular2와 함께하는 두 번째 날이며 v1 주변에서 머리를 겨우 시작하기 때문에 많은 혼란이 있습니다. 당신은 나에게 읽을 수있는 많은 물건을 주었 으므로이 책을 닫고 의심 할 여지없이 LOTS 더 많은 관련 질문으로 돌아올 것입니다. 자세한 응답을 위해 건배 :)
popClingwrap

3
당신이 확인할 수 있습니다 @popClingwrap HostListener을 잘한다. 문서에서 사용자 에게 응답 조치 아래 의 속성 지시문을 확인하여 어떻게 사용 되는지 확인하십시오 . host
Eric Martinez

@EricMartinez는 Listen 또는 ListenGlobal의 청취를 중지하는 방법이 있습니까? (removeEventListener와 동일)
Nik

3
@ user1394625 그래, 당신이 대답에서 볼 수 있듯이 ngOnDestroy코드를 모두 listenlistenGlobal호출시 / 제거 리스너를 실행하는 기능을 반환합니다. 보시 this.func다시피 반환 된 함수를 보유하고 renderer.listen있으며 내가 할 때 this.func()리스너를 제거하고 있습니다. 동일합니다 listenGlobal.
Eric Martinez

@EricMartinez는 당신에게 하나 더 질문을했습니다 ... 어떻게 함수 내부의 '이벤트'에 액세스하여 preventDefault () 또는 stopPropagation ()을 막을 수 있습니까?
Nik

5

나는 이것을 매우 혼란스럽게 생각합니다. @EricMartinez가 지적한대로 Renderer2 listen ()은 리스너를 제거하는 함수를 반환합니다.

ƒ () { return element.removeEventListener(eventName, /** @type {?} */ (handler), false); }

리스너를 추가하는 경우

this.listenToClick = this.renderer.listen('document', 'click', (evt) => {
    alert('Clicking the document');
})

나는 내 함수가 의도 한 것을 실행하고 리스너를 제거하는 전체 반대가 아니라고 기대합니다.

// I´d expect an alert('Clicking the document'); 
this.listenToClick();
// what you actually get is removing the listener, so nothing...

주어진 시나리오에서 실제로 다음과 같이 이름을 지정하는 것이 더 합리적입니다.

// Add listeners
let unlistenGlobal = this.renderer.listen('document', 'click', (evt) => {
    console.log('Clicking the document', evt);
})

let removeSimple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
    console.log('Clicking the button', evt);
});

이것에는 좋은 이유가 있어야하지만 내 의견으로는 그것은 매우 오도적이고 직관적이지 않습니다.


3
리스너를 추가하는 경우 해당 리스너를 추가하여 리턴 된 함수가 해당 리스너를 호출 할 것으로 예상하는 이유는 무엇입니까? 그것은 나에게별로 이해가되지 않습니다. 리스너를 추가하는 요점은 반드시 프로그래밍 방식으로 트리거 할 수없는 이벤트에 응답하는 것입니다. 이 함수가 리스너를 호출 할 것으로 예상하면 리스너를 완전히 이해하지 못할 수도 있습니다.
Willwsharp

@ tahiche mate 이것은 정말 혼란 스럽습니다.이 점을 지적 해 주셔서 감사합니다!
godblessstrawberry

나중에 반환 할 때 리스너를 다시 제거 할 수 있도록 이것을 반환합니다. 리스너를 추가 할 때 나중에 더 이상 필요하지 않을 때는 리스너를 제거하는 것이 좋습니다. 따라서이 반환 값을 저장하고 ngOnDestroy메서드 내에서 호출하십시오 . 처음에는 혼란스러워 보일 수도 있지만 실제로는 매우 유용한 기능입니다. 자신을 어떻게 정리하면 되나요?
윌트

1

StackBlitz 예제@tahiche 의 답변에 주석을 추가 하겠습니다.

리턴 값은 이벤트 리스너를 추가 한 후 제거하는 기능입니다. 더 이상 필요하지 않은 이벤트 리스너를 제거하는 것이 좋습니다. 이 반환 값을 저장하고ngOnDestroy 메서드 .

처음에는 혼란스러워 보일 수도 있지만 실제로는 매우 유용한 기능입니다. 자신을 어떻게 쫓아 낼 수 있습니까?

export class MyComponent implements OnInit, OnDestroy {

  public removeEventListener: () => void;

  constructor(
    private renderer: Renderer2, 
    private elementRef: ElementRef
  ) {
  }

  public ngOnInit() {
    this.removeEventListener = this.renderer.listen(this.elementRef.nativeElement, 'click', (event) => {
      if (event.target instanceof HTMLAnchorElement) {
        // Prevent opening anchors the default way
        event.preventDefault();
        // Your custom anchor click event handler
        this.handleAnchorClick(event);
      }
    });
  }

  public ngOnDestroy() {
    this.removeEventListener();
  }
}

여기 에서 StackBlitz 를 찾아 앵커 요소를 클릭 할 때 이것이 어떻게 작동하는지 알 수 있습니다 .

나는
<img src="x" onerror="alert(1)"></div>
소독제가 그 일을하고 있음을 보여주기 위해 이미지가있는 몸을 추가했습니다 .

이 바이올린에서 당신은 innerHTML그것을 위생하지 않고 붙어있는 같은 몸을 발견 하고 문제를 보여줄 것입니다.


0

내 해결 방법은 다음과 같습니다.

Angular 6을 사용하여 라이브러리를 만들었습니다 commonlib-header. 외부 응용 프로그램에서 이와 같이 사용되는 공통 구성 요소 를 추가했습니다 .

참고 serviceReference(컴포넌트 클래스에 주입되는 constructor(public serviceReference: MyService)사용 commonlib-header보류를) stringFunctionName방법 :

<commonlib-header
    [logo]="{ src: 'assets/img/logo.svg', alt: 'Logo', href: '#' }"
    [buttons]="[{ index: 0, innerHtml: 'Button', class: 'btn btn-primary', onClick: [serviceReference, 'stringFunctionName', ['arg1','arg2','arg3']] }]">
    </common-header>

라이브러리 구성 요소는 이와 같이 프로그래밍됩니다. 동적 이벤트는 다음 onClick(fn: any)메소드에 추가됩니다 .

export class HeaderComponent implements OnInit {

 _buttons: Array<NavItem> = []

 @Input()
  set buttons(buttons: Array<any>) {
    buttons.forEach(navItem => {
      let _navItem = new NavItem(navItem.href, navItem.innerHtml)

      _navItem.class = navItem.class

      _navItem.onClick = navItem.onClick // this is the array from the component @Input properties above

      this._buttons[navItem.index] = _navItem
    })
  }

  constructor() {}

  ngOnInit() {}

  onClick(fn: any){
    let ref = fn[0]
    let fnName = fn[1]
    let args = fn[2]

    ref[fnName].apply(ref, args)
  }

재사용 가능 header.component.html:

<div class="topbar-right">
  <button *ngFor="let btn of _buttons"
    class="{{ btn.class }}"
    (click)="onClick(btn.onClick)"
    [innerHTML]="btn.innerHtml | keepHtml"></button>
</div>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.