← Back to list

Your Angular Base Class Is a Time Bomb — Here’s the Modern Fix

“The feature took one afternoon. Untangling the inheritance chain took the entire week.”

Angular_with_Awais · 2026-05-28 04:48 · 18 claps · 4.0 min read paywalled
#angular #coding #programming #medium #web-development
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development

Your Angular Base Class Is a Time Bomb — Here’s the Modern Fix

“The feature took one afternoon. Untangling the inheritance chain took the entire week.”

If you’ve worked on a large Angular application, you’ve probably seen it:

export class OrdersPage extends SharedBaseComponent

Then another:

export class AnalyticsPage extends SharedBaseComponent

And another:

export class ReportsPage extends SharedBaseComponent

At first, it feels efficient. One base class. Shared logic. Less duplication.

But six months later, changing a single method inside that base class suddenly breaks tooltips, analytics tracking, keyboard shortcuts, and form validation in completely unrelated screens.

That’s the moment inheritance stops being a convenience and becomes architectural debt.

Modern Angular gives us a better alternative: composition over inheritance.

The Real Problem With Deep Inheritance

Inheritance creates upward coupling.

A child component becomes dependent on:

  • the parent class
  • the parent’s lifecycle hooks
  • hidden state mutations
  • inherited side effects
  • undocumented assumptions

The deeper the hierarchy becomes, the harder it is to reason about behavior.

Example:

DashboardWidgetComponent
  → extends MetricsContainer
    → extends AsyncStateHandler
      → extends FeaturePermissionBase

Now ask yourself:

  • Where does loading state come from?
  • Which class unsubscribes observables?
  • Which lifecycle hook is overriding another?
  • Which class mutates shared properties?

Nobody knows anymore.

This is known as the fragile base class problem.

A “small improvement” to a parent class creates unintended behavior across dozens of subclasses.

Composition Solves This Differently

Composition breaks behaviors into small reusable units.

Instead of inheriting everything from a parent class, a component only pulls in the behaviors it actually needs.

That means:

✅ Smaller responsibilities ✅ Better testing ✅ Lower coupling ✅ Easier refactoring ✅ More predictable behavior

Instead of:

extends SharedComponent

You compose behavior through:

  • directives
  • services
  • utilities
  • standalone features
  • hostDirectives

Angular Finally Embraces Composition

Angular introduced hostDirectives, and it changes how reusable behavior can be structured.

Instead of inheriting behavior:

export class CustomerCardComponent extends TrackingBase

You attach reusable behavior directly to the host component.

Example:

@Directive({
  selector: '[activityLogger]',
  standalone: true
})
export class ActivityLoggerDirective {
  @HostListener('click')
  onClick() {
    console.log('interaction tracked');
  }
}

Another behavior:

@Directive({
  selector: '[hoverHighlight]',
  standalone: true
})
export class HoverHighlightDirective {
  @HostBinding('style.border')
  border = '1px solid transparent';

@HostListener('mouseenter')
  activate() {
    this.border = '1px solid #4f46e5';
  }
  @HostListener('mouseleave')
  deactivate() {
    this.border = '1px solid transparent';
  }
}

Now compose them into a component:

@Component({
  selector: 'app-customer-tile',
  templateUrl: './customer-tile.html',
  standalone: true,
  hostDirectives: [
    ActivityLoggerDirective,
    HoverHighlightDirective
  ]
})
export class CustomerTileComponent {}

No inheritance.

No deep hierarchy.

No fragile base class.

Just reusable isolated behaviors.

Why This Scales Better

With composition:

  • behaviors remain independent
  • features stay modular
  • debugging becomes easier
  • testing becomes isolated
  • onboarding new developers becomes faster

A developer can open a component and instantly see:

hostDirectives: [
  ActivityLoggerDirective,
  HoverHighlightDirective
]

That is dramatically easier to understand than:

extends ComplexAbstractTrackingFormContainer

Inheritance vs Composition

Example: The Traditional Base Class Approach

This is common in enterprise Angular apps.

export abstract class UiBaseController {
  loading = false;

beginLoading() {
    this.loading = true;
  }
  finishLoading() {
    this.loading = false;
  }
  logInteraction(eventName: string) {
    console.log('tracking', eventName);
  }
}

Now every component inherits everything:

@Component({
  selector: 'app-order-list',
  templateUrl: './order-list.html'
})
export class OrderListComponent extends UiBaseController {
  loadOrders() {
    this.beginLoading();

// fetch data
    this.finishLoading();
  }
}

Problems appear later:

  • unrelated components inherit tracking logic
  • lifecycle collisions emerge
  • parent class becomes massive
  • every child depends on internal parent behavior

Refactoring the Same Logic Using Composition

Step 1 — Extract Loading State Into a Service

@Injectable()
export class LoadingStateService {
  private loading = signal(false);

readonly isLoading = this.loading.asReadonly();
  start() {
    this.loading.set(true);
  }
  stop() {
    this.loading.set(false);
  }
}

Step 2 — Extract Tracking Into a Directive

@Directive({
  selector: '[interactionTracker]',
  standalone: true
})
export class InteractionTrackerDirective {
  @Input() trackingName = '';

@HostListener('click')
  handleClick() {
    console.log('tracking', this.trackingName);
  }
}

Step 3 — Compose Only What You Need

@Component({
  selector: 'app-order-list',
  templateUrl: './order-list.html',
  standalone: true,
  providers: [LoadingStateService],
  hostDirectives: [InteractionTrackerDirective]
})
export class OrderListComponent {
  constructor(
    public loadingState: LoadingStateService
  ) {}

loadOrders() {
    this.loadingState.start();
    // fetch data
    this.loadingState.stop();
  }
}

Now the component is:

  • cleaner
  • easier to test
  • easier to understand
  • fully modular

Testing Becomes Simpler

Testing inheritance chains is painful because behaviors are mixed together.

Composition isolates concerns.

Example:

describe('InteractionTrackerDirective', () => {
  it('should track clicks', () => {
    // isolated test
  });
});

You no longer need giant integration tests just to validate shared behavior.

When Inheritance Still Makes Sense

Composition is not a ban on inheritance.

Inheritance is still useful when there is a true is-a relationship.

Good examples:

abstract class FormRenderer
abstract class ApiRepository<T>
class CsvExporter extends FileExporter

These represent genuine abstraction contracts.

The problem starts when inheritance is used only for:

  • sharing utility methods
  • sharing UI behaviors
  • sharing state
  • sharing lifecycle logic

That’s where composition is usually the better choice.

A Practical Rule for Angular Teams

Before writing:

extends SomethingBase

Ask:

“Am I modeling a real type hierarchy, or do I simply want reusable behavior?”

If the answer is reusable behavior:

Use:

  • directives
  • services
  • utilities
  • composition
  • hostDirectives

instead.

Real-World Impact in Enterprise Applications

The biggest performance bottleneck in large frontend systems is rarely rendering.

It’s maintainability.

Teams lose weeks because:

  • nobody understands the inheritance chain
  • behavior is hidden
  • changes create side effects
  • debugging becomes archaeology

Composition reduces that complexity dramatically.

And Angular’s modern APIs are clearly moving in that direction.

Final Thoughts

Inheritance feels fast at the beginning.

Composition stays fast years later.

That distinction matters in large Angular applications where maintainability determines delivery speed.

The best Angular architectures today are trending toward:

  • standalone components
  • isolated directives
  • signal-driven state
  • composable behaviors
  • feature modularity

And away from:

  • giant base classes
  • deep inheritance trees
  • hidden framework magic

The next time you reach for extends, pause for a second.

You might not need a parent class.

You might just need composition.

This article builds upon patterns shared by Roberto Hecker in his exploration of Angular


메타데이터
post_id
d98d5f213e04
slug
your-angular-base-class-is-a-time-bomb-heres-the-modern-fix-d98d5f213e04
url
https://medium.com/@Angular_With_Awais/your-angular-base-class-is-a-time-bomb-heres-the-modern-fix-d98d5f213e04
canonical_url
https://medium.com/@Angular_With_Awais/your-angular-base-class-is-a-time-bomb-heres-the-modern-fix-d98d5f213e04
author_url
https://medium.com/@Angular_With_Awais
status
ok
fetched_at
2026-06-09 15:37:30