Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 932eac6

Browse files
rapropostrimox
authored andcommitted
fix: Directives inheriting from components for Ivy template type checks (#2017)
As described in the comments on #1982, Ivy does not appreciate it when directives inherit from components. I found three places where this happens: * `MdcRippleDirective` extends `MdcRippleComponent` * `MdcSelectIcon` extends `MdcIcon` * `MdcTextFieldIcon` extends `MdcIcon` I apologize in advance if any of these attempts are not done correctly (I'm well out of my comfort zone here), as well as for any needless change caused by code formatting. Perhaps a more opinionated `.editorconfig` would help. The last two cases are similar and simpler: as each is apparently designed to augment an `<mdc-icon>` element, I figured that simply eliminating the inheritance could be performed without negative impact. Ripple was more challenging. As the component has effectively no template, I decided to try to implement both with a single directive, as is described in the comments inside the commit. Initially, I tried defining a type alias for `MdcRippleComponent`, in order to be able to fool any existing code (including the tests). This was unsuccessful, as apparently an actual JS object must exist. Therefore, I had to make a couple of small changes to the tests as well, namely replacing instances of `MdcRippleComponent` with `MdcRippleDirective` and changing the way that the test grabs the TS controller object from `componentInstance` to `injector.get()`. The meat of the tests themselves are unmodified, and hopefully that is acceptable. I was able to at least build and run an app using Angular 9.0.0-rc0 out of the box using this branch, and hope that it is at least of some use to others, if only as a stepping stone towards being done more skillfully by somebody more intimately familiar with Angular internals than I. Thank you for this effort, much appreciated @rapropos !!
1 parent 1882912 commit 932eac6

File tree

6 files changed

+110
-115
lines changed

6 files changed

+110
-115
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
"url": "https://github.com/trimox/angular-mdc-web.git"
99
},
1010
"license": "MIT",
11-
"version": "3.2.1",
11+
"version": "4.0.0-SNAPSHOT",
1212
"engines": {
1313
"node": ">= 9.11.1"
1414
},
15-
"requiredAngularVersion": "^8.0.0 || ^9.0.0-0",
15+
"requiredAngularVersion": "^8.0.0 || ^9.0.0-rc.0",
1616
"scripts": {
1717
"build": "gulp web:build",
1818
"build:release": "gulp web:build-release",

packages/ripple/ripple-module.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import {NgModule} from '@angular/core';
22

33
import {
4-
MdcRippleComponent,
54
MdcRippleDirective
65
} from './ripple';
76

87
@NgModule({
9-
exports: [MdcRippleComponent, MdcRippleDirective],
10-
declarations: [MdcRippleComponent, MdcRippleDirective],
8+
exports: [MdcRippleDirective],
9+
declarations: [MdcRippleDirective],
1110
})
1211
export class MdcRippleModule { }

packages/ripple/ripple.ts

+100-101
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,111 @@
1-
import {
2-
AfterViewInit,
3-
ChangeDetectionStrategy,
4-
Component,
5-
Directive,
6-
ElementRef,
7-
Input,
8-
OnDestroy
9-
} from '@angular/core';
1+
import {AfterViewInit, Directive, ElementRef, Input, OnDestroy} from '@angular/core';
102
import {coerceBooleanProperty} from '@angular/cdk/coercion';
113

124
import {MdcRipple, MDCRippleCapableSurface} from './ripple.service';
135

14-
@Component({
15-
selector: 'mdc-ripple, [mdc-ripple]',
16-
template: '<ng-content></ng-content>',
17-
providers: [MdcRipple],
18-
changeDetection: ChangeDetectionStrategy.OnPush
6+
// rapropos 2019.11.02
7+
// Ivy doesn't allow directives to inherit from components, so this merges
8+
// what used to be the two. see the constructor for more information
9+
10+
@Directive({
11+
selector: 'mdc-ripple, [mdc-ripple], [mdcRipple]',
12+
providers: [MdcRipple],
1913
})
20-
export class MdcRippleComponent implements AfterViewInit, OnDestroy, MDCRippleCapableSurface {
21-
_root!: Element;
22-
23-
get ripple(): MdcRipple {
24-
return this._ripple;
25-
}
26-
27-
@Input()
28-
get attachTo(): any {
29-
return this._attachTo;
30-
}
31-
set attachTo(element: any) {
32-
if (this._attachTo) {
33-
this._attachTo.classList.remove('mdc-ripple-surface');
14+
export class MdcRippleDirective implements AfterViewInit, OnDestroy, MDCRippleCapableSurface {
15+
_root!: Element;
16+
17+
get ripple(): MdcRipple {
18+
return this._ripple;
3419
}
35-
this._attachTo = element;
36-
if (this._attachTo) {
37-
this._attachTo.classList.add('mdc-ripple-surface');
20+
21+
@Input()
22+
get attachTo(): any {
23+
return this._attachTo;
3824
}
39-
}
40-
private _attachTo: any;
41-
42-
@Input()
43-
get primary(): boolean {
44-
return this._primary;
45-
}
46-
set primary(value: boolean) {
47-
this._primary = coerceBooleanProperty(value);
48-
this._primary ? this.attachTo.classList.add('mdc-ripple-surface--primary')
49-
: this.attachTo.classList.remove('mdc-ripple-surface--primary');
50-
}
51-
private _primary: boolean = false;
52-
53-
@Input()
54-
get secondary(): boolean {
55-
return this._secondary;
56-
}
57-
set secondary(value: boolean) {
58-
this._secondary = coerceBooleanProperty(value);
59-
this._secondary ? this.attachTo.classList.add('mdc-ripple-surface--accent')
60-
: this.attachTo.classList.remove('mdc-ripple-surface--accent');
61-
}
62-
private _secondary: boolean = false;
63-
64-
@Input()
65-
get disabled(): boolean {
66-
return this._disabled;
67-
}
68-
set disabled(value: boolean) {
69-
this._disabled = coerceBooleanProperty(value);
70-
}
71-
private _disabled: boolean = false;
72-
73-
@Input()
74-
get unbounded(): boolean {
75-
return this._unbounded;
76-
}
77-
set unbounded(value: boolean) {
78-
this._unbounded = coerceBooleanProperty(value);
79-
}
80-
protected _unbounded: boolean = false;
81-
82-
constructor(
83-
private _ripple: MdcRipple,
84-
public elementRef: ElementRef<HTMLElement>) {
85-
this._root = this.elementRef.nativeElement;
86-
}
87-
88-
ngAfterViewInit(): void {
89-
this._ripple = new MdcRipple(this.elementRef);
90-
this._ripple.init();
91-
}
92-
93-
ngOnDestroy(): void {
94-
this.ripple.destroy();
95-
}
96-
}
9725

98-
@Directive({
99-
selector: '[mdcRipple]',
100-
providers: [MdcRipple]
101-
})
102-
export class MdcRippleDirective extends MdcRippleComponent {
103-
constructor(
104-
_ripple: MdcRipple,
105-
elementRef: ElementRef) {
26+
set attachTo(element: any) {
27+
if (this._attachTo) {
28+
this._attachTo.classList.remove('mdc-ripple-surface');
29+
}
30+
this._attachTo = element;
31+
if (this._attachTo) {
32+
this._attachTo.classList.add('mdc-ripple-surface');
33+
}
34+
}
35+
36+
private _attachTo: any;
37+
38+
@Input()
39+
get primary(): boolean {
40+
return this._primary;
41+
}
42+
43+
set primary(value: boolean) {
44+
this._primary = coerceBooleanProperty(value);
45+
this._primary ? this.attachTo.classList.add('mdc-ripple-surface--primary')
46+
: this.attachTo.classList.remove('mdc-ripple-surface--primary');
47+
}
48+
49+
private _primary: boolean = false;
50+
51+
@Input()
52+
get secondary(): boolean {
53+
return this._secondary;
54+
}
55+
56+
set secondary(value: boolean) {
57+
this._secondary = coerceBooleanProperty(value);
58+
this._secondary ? this.attachTo.classList.add('mdc-ripple-surface--accent')
59+
: this.attachTo.classList.remove('mdc-ripple-surface--accent');
60+
}
10661

107-
super(_ripple, elementRef);
62+
private _secondary: boolean = false;
10863

109-
this._unbounded = true;
110-
this.elementRef.nativeElement.setAttribute('data-mdc-ripple-is-unbounded', '');
111-
}
64+
@Input()
65+
get disabled(): boolean {
66+
return this._disabled;
67+
}
68+
69+
set disabled(value: boolean) {
70+
this._disabled = coerceBooleanProperty(value);
71+
}
72+
73+
private _disabled: boolean = false;
74+
75+
@Input()
76+
get unbounded(): boolean {
77+
return this._unbounded;
78+
}
79+
80+
set unbounded(value: boolean) {
81+
this._unbounded = coerceBooleanProperty(value);
82+
}
83+
84+
protected _unbounded: boolean = false;
85+
86+
constructor(
87+
private _ripple: MdcRipple,
88+
public elementRef: ElementRef<HTMLElement>) {
89+
this._root = this.elementRef.nativeElement;
90+
91+
// rapropos 2019.11.02
92+
// the difference between the old component and directive was boundedness
93+
// and the presence of the 'data-mdc-ripple-is-unbounded' attribute. we
94+
// recreate that here by checking our tag name. if it is `MDC-RIPPLE',
95+
// then we are acting as the component did. otherwise, we act as the directive
96+
if (this._root.tagName !== 'MDC-RIPPLE') {
97+
this.unbounded = true;
98+
this._root.setAttribute('data-mdc-ripple-is-unbounded', '');
99+
}
100+
}
101+
102+
ngAfterViewInit(): void {
103+
this._ripple = new MdcRipple(this.elementRef);
104+
this._ripple.init();
105+
}
106+
107+
ngOnDestroy(): void {
108+
this.ripple.destroy();
109+
}
112110
}
111+

packages/select/select-icon.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {Directive} from '@angular/core';
2-
import {MdcIcon} from '@angular-mdc/web/icon';
32

43
@Directive({
54
selector: '[mdcSelectIcon]',
65
exportAs: 'mdcSelectIcon',
76
host: { 'class': 'mdc-select__icon' }
87
})
9-
export class MdcSelectIcon extends MdcIcon { }
8+
export class MdcSelectIcon { }

packages/textfield/text-field-icon.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import {
33
Input
44
} from '@angular/core';
55
import {coerceBooleanProperty} from '@angular/cdk/coercion';
6-
import {MdcIcon} from '@angular-mdc/web/icon';
76

87
@Directive({
98
selector: '[mdcTextFieldIcon]',
109
exportAs: 'mdcTextFieldIcon',
1110
host: { 'class': 'mdc-text-field__icon' }
1211
})
13-
export class MdcTextFieldIcon extends MdcIcon {
12+
export class MdcTextFieldIcon {
1413
@Input()
1514
get leading(): boolean { return this._leading; }
1615
set leading(value: boolean) {

test/ripple/ripple.test.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {dispatchMouseEvent} from '../testing/dispatch-events';
77

88
import {
99
MdcRippleModule,
10-
MdcRippleComponent,
1110
MdcRippleDirective
1211
} from '@angular-mdc/web';
1312

@@ -32,16 +31,16 @@ describe('MdcRippleComponent', () => {
3231
describe('basic behaviors', () => {
3332
let testDebugElement: DebugElement;
3433
let testNativeElement: HTMLElement;
35-
let testInstance: MdcRippleComponent;
34+
let testInstance: MdcRippleDirective;
3635
let testComponent: SimpleTest;
3736

3837
beforeEach(() => {
3938
fixture = TestBed.createComponent(SimpleTest);
4039
fixture.detectChanges();
4140

42-
testDebugElement = fixture.debugElement.query(By.directive(MdcRippleComponent));
41+
testDebugElement = fixture.debugElement.query(By.directive(MdcRippleDirective));
4342
testNativeElement = testDebugElement.nativeElement;
44-
testInstance = testDebugElement.componentInstance;
43+
testInstance = testDebugElement.injector.get(MdcRippleDirective);
4544
testComponent = fixture.debugElement.componentInstance;
4645
});
4746

@@ -137,7 +136,7 @@ describe('MdcRippleComponent', () => {
137136

138137
testDebugElement = fixture.debugElement.query(By.directive(MdcRippleDirective));
139138
testNativeElement = testDebugElement.nativeElement;
140-
testInstance = testDebugElement.componentInstance;
139+
testInstance = testDebugElement.injector.get(MdcRippleDirective);
141140
testComponent = fixture.debugElement.componentInstance;
142141
});
143142

0 commit comments

Comments
 (0)