Let's say you want to implement a dropable area on some element, and you are using angular 2+.
Your directive for this may initially look following way:
import {
    Directive,
    EventEmitter,
    HostListener,
    Output
} from '@angular/core';

/**
 * Directive for tracking drag events on area
 */
@Directive({
    selector: '[dropable]'
})
export class DropableDirective {

    @Output() dropOn: EventEmitter<any> = new EventEmitter();
    @Output() dragOver: EventEmitter<any> = new EventEmitter();

    @HostListener('drop', ['$event'])
    onDrop($event: Event) {
        this.dropOn.emit(true);
        $event.preventDefault();
        return false;
    }

    @HostListener('dragend', ['$event'])
    onDragEnd($event: Event) {
        this.dragOver.emit(false);
        $event.preventDefault();
        return false;
    }

    @HostListener('dragover', ['$event'])
    onDragOver($event: Event) {
        $event.preventDefault();
        return false;
    }

    @HostListener('dragenter', ['$event'])
    onDragEnter() {
        this.dragOver.emit(true);
    }

    @HostListener('dragleave', ['$event'])
    onDragLeave() {
        this.dragOver.emit(false);
    }
}
When you try to apply directive with such implementation, you will see that "dropOn" and "dragOver" events will be triggered with some delay, and such thing will not allow your element to react fast enough.
For fixing event tracking issues we need to wrap event listeners bindings into "NgZone.runOutsideAngular" call in order to apply the bindings out of Angular change detection.
import {
    Directive,
    EventEmitter,
    Output,
    ElementRef,
    NgZone
} from '@angular/core';

/**
 * Directive for tracking drag events on area
 */
@Directive({
    selector: '[dropable]'
})
export class DropableDirective {

    @Output() dropOn: EventEmitter<any> = new EventEmitter();
    @Output() dragOver: EventEmitter<any> = new EventEmitter();

    constructor(
        private _el: ElementRef,
        private _zone: NgZone
    ) {}

    ngOnInit() {
        this._zone.runOutsideAngular(() => {
            this._el.nativeElement.addEventListener(
                'drop', this._onDrop.bind(this)
            );
            this._el.nativeElement.addEventListener(
                'dragend', this._onDragEnd.bind(this)
            );
            this._el.nativeElement.addEventListener(
                'dragover', this._onDragOver.bind(this)
            );
            this._el.nativeElement.addEventListener(
                'dragenter', this._onDragEnter.bind(this)
            );
            this._el.nativeElement.addEventListener(
                'dragleave', this._onDragLeave.bind(this)
            );
        });
    }

    private _onDrop($event: Event) {
        this.dropOn.emit();
        $event.preventDefault();
        return false;
    }

    private _onDragEnd($event: Event) {
        this.dragOver.emit(false);
        $event.preventDefault();
        return false;
    }

    private _onDragOver($event: Event) {
        $event.preventDefault();
        return false;
    }

    private _onDragEnter() {
        this.dragOver.emit(true);
    }

    private _onDragLeave() {
         this.dragOver.emit(false);
    }
}
With such implementation drag and drop will work fast. This directive can be applied for file upload purposes. For more complex drag and drop cases it is better use more advanced ngDragular library.