I have two Angular components which share a lot of common appearance and behavior, so much of that commonality is in an abstract superclass. Here's the start of that superclass:
@Directive({ standalone: true })
export abstract class DigitSequenceEditorDirective<T> implements
AfterViewInit, ControlValueAccessor, OnInit, OnDestroy, Validator {
...
And this is how the two subclasses are declared:
@Component({
selector: 'tbw-angle-editor',
animations: [BACKGROUND_ANIMATIONS],
templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
styleUrls: ['../digit-sequence-editor/digit-sequence-editor.directive.scss'],
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AngleEditorComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => AngleEditorComponent), multi: true }],
imports: [NgStyle, NgClass],
standalone: true
})
export class AngleEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
...
@Component({
selector: 'tbw-time-editor',
animations: [BACKGROUND_ANIMATIONS],
templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
styleUrls: ['../digit-sequence-editor/digit-sequence-editor.directive.scss', './time-editorponent.scss'],
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimeEditorComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => TimeEditorComponent), multi: true }],
imports: [NgClass, NgStyle],
standalone: true
})
export class TimeEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
...
(A lot of redundant boilerplate in the declarations, but no way to reduce that much that I know of.)
I would think (but apparently I am wrong) I should be able to pass either AngleEditorComponent
or TimeEditorComponent
to a function with a generic type that extends DigitSequenceEditorDirective<any>
, something I need to do to effectively consolidate a lot of common Karma/Jasmine test code. Part of that common code looks like this:
type DigitSequenceSuperclass = DigitSequenceEditorDirective<any>;
export class CommonTestEnvironment<T, U extends DigitSequenceSuperclass> {
// ...
export async function sharedBeforeEach<T, U extends DigitSequenceSuperclass>(qlass: Type<T>, innerClass: U,
selector: string, initValue: string): Promise<CommonTestEnvironment<T, U>> {
await TestBed.configureTestingModule({
// ...
Unless I just hack around the issue with @ts-ignore
, this is the grief I get:
So, DigitSequenceEditorDirective<T>
is not a class derived from... itself?
Can anyone explain what might be happening here, and offer a better work around than @ts-ignore
?
The unit testing is working fine, by the way, using the common code. I'd just prefer cleaner typing if I can manage it.
Full source code here:
Update:
instanceof DigitSequenceEditorDirective
evaluates as true for instances of both AngleEditorComponent
and TimeEditorComponent
.
I have two Angular components which share a lot of common appearance and behavior, so much of that commonality is in an abstract superclass. Here's the start of that superclass:
@Directive({ standalone: true })
export abstract class DigitSequenceEditorDirective<T> implements
AfterViewInit, ControlValueAccessor, OnInit, OnDestroy, Validator {
...
And this is how the two subclasses are declared:
@Component({
selector: 'tbw-angle-editor',
animations: [BACKGROUND_ANIMATIONS],
templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
styleUrls: ['../digit-sequence-editor/digit-sequence-editor.directive.scss'],
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AngleEditorComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => AngleEditorComponent), multi: true }],
imports: [NgStyle, NgClass],
standalone: true
})
export class AngleEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
...
@Component({
selector: 'tbw-time-editor',
animations: [BACKGROUND_ANIMATIONS],
templateUrl: '../digit-sequence-editor/digit-sequence-editor.directive.html',
styleUrls: ['../digit-sequence-editor/digit-sequence-editor.directive.scss', './time-editorponent.scss'],
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimeEditorComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => TimeEditorComponent), multi: true }],
imports: [NgClass, NgStyle],
standalone: true
})
export class TimeEditorComponent extends DigitSequenceEditorDirective<number> implements OnInit {
...
(A lot of redundant boilerplate in the declarations, but no way to reduce that much that I know of.)
I would think (but apparently I am wrong) I should be able to pass either AngleEditorComponent
or TimeEditorComponent
to a function with a generic type that extends DigitSequenceEditorDirective<any>
, something I need to do to effectively consolidate a lot of common Karma/Jasmine test code. Part of that common code looks like this:
type DigitSequenceSuperclass = DigitSequenceEditorDirective<any>;
export class CommonTestEnvironment<T, U extends DigitSequenceSuperclass> {
// ...
export async function sharedBeforeEach<T, U extends DigitSequenceSuperclass>(qlass: Type<T>, innerClass: U,
selector: string, initValue: string): Promise<CommonTestEnvironment<T, U>> {
await TestBed.configureTestingModule({
// ...
Unless I just hack around the issue with @ts-ignore
, this is the grief I get:
So, DigitSequenceEditorDirective<T>
is not a class derived from... itself?
Can anyone explain what might be happening here, and offer a better work around than @ts-ignore
?
The unit testing is working fine, by the way, using the common code. I'd just prefer cleaner typing if I can manage it.
Full source code here: https://github/kshetline/tubular_ng-widgets/tree/development
Update:
instanceof DigitSequenceEditorDirective
evaluates as true for instances of both AngleEditorComponent
and TimeEditorComponent
.
1 Answer
Reset to default 2Ah hah! I got screwed by an auto-import.
This import
was automatically created for me:
import { DigitSequenceEditorDirective } from 'tubular-ng-widgets';
...when what I really needed was:
import { DigitSequenceEditorDirective } from './digit-sequence-editor.directive';
For my type definitions to be consistent they needed to point to the same source. The errors seemed nonsensical because there were doubled-up definitions of the same classes.