angular: docs: Add documentation for `error NG2007: Ignore abstract classes`

Please ignore abstract classes. Currently I cannot build. The compiler throws:

NG2007: Class is using Angular features but is not decorated. Please add an explicit Angular decorator.

export abstract class MyAbstractComponent implements OnInit, OnDestroy {}

You can detect it by the keyword abstract. Is there a workaround? A config to ignore this error? How does Angular detect it? By the extended Interfaces or the suffix “Component”?

Angular 11.2.5

Update: I changed the name to MyComponentAbstract. Not working… My current workaround is:

@Component({ template: '' })
export abstract class MyAbstractComponent implements OnInit, OnDestroy {}

(dummy decorator)

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 15 (8 by maintainers)

Most upvoted comments

In JavaScript there is no difference between a normal class and an abstract class. For the Angular runtime to be able to wire up the life-cycle hooks it needs to compile the class, and the way you tell Angular to do this is by marking it as an “Angular injectable”. Any any decorator will do the job. In your case the simplest resolution is actually to use @Directive() rather than @Component({template: ''}).

This is how Angular is designed to work, so your request is very unlikely to be implemented. That being said I think that this error would be a good candidate for documentation similar to https://angular.io/errors/NG0100. cc @twerske

Marking this as a documentation request.

No, that classes should be ignored. Only take care if a decorator is assigned. As it worked before. Otherwise it’s becomes very ugly, annoying and confusing in the code.

Note that an abstract class should not uses directly. Same meaning for Angular Component / Directive. Also “The name of the class MyAbstractComponent should end with the suffix Directive” when using Directive() (linter).

@infacto - I am not quite sure I understand this.

For Angular to work (since Ivy) it needs all classes that expect to take part in Angular life-cycle hooks (e.g. ngOnInit()) or change detection (e.g. @Input) to have an Angular “class” decorator on them, even if they are abstract and will never be instantiated directly.

Marking a an abstract class with @Directive() (no arguments) tells Angular that it should compile this class, creating a factory and wiring up any methods or properties that will be interacted with by Angular. Without that, the classes that inherit from it may not behave as expected. For example if the abstract class has ngOnInit and the concrete class does not, Angular must grab that method and make sure it gets called when the concrete class is instantiated. In Ivy each class only knows about itself at runtime, so the Ivy renderer needs to have the additional information about the abstract base class.

If the linter if forcing you to name this abstract class ...Directive then either the linter needs configuring or perhaps we should ask for a feature request where it doesn’t try to specify the name if the class is abstract.

Ok, I think I understand. Since Ivy the following may not work:

export abstract class MyAbstractComponent implements OnInit {
  @Input() value: number;

  ngOnInit() {
    console.log(this.value);
  }
}

@Component(...)
export class MyRealComponent extends MyAbstractComponent implements AfterViewInit {
  @Input() name: string;

  ngAfterViewInit() {
    console.log(this.name);
  }
}

Results in: Only name from ngAfterViewInit is visible. Because Angular does not know ngOnInit and value from the abstract class. Right? Therefore we need to set a decorator:

@Directive()
export abstract class MyAbstractComponent implements OnInit {
  @Input() value: number;

  ngOnInit() {
    console.log(this.value);
  }
}

@Component(...)
export class MyRealComponent extends MyAbstractComponent implements AfterViewInit {
  @Input() name: string;

  ngAfterViewInit() {
    console.log(this.name);
  }
}

Not tested. Just a self explanation.

I agree with @petebacondarwin this is a good candidate for an error guide, especially if recent updates and migrations increase it’s frequency. It’s tricky since the base error is thrown for a wide variety of reasons.

This seems to be what I’ve seen labeled as the “Undecorated Parent pattern.” My understanding is the ng update adds @Directive() since that abstract class isn’t really a component, though it’s smart and knows it needs to be marked for Angular compilations. This isn’t really a work around, it’s intentional, but documenting best practices for abstract components is a good issue submission!

This doc on the original proposal for this migration adds some context.

I’d probably do something like:

@Directive()
export abstract class ListItems implements OnInit, OnDestroy {}

@Component({template: '<div>todo list</div>'})
export class TodoListComponent extends ListItems ... {}

@Component({template: '<div>grocery list</div>'})
export class GroceryListComponent extends ListItems ... {}

but this should also work:

@Component({template: ''})
export abstract class ListItems implements OnInit, OnDestroy {}

other solutions might be the “use aggregation rather than inheritance” idea and migrate base class concepts to a service or directive to inject in children…

Using @Directive() forces the name of my abstract component to have a suffix ...Directive. And a Component want to be inside a NgModule. It’s not the best solution right now… (The code shines like a xmas tree.) I would prefer to just ignore that class. (without decorator).