Je commence tout juste à jouer avec Angular 2 et je me demande si quelqu'un peut me dire quel est le meilleur moyen d'ajouter et de supprimer de manière dynamique des écouteurs d'événements des éléments.
J'ai un composant mis en place. Lorsque je clique sur un élément du modèle, je souhaite ajouter un écouteur pour mousemove
à un autre élément du même modèle. Je veux ensuite supprimer cet écouteur quand un troisième élément est cliqué.
J'ai eu ce genre de travail en utilisant simplement du Javascript simple pour saisir les éléments, puis en appelant la addEventListener()
standard, mais je me suis demandé s'il y avait une manière plus " Angular2.0 " de le faire que je devrais étudier.
La manière angular2 consiste à utiliser listen
ou listenGlobal
à partir de Renderer
Par exemple, si vous souhaitez ajouter un événement click à un composant, vous devez utiliser Renderer et ElementRef (cela vous donne également la possibilité d'utiliser ViewChild, ou tout ce qui récupère la nativeElement
).
constructor(elementRef: ElementRef, renderer: Renderer) {
// Listen to click events in the component
renderer.listen(elementRef.nativeElement, 'click', (event) => {
// Do something with 'event'
})
);
Vous pouvez utiliser listenGlobal
qui vous donnera accès à document
, body
, etc.
renderer.listenGlobal('document', 'click', (event) => {
// Do something with 'event'
});
Notez que depuis la version bêta.2, les variables listen
et listenGlobal
renvoient une fonction permettant de supprimer l’auditeur (voir la section modifications importantes } de changelog pour la version bêta 2). Ceci permet d'éviter les fuites de mémoire dans les grandes applications (voir # 6686 ).
Donc, pour supprimer l'écouteur ajouté dynamiquement, nous devons affecter listen
ou listenGlobal
à une variable qui contiendra la fonction renvoyée, puis nous l'exécuterons.
// 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();
}
Voici un plnkr avec un exemple qui fonctionne. L'exemple contient l'utilisation de listen
et listenGlobal
.
25/02/2017: Renderer
est obsolète, nous devrions maintenant utiliser (voir ligne ci-dessous). Voir le commit .RendererV2
10/03/2017: RendererV2
a été renommé en Renderer2
. Voir le briser les changements .
_ { RendererV2
n'a plus de fonction listenGlobal
pour les événements globaux (document, corps, fenêtre). Il a seulement une fonction listen
qui réalise les deux fonctionnalités.
Pour référence, je copie/colle le code source de l'implémentation DOM Renderer car il peut changer (oui, c'est angulaire!).
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;
}
Comme vous pouvez le constater, il vérifie maintenant si nous transmettons une chaîne (document, corps ou fenêtre), auquel cas il utilisera une fonction interne addGlobalEventListener
. Dans tous les autres cas, lorsque nous passons un élément (nativeElement), il utilisera un simple addEventListener
Pour supprimer l'auditeur, il en va de même avec Renderer
dans angular 2.x. listen
renvoie une fonction, puis appelle cette fonction.
// 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 avec Angular 4.0.0-rc.1 en utilisant RendererV2
plnkr avec Angular 4.0.0-rc.3 à l'aide de Renderer2
J’ai également trouvé cela extrêmement déroutant…. Comme @EricMartinez le fait remarquer: Renderer2 listen () renvoie la fonction pour supprimer l’auditeur:
ƒ () { return element.removeEventListener(eventName, /** @type {?} */ (handler), false); }
Si j'ajoute un auditeur
this.listenToClick = this.renderer.listen('document', 'click', (evt) => {
alert('Clicking the document');
})
Je m'attendais à ce que ma fonction exécute ce que je voulais, pas le contraire qui consiste à supprimer l'auditeur.
// I´d expect an alert('Clicking the document');
this.listenToClick();
// what you actually get is removing the listener, so nothing...
Dans le scénario donné, il serait plus logique de le nommer ainsi:
// 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);
});
Il doit y avoir une bonne raison à cela, mais à mon avis, c'est très trompeur et pas intuitif.
Voici ma solution de contournement:
J'ai créé une bibliothèque avec Angular 6. J'ai ajouté un composant commun commonlib-header
qui est utilisé comme ceci dans une application externe.
Notez la serviceReference
qui est la classe (injectée dans le composant constructor(public serviceReference: MyService)
qui utilise le commonlib-header
) qui contient la méthode 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>
Le composant de bibliothèque est programmé comme ceci. L'événement dynamique est ajouté à la méthode 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)
}
Le header.component.html
réutilisable:
<div class="topbar-right">
<button *ngFor="let btn of _buttons"
class="{{ btn.class }}"
(click)="onClick(btn.onClick)"
[innerHTML]="btn.innerHtml | keepHtml"></button>
</div>