Inheritance in Angular2 Components

In Angular 2.3 component inheritance was introduced [ reference: changelog ]. This issue was debated quite a bit as JavaScript lends itself more towards functional programming. Given all the discussion about it, I wanted to take a minute and show you how, why and when to use it.

I recently had an oppertunity to talk about this on AngularAir, if you haven’t seen I recommend giving the episode a watch.

ngAir 95 — TIL #2

So let's go back to the fundamental of what inheritance actually is before we dig in. Inheritance is a object-oriented fundamental that allows us to derive properties and functions from the base class [ reference: wikipedia ]. Most of us have used this quite often throughout our career. Problem is JavaScript has prototypical inheritance which is actually different from classical inheritance you see in languages like Java and C#. MDN has a great excerpt on this so rather than butcher this, I’ll just post that:

When it comes to inheritance, JavaScript only has one construct: objects. Each object has an internal link to another object called its prototype. That prototype object has a prototype of its own, and so on until an object is reached with null as its prototype. null, by definition, has no prototype, and acts as the final link in this prototype chain.

Us old school JavaScripters’ probably even remember trying to fake this until it finally became syntax sugar in ES2015.

So how does this apply to Angular? Let’s say we have a base component called Button and a new requirement comes in that says we want this same button but when you click it a progress indicator is shown. Well prior to Angular 2.3.0 release, you could implement inheritance but none of the properties of the base class would be actually bound to the component, however, you could still share inherited methods from the base class. Essentially it would not get instantiated as a Component. Now that it landed in the core, it will actually chain the instantiation so your child component can share inputs of the base without having to re-implement them. So enough talking, let's get to some code!

Here I have implemented a component called BaseComponent that just has a simple input and a template using the input:

@Component({
selector : 'my-base',
template: `
<div>
Am I the base component: {{isBase}}?
</div>
`
})
export class BaseComponent {
@Input() isBase: boolean = true;
}

Next, I will create a component called InheritedComponent that shares the isBase input and overrides the component decorator metadata with its own selector and template:

@Component({
selector : 'my-inherited',
template: `
<div>
I'm def not the {{isBase}}!
</div>
`
})
export class InheritedComponent extends BaseComponent {}

Now we can use each component just as you would expect with proper inheritance:

@Component({
selector: 'my-app',
template: `
<div>
<my-base></my-base>
<hr />
<my-inherited [isBase]="false"></my-inherited>
</div>
`
})
export class App { }

In the code above, I don’t pass an input to the my-base component since it has a class member variable initializer of true. However, in the my-inherited component, I pass the property of isBase as false. This shows how you can mix and use inputs from base and child components. Awesome right? Here's a plunkr for you to explore more with…

On the show, we briefly discuss one topic that could be confusing which is Does the component decorator metadata allow for inheritance as well? The answer is no, you can’t inherit metadata properties described in the decorator into another component. Fundamentally, it makes sense since you are describing a totally different object than what you are actually extending the class from. If you really wanted to do this, you could do something crazy like:

const base = { template: `<h1>Hi</h1>` };
@Component(Object.assign({
selector : 'my-base'
}, base))
export class BaseComponent {}
@Component(Object.assign({
selector : 'my-child'
}, base))
export class InheritedComponent extends BaseComponent {}

If you look at that you can probably understand why it doesn’t inherit. Please don’t do the above ^^ it was just for demonstration purposes.

On ngAir, we were experimenting to see if host attributes in decorator metadata the component would be inherited too. The answer is no, however, not all is lost. You can still do those attributes via class decorators, so rather than:

@Component({
selector : 'my-base',
template: `<h1>hi</h1>`,
host: { 'style.color': 'yellow' }
})
export class BaseComponent {}

you could do:

@Component({
selector : 'my-base',
template: `<h1>hi</h1>`
})
export class BaseComponent {
@HostBinding('style.color') color: string = 'yellow';
}

this works because we are attaching the binding to the class that is actually getting extended rather than the component metadata.

Thanks for joining me and hope this helped someone with their children ;P!

I’m #ngPanda

I hope you enjoyed my story, if you liked it follow me on Twitter and Githubfor more JavaScript tips/opinions/projects/etc!