Skip to content

Commit

Permalink
refactor(multiple): clean up host decorator workarounds (#23879)
Browse files Browse the repository at this point in the history
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.

(cherry picked from commit 5fb821f)
  • Loading branch information
crisbeto authored and amysorto committed Nov 8, 2021
1 parent 5d49df1 commit e4dcb4f
Show file tree
Hide file tree
Showing 27 changed files with 113 additions and 359 deletions.
30 changes: 5 additions & 25 deletions src/cdk-experimental/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
ComponentRef,
ElementRef,
EmbeddedViewRef,
HostBinding,
Inject,
NgZone,
OnDestroy,
Expand Down Expand Up @@ -70,6 +69,11 @@ export function throwDialogContentAlreadyAttachedError() {
}`,
'(@dialog.start)': '_onAnimationStart($event)',
'(@dialog.done)': '_animationDone.next($event)',
'tabindex': '-1',
'[attr.role]': '_config.role',
'aria-modal': 'true',
'[attr.aria-label]': '_config.ariaLabel || null',
'[attr.aria-describedby]': '_config.ariaDescribedBy',
},
})
export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
Expand All @@ -84,30 +88,6 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
/** The class that traps and manages focus within the dialog. */
private _focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement);

// @HostBinding is used in the class as it is expected to be extended. Since @Component decorator
// metadata is not inherited by child classes, instead the host binding data is defined in a way
// that can be inherited.
// tslint:disable:no-host-decorator-in-concrete no-private-getters
@HostBinding('attr.aria-label') get _ariaLabel() {
return this._config.ariaLabel || null;
}

@HostBinding('attr.aria-describedby')
get _ariaDescribedBy() {
return this._config.ariaDescribedBy;
}

@HostBinding('attr.role') get _role() {
return this._config.role;
}

@HostBinding('attr.aria-modal') _ariaModal: boolean = true;

@HostBinding('attr.tabindex') get _tabindex() {
return -1;
}
// tslint:disable:no-host-decorator-in-concrete no-private-getters

/** The portal host inside of this container into which the dialog content will be loaded. */
@ViewChild(CdkPortalOutlet, {static: true}) _portalHost: CdkPortalOutlet;

Expand Down
13 changes: 2 additions & 11 deletions src/cdk-experimental/menu/menu-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
OnDestroy,
Optional,
NgZone,
HostListener,
ElementRef,
Inject,
Self,
Expand Down Expand Up @@ -46,6 +45,8 @@ import {MenuAim, MENU_AIM} from './menu-aim';
'class': 'cdk-menu-bar',
'tabindex': '0',
'[attr.aria-orientation]': 'orientation',
'(focus)': 'focusFirstItem()',
'(keydown)': '_handleKeyEvent($event)',
},
providers: [
{provide: CdkMenuGroup, useExisting: CdkMenuBar},
Expand Down Expand Up @@ -97,11 +98,6 @@ export class CdkMenuBar extends CdkMenuGroup implements Menu, AfterContentInit,
this._menuAim?.initialize(this, this._pointerTracker!);
}

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

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable:no-host-decorator-in-concrete
@HostListener('keydown', ['$event'])
/**
* Handle keyboard events, specifically changing the focused element and/or toggling the active
* items menu.
Expand Down
29 changes: 6 additions & 23 deletions src/cdk-experimental/menu/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
Output,
EventEmitter,
Inject,
HostListener,
NgZone,
OnDestroy,
} from '@angular/core';
Expand Down Expand Up @@ -53,6 +52,12 @@ function removeIcons(element: Element) {
'role': 'menuitem',
'class': 'cdk-menu-item',
'[attr.aria-disabled]': 'disabled || null',
'(blur)': '_resetTabIndex()',
'(mouseout)': '_resetTabIndex()',
'(focus)': '_setTabIndex()',
'(mouseenter)': '_setTabIndex($event)',
'(click)': 'trigger()',
'(keydown)': '_onKeydown($event)',
},
})
export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler, OnDestroy {
Expand Down Expand Up @@ -104,25 +109,13 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
this._elementRef.nativeElement.focus();
}

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

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

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

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable:no-host-decorator-in-concrete
@HostListener('keydown', ['$event'])
/**
* Handles keyboard events for the menu item, specifically either triggering the user defined
* callback or opening/closing the current menu based on whether the left or right arrow key was
Expand Down
13 changes: 2 additions & 11 deletions src/cdk-experimental/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
Optional,
OnInit,
NgZone,
HostListener,
ElementRef,
Inject,
Self,
Expand Down Expand Up @@ -60,6 +59,8 @@ import {MENU_AIM, MenuAim} from './menu-aim';
'class': 'cdk-menu',
'[class.cdk-menu-inline]': '_isInline()',
'[attr.aria-orientation]': 'orientation',
'(focus)': 'focusFirstItem()',
'(keydown)': '_handleKeyEvent($event)',
},
providers: [
{provide: CdkMenuGroup, useExisting: CdkMenu},
Expand Down Expand Up @@ -136,11 +137,6 @@ export class CdkMenu extends CdkMenuGroup implements Menu, AfterContentInit, OnI
this._menuAim?.initialize(this, this._pointerTracker!);
}

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

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable:no-host-decorator-in-concrete
@HostListener('keydown', ['$event'])
/** Handle keyboard events for the Menu. */
_handleKeyEvent(event: KeyboardEvent) {
const keyManager = this._keyManager;
Expand Down
54 changes: 15 additions & 39 deletions src/cdk-experimental/popover-edit/lens-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@
*/

import {Subject} from 'rxjs';
import {
Directive,
ElementRef,
EventEmitter,
OnDestroy,
OnInit,
Input,
HostListener,
} from '@angular/core';
import {Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Input} from '@angular/core';
import {hasModifierKey} from '@angular/cdk/keycodes';
import {EDIT_PANE_SELECTOR} from './constants';
import {closest} from './polyfill';
Expand All @@ -39,6 +31,11 @@ export type PopoverEditClickOutBehavior = 'close' | 'submit' | 'noop';
],
outputs: ['preservedFormValueChange: cdkEditControlPreservedFormValueChange'],
providers: [EditRef],
host: {
'(ngSubmit)': 'handleFormSubmit()',
'(document:click)': 'handlePossibleClickOut($event)',
'(keydown)': '_handleKeydown($event)',
},
})
export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
protected readonly destroyed = new Subject<void>();
Expand Down Expand Up @@ -81,11 +78,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
* the form for validity before proceeding.
* Updates the revert state with the latest submitted value then closes the edit.
*/
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('ngSubmit')
handleFormSubmit(): void {
if (this.ignoreSubmitUnlessValid && !this.editRef.isValid()) {
return;
Expand All @@ -106,11 +98,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
* Called on click anywhere in the document.
* If the click was outside of the lens, trigger the specified click out behavior.
*/
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('document:click', ['$event'])
handlePossibleClickOut(evt: Event): void {
if (closest(evt.target, EDIT_PANE_SELECTOR)) {
return;
Expand All @@ -129,11 +116,6 @@ export class CdkEditControl<FormValue> implements OnDestroy, OnInit {
}
}

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

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

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

/** Closes the lens on click. */
@Directive({selector: '[cdkEditClose]'})
@Directive({
selector: '[cdkEditClose]',
host: {
'(click)': 'closeEdit()',
'(keydown.enter)': 'closeEdit()',
'(keydown.space)': 'closeEdit()',
},
})
export class CdkEditClose<FormValue> {
constructor(
protected readonly elementRef: ElementRef<HTMLElement>,
Expand All @@ -192,15 +177,6 @@ export class CdkEditClose<FormValue> {
}
}

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('click')
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('keydown.enter')
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('keydown.space')
closeEdit(): void {
// Note that we use `click` here, rather than a keyboard event, because some screen readers
// will emit a fake click event instead of an enter keyboard event on buttons. For the keyboard
Expand Down
9 changes: 3 additions & 6 deletions src/cdk-experimental/popover-edit/table-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
OnDestroy,
TemplateRef,
ViewContainerRef,
HostListener,
} from '@angular/core';
import {fromEvent, fromEventPattern, merge, Subject} from 'rxjs';
import {
Expand Down Expand Up @@ -496,6 +495,9 @@ export class CdkRowHoverContent implements AfterViewInit, OnDestroy {
*/
@Directive({
selector: '[cdkEditOpen]',
host: {
'(click)': 'openEdit($event)',
},
})
export class CdkEditOpen {
constructor(
Expand All @@ -510,11 +512,6 @@ export class CdkEditOpen {
}
}

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable-next-line:no-host-decorator-in-concrete
@HostListener('click', ['$event'])
openEdit(evt: Event): void {
this.editEventDispatcher.editing.next(closest(this.elementRef.nativeElement!, CELL_SELECTOR));
evt.stopPropagation();
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/coercion/coercion.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Utility functions for coercing `@Input`s into specific types.
### Example

```ts
import {Directive, ElementRef, HostListener} from '@angular/core';
import {Directive, ElementRef} from '@angular/core';
import {
coerceBooleanProperty,
BooleanInput,
Expand Down
Loading

0 comments on commit e4dcb4f

Please sign in to comment.