Skip to content

Commit 5739d86

Browse files
edcarrollmcosta74
authored andcommitted
fix: Manually added all document listeners to avoid performance issues (edcarroll#262)
angular/angular#11857
1 parent 8085f4f commit 5739d86

File tree

13 files changed

+68
-46
lines changed

13 files changed

+68
-46
lines changed

src/modules/datepicker/views/calendar-view.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Input, QueryList, ViewChildren, AfterViewInit, HostListener } from "@angular/core";
1+
import { Input, QueryList, ViewChildren, AfterViewInit, HostListener, Renderer2, OnDestroy } from "@angular/core";
22
import { KeyCode } from "../../../misc/util/index";
33
import { CalendarItem, SuiCalendarItem } from "../directives/calendar-item";
44
import { CalendarService } from "../services/calendar.service";
@@ -13,7 +13,7 @@ export enum CalendarViewType {
1313
}
1414
export type CalendarViewResult = [Date, CalendarViewType];
1515

16-
export abstract class CalendarView implements AfterViewInit {
16+
export abstract class CalendarView implements AfterViewInit, OnDestroy {
1717
private _type:CalendarViewType;
1818
private _service:CalendarService;
1919

@@ -48,9 +48,13 @@ export abstract class CalendarView implements AfterViewInit {
4848
return this.service.selectedDate;
4949
}
5050

51-
constructor(viewType:CalendarViewType, ranges:CalendarRangeService) {
51+
private _documentKeyDownListener:() => void;
52+
53+
constructor(renderer:Renderer2, viewType:CalendarViewType, ranges:CalendarRangeService) {
5254
this._type = viewType;
5355
this.ranges = ranges;
56+
57+
this._documentKeyDownListener = renderer.listen("document", "keydown", (e:KeyboardEvent) => this.onDocumentKeyDown(e));
5458
}
5559

5660
// Template Methods
@@ -107,8 +111,7 @@ export abstract class CalendarView implements AfterViewInit {
107111
}
108112
}
109113

110-
@HostListener("document:keydown", ["$event"])
111-
private onDocumentKeydown(e:KeyboardEvent):void {
114+
private onDocumentKeyDown(e:KeyboardEvent):void {
112115
if (this._highlightedItem && e.keyCode === KeyCode.Enter) {
113116
this.setDate(this._highlightedItem);
114117
return;
@@ -181,4 +184,8 @@ export abstract class CalendarView implements AfterViewInit {
181184
this.ranges.move(isMovingForward);
182185
this._highlightedItem = this.ranges.current.find(nextItem);
183186
}
187+
188+
public ngOnDestroy():void {
189+
this._documentKeyDownListener();
190+
}
184191
}

src/modules/datepicker/views/date-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@angular/core";
1+
import { Component, Renderer2 } from "@angular/core";
22
import { DateUtil, DatePrecision } from "../../../misc/util/index";
33
import { CalendarItem } from "../directives/calendar-item";
44
import { CalendarView, CalendarViewType } from "./calendar-view";
@@ -57,7 +57,7 @@ export class SuiCalendarDateView extends CalendarView {
5757
return new DateParser(this.service.localeValues.formats.month, this.service.localeValues).format(this.currentDate);
5858
}
5959

60-
constructor() {
61-
super(CalendarViewType.Date, new CalendarRangeDateService(DatePrecision.Month, 6, 7));
60+
constructor(renderer:Renderer2) {
61+
super(renderer, CalendarViewType.Date, new CalendarRangeDateService(DatePrecision.Month, 6, 7));
6262
}
6363
}

src/modules/datepicker/views/hour-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@angular/core";
1+
import { Component, Renderer2 } from "@angular/core";
22
import { DatePrecision } from "../../../misc/util/index";
33
import { CalendarView, CalendarViewType } from "./calendar-view";
44
import { CalendarItem } from "../directives/calendar-item";
@@ -45,7 +45,7 @@ export class SuiCalendarHourView extends CalendarView {
4545
return new DateParser(this.service.localeValues.formats.date, this.service.localeValues).format(this.currentDate);
4646
}
4747

48-
constructor() {
49-
super(CalendarViewType.Hour, new CalendarRangeHourService(DatePrecision.Date, 6, 4));
48+
constructor(renderer:Renderer2) {
49+
super(renderer, CalendarViewType.Hour, new CalendarRangeHourService(DatePrecision.Date, 6, 4));
5050
}
5151
}

src/modules/datepicker/views/minute-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@angular/core";
1+
import { Component, Renderer2 } from "@angular/core";
22
import { Util, DateUtil, DatePrecision } from "../../../misc/util/index";
33
import { CalendarView, CalendarViewType } from "./calendar-view";
44
import { CalendarItem } from "../directives/calendar-item";
@@ -61,7 +61,7 @@ export class SuiCalendarMinuteView extends CalendarView {
6161
}
6262
}
6363

64-
constructor() {
65-
super(CalendarViewType.Minute, new CalendarRangeMinuteService(DatePrecision.Hour, 4, 3));
64+
constructor(renderer:Renderer2) {
65+
super(renderer, CalendarViewType.Minute, new CalendarRangeMinuteService(DatePrecision.Hour, 4, 3));
6666
}
6767
}

src/modules/datepicker/views/month-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@angular/core";
1+
import { Component, Renderer2 } from "@angular/core";
22
import { DatePrecision } from "../../../misc/util/index";
33
import { CalendarView, CalendarViewType } from "./calendar-view";
44
import { CalendarItem } from "../directives/calendar-item";
@@ -42,7 +42,7 @@ export class SuiCalendarMonthView extends CalendarView {
4242
return new DateParser(this.service.localeValues.formats.year, this.service.localeValues).format(this.currentDate);
4343
}
4444

45-
constructor() {
46-
super(CalendarViewType.Month, new CalendarRangeMonthService(DatePrecision.Year, 4, 3));
45+
constructor(renderer:Renderer2) {
46+
super(renderer, CalendarViewType.Month, new CalendarRangeMonthService(DatePrecision.Year, 4, 3));
4747
}
4848
}

src/modules/datepicker/views/year-view.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@angular/core";
1+
import { Component, Renderer2 } from "@angular/core";
22
import { Util, DateUtil, DatePrecision } from "../../../misc/util/index";
33
import { CalendarView, CalendarViewType } from "./calendar-view";
44
import { CalendarItem } from "../directives/calendar-item";
@@ -43,8 +43,8 @@ export class SuiCalendarYearView extends CalendarView {
4343
.getFullYear();
4444
}
4545

46-
constructor() {
47-
super(CalendarViewType.Year, new CalendarRangeYearService(DatePrecision.Decade, 4, 3));
46+
constructor(renderer:Renderer2) {
47+
super(renderer, CalendarViewType.Year, new CalendarRangeYearService(DatePrecision.Decade, 4, 3));
4848
}
4949

5050
public pad(year:number):string {

src/modules/dropdown/directives/dropdown-menu.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
Directive, ContentChild, forwardRef, Renderer2, ElementRef, AfterContentInit,
3-
ContentChildren, QueryList, Input, HostListener, ChangeDetectorRef
3+
ContentChildren, QueryList, Input, HostListener, ChangeDetectorRef, OnDestroy
44
} from "@angular/core";
55
import { Transition, SuiTransition, TransitionController, TransitionDirection } from "../../transition/index";
66
import { HandledEvent, IAugmentedElement, KeyCode } from "../../../misc/util/index";
@@ -59,7 +59,7 @@ export class SuiDropdownMenuItem {
5959
@Directive({
6060
selector: "[suiDropdownMenu]"
6161
})
62-
export class SuiDropdownMenu extends SuiTransition implements AfterContentInit {
62+
export class SuiDropdownMenu extends SuiTransition implements AfterContentInit, OnDestroy {
6363
private _service:DropdownService;
6464
private _transitionController:TransitionController;
6565

@@ -129,6 +129,8 @@ export class SuiDropdownMenu extends SuiTransition implements AfterContentInit {
129129
@Input()
130130
public menuSelectedItemClass:string;
131131

132+
private _documentKeyDownListener:() => void;
133+
132134
constructor(renderer:Renderer2, public element:ElementRef, changeDetector:ChangeDetectorRef) {
133135
super(renderer, element, changeDetector);
134136

@@ -141,6 +143,8 @@ export class SuiDropdownMenu extends SuiTransition implements AfterContentInit {
141143

142144
this.menuAutoSelectFirst = false;
143145
this.menuSelectedItemClass = "selected";
146+
147+
this._documentKeyDownListener = renderer.listen("document", "keydown", (e:KeyboardEvent) => this.onDocumentKeyDown(e));
144148
}
145149

146150
@HostListener("click", ["$event"])
@@ -158,8 +162,7 @@ export class SuiDropdownMenu extends SuiTransition implements AfterContentInit {
158162
}
159163
}
160164

161-
@HostListener("document:keydown", ["$event"])
162-
public onDocumentKeydown(e:KeyboardEvent):void {
165+
public onDocumentKeyDown(e:KeyboardEvent):void {
163166
// Only the root dropdown (i.e. not nested dropdowns) is responsible for keeping track of the currently selected item.
164167
if (this._service.isOpen && !this._service.isNested) {
165168
// Stop document events like scrolling while open.
@@ -312,4 +315,8 @@ export class SuiDropdownMenu extends SuiTransition implements AfterContentInit {
312315
// We use `_items` rather than `items` in case one or more have become disabled.
313316
this.resetSelection();
314317
}
318+
319+
public ngOnDestroy():void {
320+
this._documentKeyDownListener();
321+
}
315322
}

src/modules/modal/components/modal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,9 @@ export class SuiModal<T, U> implements OnInit, AfterViewInit {
288288
e.stopPropagation();
289289
}
290290

291+
// Document listener is fine here because nobody will enough modals open.
291292
@HostListener("document:keyup", ["$event"])
292-
public onDocumentKeyup(e:KeyboardEvent):void {
293+
public onDocumentKeyUp(e:KeyboardEvent):void {
293294
if (e.keyCode === KeyCode.Escape) {
294295
// Close automatically covers case of `!isClosable`, so check not needed.
295296
this.close();

src/modules/popup/classes/popup-controller.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export abstract class SuiPopupController implements IPopup, OnDestroy {
2626
// Function to remove the document click handler.
2727
private _documentListener:() => void;
2828

29-
constructor(private _renderer:Renderer2,
29+
constructor(renderer:Renderer2,
3030
protected _element:ElementRef,
3131
protected _componentFactory:SuiComponentFactory,
3232
config:PopupConfig) {
@@ -39,6 +39,8 @@ export abstract class SuiPopupController implements IPopup, OnDestroy {
3939

4040
// When the popup is closed (onClose fires on animation complete),
4141
this.popup.onClose.subscribe(() => this.cleanup());
42+
43+
this._documentListener = renderer.listen("document", "click", (e:MouseEvent) => this.onDocumentClick(e));
4244
}
4345

4446
public configure(config?:IPopupConfig):void {
@@ -65,8 +67,6 @@ export abstract class SuiPopupController implements IPopup, OnDestroy {
6567
// Attach a reference to the anchor element. We do it here because IE11 loves to complain.
6668
this.popup.anchor = this._element;
6769

68-
this._documentListener = this._renderer.listen("document", "click", (e:MouseEvent) => this.onDocumentClick(e));
69-
7070
// Start popup open transition.
7171
this.popup.open();
7272

@@ -177,14 +177,11 @@ export abstract class SuiPopupController implements IPopup, OnDestroy {
177177
}
178178

179179
this._componentFactory.detachFromApplication(this._componentRef);
180-
181-
if (this._documentListener) {
182-
// Remove the document click handler
183-
this._documentListener();
184-
}
185180
}
186181

187182
public ngOnDestroy():void {
188183
this.cleanup();
184+
185+
this._documentListener();
189186
}
190187
}

src/modules/search/components/search.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
Component, ViewChild, HostBinding, Input, AfterViewInit, HostListener,
3-
EventEmitter, Output, Directive, ElementRef, TemplateRef
3+
EventEmitter, Output, Directive, ElementRef, TemplateRef, Renderer2, OnDestroy
44
} from "@angular/core";
55
import { Util, ITemplateRefContext, IFocusEvent } from "../../../misc/util/index";
66
import { DropdownService, SuiDropdownMenu } from "../../dropdown/index";
@@ -51,7 +51,7 @@ export interface IResultContext<T> extends ITemplateRefContext<T> {
5151
}
5252
`]
5353
})
54-
export class SuiSearch<T> implements AfterViewInit {
54+
export class SuiSearch<T> implements AfterViewInit, OnDestroy {
5555
public dropdownService:DropdownService;
5656
public searchService:SearchService<T, T>;
5757

@@ -182,7 +182,9 @@ export class SuiSearch<T> implements AfterViewInit {
182182
@Input()
183183
public transitionDuration:number;
184184

185-
constructor(private _element:ElementRef, private _localizationService:SuiLocalizationService) {
185+
private _documentClickListener:() => void;
186+
187+
constructor(private _element:ElementRef, renderer:Renderer2, private _localizationService:SuiLocalizationService) {
186188
this.dropdownService = new DropdownService();
187189
this.searchService = new SearchService<T, T>();
188190

@@ -199,6 +201,8 @@ export class SuiSearch<T> implements AfterViewInit {
199201

200202
this.transition = "scale";
201203
this.transitionDuration = 200;
204+
205+
this._documentClickListener = renderer.listen("document", "click", (e:MouseEvent) => this.onDocumentClick(e));
202206
}
203207

204208
public ngAfterViewInit():void {
@@ -247,7 +251,6 @@ export class SuiSearch<T> implements AfterViewInit {
247251
}
248252
}
249253

250-
@HostListener("document:click", ["$event"])
251254
public onDocumentClick(e:MouseEvent):void {
252255
if (!this._element.nativeElement.contains(e.target)) {
253256
this.dropdownService.setOpenState(false);
@@ -258,4 +261,8 @@ export class SuiSearch<T> implements AfterViewInit {
258261
public readValue(object:T):string {
259262
return Util.Object.readValue<T, string>(object, this.searchService.optionsField);
260263
}
264+
265+
public ngOnDestroy():void {
266+
this._documentClickListener();
267+
}
261268
}

0 commit comments

Comments
 (0)