angularangular-directiveangular-componentsangular-inheritance

Angular Component inherits _WITHOUT_ repeating the constructor


I've been looking for how to create a good Component parent to improve the DRY of my code. Inheriting a Component from a Component requires repetition of the constructor(<services>){} and this is counter to the DRY objective. I fell across a warning message from Angular which suggested inheriting from a Directive ??? So I tried it, it seems to work.

Are you aware of any risks or issues I will create with this approach?

Angular docs hint at an "abstract" directive, so perhaps there is something intentional here. In any event a working example is here on stackblitz. The key parent and child files in their entirety are below.

import { Directive, OnDestroy } from '@angular/core';
import { Tag, TagSubject } from './tag.store';

@Directive()  // This was the unexpected success
export class BaseTagDirective implements OnDestroy {
  subs: any = [];
  tag: Tag;

  constructor(  // This is the constructor I did NOT want to repeat
    private tagstore: TagSubject
  ) {
    this.tag = new Tag();
  }

  subTag(tagname: string) {
    this.subs.push(
      this.tagstore.subject(tagname).asObservable().subscribe((tag: any) => {
          this.tag.name = tag.name;
          this.tag.value = tag.value;
        })
    );
  }

  ngOnDestroy() {  // unrelated to the question, just a subscription tidy up.
    for (let i = 0; i < this.subs.length; i++) {
      this.subs[i].unsubscribe();
    }
  }
}

And the resulting child is ...

import { Component, OnInit, Input } from '@angular/core';
import { BaseTagDirective } from './base.directive';

@Component({
  selector: 'app-value',
  templateUrl: './value.component.html'
})
export class ValueComponent extends BaseTagDirective implements OnInit {
  @Input() tagname: any;

  // look no constructor :D
  
  ngOnInit() {
    this.subTag(this.tagname);
  }
}

The tag store referred to by the parent includes the following items.

  update(tagname: string, value: number) {
    this.tags[tagname].value = value;
    this.subjects[tagname].next(this.tags[tagname]);
  }

  subject(tagname: string) {
    this.subjects[tagname] = new BehaviorSubject<Tag>(this.tags[tagname]);
    this.subjects[tagname].next(this.tags[tagname]);
    return this.subjects[tagname];
  }

Solution

  • from Angular 14 there is a handy feature for your use case - global inject. You can now move your dependencies away from a constructor. So no repitition of injection would be required

    class Parent {
      service1 = inject(ServiceOne);
    }
    
    class Child extends Parent {
      service2 = inject(ServiceTwo);
    }