Skip to content

Commit 5fb821f

Browse files
authored
refactor(multiple): clean up host decorator workarounds (#23879)
We had some workarounds with `HostListener` and `HostBinding` which go against our conventions. They were used to support ViewEngine, but they are no longer necessary.
1 parent a6d4141 commit 5fb821f

File tree

27 files changed

+113
-359
lines changed

27 files changed

+113
-359
lines changed

src/cdk-experimental/dialog/dialog-container.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
ComponentRef,
2525
ElementRef,
2626
EmbeddedViewRef,
27-
HostBinding,
2827
Inject,
2928
NgZone,
3029
OnDestroy,
@@ -70,6 +69,11 @@ export function throwDialogContentAlreadyAttachedError() {
7069
}`,
7170
'(@dialog.start)': '_onAnimationStart($event)',
7271
'(@dialog.done)': '_animationDone.next($event)',
72+
'tabindex': '-1',
73+
'[attr.role]': '_config.role',
74+
'aria-modal': 'true',
75+
'[attr.aria-label]': '_config.ariaLabel || null',
76+
'[attr.aria-describedby]': '_config.ariaDescribedBy',
7377
},
7478
})
7579
export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
@@ -84,30 +88,6 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
8488
/** The class that traps and manages focus within the dialog. */
8589
private _focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement);
8690

87-
// @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
88-
// metadata is not inherited by child classes, instead the host binding data is defined in a way
89-
// that can be inherited.
90-
// tslint:disable:no-host-decorator-in-concrete no-private-getters
91-
@HostBinding('attr.aria-label') get _ariaLabel() {
92-
return this._config.ariaLabel || null;
93-
}
94-
95-
@HostBinding('attr.aria-describedby')
96-
get _ariaDescribedBy() {
97-
return this._config.ariaDescribedBy;
98-
}
99-
100-
@HostBinding('attr.role') get _role() {
101-
return this._config.role;
102-
}
103-
104-
@HostBinding('attr.aria-modal') _ariaModal: boolean = true;
105-
106-
@HostBinding('attr.tabindex') get _tabindex() {
107-
return -1;
108-
}
109-
// tslint:disable:no-host-decorator-in-concrete no-private-getters
110-
11191
/** The portal host inside of this container into which the dialog content will be loaded. */
11292
@ViewChild(CdkPortalOutlet, {static: true}) _portalHost: CdkPortalOutlet;
11393

src/cdk-experimental/menu/menu-bar.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
OnDestroy,
1616
Optional,
1717
NgZone,
18-
HostListener,
1918
ElementRef,
2019
Inject,
2120
Self,
@@ -46,6 +45,8 @@ import {MenuAim, MENU_AIM} from './menu-aim';
4645
'class': 'cdk-menu-bar',
4746
'tabindex': '0',
4847
'[attr.aria-orientation]': 'orientation',
48+
'(focus)': 'focusFirstItem()',
49+
'(keydown)': '_handleKeyEvent($event)',
4950
},
5051
providers: [
5152
{provide: CdkMenuGroup, useExisting: CdkMenuBar},
@@ -97,11 +98,6 @@ export class CdkMenuBar extends CdkMenuGroup implements Menu, AfterContentInit,
9798
this._menuAim?.initialize(this, this._pointerTracker!);
9899
}
99100

100-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
101-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
102-
// can move this back into `host`.
103-
// tslint:disable:no-host-decorator-in-concrete
104-
@HostListener('focus')
105101
/** Place focus on the first MenuItem in the menu and set the focus origin. */
106102
focusFirstItem(focusOrigin: FocusOrigin = 'program') {
107103
this._keyManager.setFocusOrigin(focusOrigin);
@@ -114,11 +110,6 @@ export class CdkMenuBar extends CdkMenuGroup implements Menu, AfterContentInit,
114110
this._keyManager.setLastItemActive();
115111
}
116112

117-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
118-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
119-
// can move this back into `host`.
120-
// tslint:disable:no-host-decorator-in-concrete
121-
@HostListener('keydown', ['$event'])
122113
/**
123114
* Handle keyboard events, specifically changing the focused element and/or toggling the active
124115
* items menu.

src/cdk-experimental/menu/menu-item.ts

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
Output,
1616
EventEmitter,
1717
Inject,
18-
HostListener,
1918
NgZone,
2019
OnDestroy,
2120
} from '@angular/core';
@@ -53,6 +52,12 @@ function removeIcons(element: Element) {
5352
'role': 'menuitem',
5453
'class': 'cdk-menu-item',
5554
'[attr.aria-disabled]': 'disabled || null',
55+
'(blur)': '_resetTabIndex()',
56+
'(mouseout)': '_resetTabIndex()',
57+
'(focus)': '_setTabIndex()',
58+
'(mouseenter)': '_setTabIndex($event)',
59+
'(click)': 'trigger()',
60+
'(keydown)': '_onKeydown($event)',
5661
},
5762
})
5863
export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler, OnDestroy {
@@ -104,25 +109,13 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
104109
this._elementRef.nativeElement.focus();
105110
}
106111

107-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
108-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
109-
// can move this back into `host`.
110-
// tslint:disable:no-host-decorator-in-concrete
111-
@HostListener('blur')
112-
@HostListener('mouseout')
113112
/** Reset the _tabindex to -1. */
114113
_resetTabIndex() {
115114
if (!this._isStandaloneItem()) {
116115
this._tabindex = -1;
117116
}
118117
}
119118

120-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
121-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
122-
// can move this back into `host`.
123-
// tslint:disable:no-host-decorator-in-concrete
124-
@HostListener('focus')
125-
@HostListener('mouseenter', ['$event'])
126119
/**
127120
* Set the tab index to 0 if not disabled and it's a focus event, or a mouse enter if this element
128121
* is not in a menu bar.
@@ -143,11 +136,6 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
143136
return !this._parentMenu;
144137
}
145138

146-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
147-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
148-
// can move this back into `host`.
149-
// tslint:disable:no-host-decorator-in-concrete
150-
@HostListener('click')
151139
/**
152140
* If the menu item is not disabled and the element does not have a menu trigger attached, emit
153141
* on the cdkMenuItemTriggered emitter and close all open menus.
@@ -192,11 +180,6 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
192180
return clone.textContent?.trim() || '';
193181
}
194182

195-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
196-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
197-
// can move this back into `host`.
198-
// tslint:disable:no-host-decorator-in-concrete
199-
@HostListener('keydown', ['$event'])
200183
/**
201184
* Handles keyboard events for the menu item, specifically either triggering the user defined
202185
* callback or opening/closing the current menu based on whether the left or right arrow key was

src/cdk-experimental/menu/menu.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
Optional,
1919
OnInit,
2020
NgZone,
21-
HostListener,
2221
ElementRef,
2322
Inject,
2423
Self,
@@ -60,6 +59,8 @@ import {MENU_AIM, MenuAim} from './menu-aim';
6059
'class': 'cdk-menu',
6160
'[class.cdk-menu-inline]': '_isInline()',
6261
'[attr.aria-orientation]': 'orientation',
62+
'(focus)': 'focusFirstItem()',
63+
'(keydown)': '_handleKeyEvent($event)',
6364
},
6465
providers: [
6566
{provide: CdkMenuGroup, useExisting: CdkMenu},
@@ -136,11 +137,6 @@ export class CdkMenu extends CdkMenuGroup implements Menu, AfterContentInit, OnI
136137
this._menuAim?.initialize(this, this._pointerTracker!);
137138
}
138139

139-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
140-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
141-
// can move this back into `host`.
142-
// tslint:disable:no-host-decorator-in-concrete
143-
@HostListener('focus')
144140
/** Place focus on the first MenuItem in the menu and set the focus origin. */
145141
focusFirstItem(focusOrigin: FocusOrigin = 'program') {
146142
this._keyManager.setFocusOrigin(focusOrigin);
@@ -153,11 +149,6 @@ export class CdkMenu extends CdkMenuGroup implements Menu, AfterContentInit, OnI
153149
this._keyManager.setLastItemActive();
154150
}
155151

156-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
157-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
158-
// can move this back into `host`.
159-
// tslint:disable:no-host-decorator-in-concrete
160-
@HostListener('keydown', ['$event'])
161152
/** Handle keyboard events for the Menu. */
162153
_handleKeyEvent(event: KeyboardEvent) {
163154
const keyManager = this._keyManager;

src/cdk-experimental/popover-edit/lens-directives.ts

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,7 @@
77
*/
88

99
import {Subject} from 'rxjs';
10-
import {
11-
Directive,
12-
ElementRef,
13-
EventEmitter,
14-
OnDestroy,
15-
OnInit,
16-
Input,
17-
HostListener,
18-
} from '@angular/core';
10+
import {Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Input} from '@angular/core';
1911
import {hasModifierKey} from '@angular/cdk/keycodes';
2012
import {EDIT_PANE_SELECTOR} from './constants';
2113
import {closest} from './polyfill';
@@ -39,6 +31,11 @@ export type PopoverEditClickOutBehavior = 'close' | 'submit' | 'noop';
3931
],
4032
outputs: ['preservedFormValueChange: cdkEditControlPreservedFormValueChange'],
4133
providers: [EditRef],
34+
host: {
35+
'(ngSubmit)': 'handleFormSubmit()',
36+
'(document:click)': 'handlePossibleClickOut($event)',
37+
'(keydown)': '_handleKeydown($event)',
38+
},
4239
})
4340
export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
4441
protected readonly destroyed = new Subject<void>();
@@ -81,11 +78,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
8178
* the form for validity before proceeding.
8279
* Updates the revert state with the latest submitted value then closes the edit.
8380
*/
84-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
85-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
86-
// can move this back into `host`.
87-
// tslint:disable-next-line:no-host-decorator-in-concrete
88-
@HostListener('ngSubmit')
8981
handleFormSubmit(): void {
9082
if (this.ignoreSubmitUnlessValid && !this.editRef.isValid()) {
9183
return;
@@ -106,11 +98,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
10698
* Called on click anywhere in the document.
10799
* If the click was outside of the lens, trigger the specified click out behavior.
108100
*/
109-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
110-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
111-
// can move this back into `host`.
112-
// tslint:disable-next-line:no-host-decorator-in-concrete
113-
@HostListener('document:click', ['$event'])
114101
handlePossibleClickOut(evt: Event): void {
115102
if (closest(evt.target, EDIT_PANE_SELECTOR)) {
116103
return;
@@ -129,11 +116,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
129116
}
130117
}
131118

132-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
133-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
134-
// can move this back into `host`.
135-
// tslint:disable-next-line:no-host-decorator-in-concrete
136-
@HostListener('keydown', ['$event'])
137119
_handleKeydown(event: KeyboardEvent) {
138120
if (event.key === 'Escape' && !hasModifierKey(event)) {
139121
this.close();
@@ -159,6 +141,7 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
159141
selector: 'button[cdkEditRevert]',
160142
host: {
161143
'type': 'button', // Prevents accidental form submits.
144+
'(click)': 'revertEdit()',
162145
},
163146
})
164147
export class CdkEditRevert<FormValue> {
@@ -167,18 +150,20 @@ export class CdkEditRevert<FormValue> {
167150

168151
constructor(protected readonly editRef: EditRef<FormValue>) {}
169152

170-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
171-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
172-
// can move this back into `host`.
173-
// tslint:disable-next-line:no-host-decorator-in-concrete
174-
@HostListener('click')
175153
revertEdit(): void {
176154
this.editRef.reset();
177155
}
178156
}
179157

180158
/** Closes the lens on click. */
181-
@Directive({selector: '[cdkEditClose]'})
159+
@Directive({
160+
selector: '[cdkEditClose]',
161+
host: {
162+
'(click)': 'closeEdit()',
163+
'(keydown.enter)': 'closeEdit()',
164+
'(keydown.space)': 'closeEdit()',
165+
},
166+
})
182167
export class CdkEditClose<FormValue> {
183168
constructor(
184169
protected readonly elementRef: ElementRef<HTMLElement>,
@@ -192,15 +177,6 @@ export class CdkEditClose<FormValue> {
192177
}
193178
}
194179

195-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
196-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
197-
// can move this back into `host`.
198-
// tslint:disable-next-line:no-host-decorator-in-concrete
199-
@HostListener('click')
200-
// tslint:disable-next-line:no-host-decorator-in-concrete
201-
@HostListener('keydown.enter')
202-
// tslint:disable-next-line:no-host-decorator-in-concrete
203-
@HostListener('keydown.space')
204180
closeEdit(): void {
205181
// Note that we use `click` here, rather than a keyboard event, because some screen readers
206182
// will emit a fake click event instead of an enter keyboard event on buttons. For the keyboard

src/cdk-experimental/popover-edit/table-directives.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
OnDestroy,
1818
TemplateRef,
1919
ViewContainerRef,
20-
HostListener,
2120
} from '@angular/core';
2221
import {fromEvent, fromEventPattern, merge, Subject} from 'rxjs';
2322
import {
@@ -496,6 +495,9 @@ export class CdkRowHoverContent implements AfterViewInit, OnDestroy {
496495
*/
497496
@Directive({
498497
selector: '[cdkEditOpen]',
498+
host: {
499+
'(click)': 'openEdit($event)',
500+
},
499501
})
500502
export class CdkEditOpen {
501503
constructor(
@@ -510,11 +512,6 @@ export class CdkEditOpen {
510512
}
511513
}
512514

513-
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
514-
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
515-
// can move this back into `host`.
516-
// tslint:disable-next-line:no-host-decorator-in-concrete
517-
@HostListener('click', ['$event'])
518515
openEdit(evt: Event): void {
519516
this.editEventDispatcher.editing.next(closest(this.elementRef.nativeElement!, CELL_SELECTOR));
520517
evt.stopPropagation();

src/cdk/coercion/coercion.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Utility functions for coercing `@Input`s into specific types.
33
### Example
44

55
```ts
6-
import {Directive, ElementRef, HostListener} from '@angular/core';
6+
import {Directive, ElementRef} from '@angular/core';
77
import {
88
coerceBooleanProperty,
99
BooleanInput,

0 commit comments

Comments
 (0)