Dieser Beitrag ist Teil 4 von 5 in the series WPS @ Angular

This is Part 4 of Best Practices for Not Getting Lost When Building Real-World Applications

by Richard Voß @richard_voss

Since last year we find ourselves developing web applications for our customers using Angular 4, 5, 6 und now 7. Angular is a popular framework (some call it a platform) for developing web-based front-end applications using Typescript, HTML and CSS. It is open-source and primarily driven by Google.

The framework makes it easier and more fun to structure complex user interfaces, promotes re-use of components and — most valuable — allows test-driven development in the front-end.

Despite all of the greatness of Angular, even with the framework there are lots of mistakes left to make. We have identified a few good patterns and pitfalls that we collected.

This article is part of a series that offers a glimpse into our “Angular Pattern Library”. The last article was about how to unit test components. This time will be a short story about a bad decision around delegation vs inheritance.

👎 Avoid Component Class Inheritance with Template Duplication

At this point comes the story of a rather painful experience our team made.

We’re still on component development and it is very common that you need to implement a bunch of similar components. The key here is similar, that means some aspects are equal and some aspects are different. Since components are built around a class, why not let these similar components have a common base class to put the similarities there?

However, when looking at a component, you have to be aware that it consists of a class and a template. These two are rather tightly coupled to each other. So factoring out a superclass only affects half of a component: Factoring out a piece of template cannot happen using inheritance.

How to rather not implement similar components

So this is a typical Composition over Inheritance moment, but we missed it back then when we developed various “editors”. They were supposed to be similar – based on a reactive form, having “save”, “cancel”, and maybe other buttons. But also different: the form structure and controls but also the action performed on “save” would be different.

To illustrate the problem here, we assume two editors for editing an apple and a banana.

abstract-editor.ts
abstract class AbstractEditor {
  form: Form;
  readonly: boolean;

  abstract initForm();
  abstract submitForm();

  utilities();
}
apple-editor.component.ts
@Component()
class AppleEditorComponent extends AbstractEditor {
  initForm() { … }
  submitForm() { … }
}
apple-editor.component.html
<form (ngSubmit)="submitForm()" [formGroup]="form">
  (apple stuff)
</form>
banana-editor.component.ts
@Component()
class BananaEditorComponent extends AbstractEditor {
  initForm() { … }
  submitForm() { … }
}
banana-editor.component.html
<form (ngSubmit)="submitForm()" [formGroup]="form">
  (banana stuff)
</form>

Now, this looks like another case of template duplication. In reality this pretty soon became more than just a line, but many lines and quite an array of mapped variables.

Template Duplication with Shared Superclass

The red arrows show the coupling between the templates of the subclass components and the superclass methods.

Extracting a component does not solve the problem

The problem is, now that you’re here, simply extracting the similarities to another component is not the whole story. This is roughly how we did it:

editor-shell.component.ts
@Component()
class EditorShellComponent {
  @Input()
  form: Form;
  @Output()
  submitForm: EventEmitter<void>()
}
editor-shell.component.html
<form (ngSubmit)="submitForm()" [formGroup]="form">
  <ng-content></ng-content>
</form>
apple-editor.component.ts
@Component()
class AppleEditorComponent extends AbstractEditor {
  initForm() { … }
  submitForm() { … }
}
apple-editor.component.html
<editor-shell (submitForm)="submitForm()" [form]="form">
  (apple stuff)
</editor-shell>
banana-editor.component.ts
@Component()
class BananaEditorComponent extends AbstractEditor {
  initForm() { … }
  submitForm() { … }
}
banana-editor.component.html
<editor-shell (submitForm)="submitForm()" [form]=“form”>
  (banana stuff)
</editor-shell>

Okay, so now we have removed some redundant template code, which is good. But to be honest, the use of <editor-shell> is still quite redundant and violates DRY: All parameters passed to and from the component must look the same, because they’re from the superclass. Consequently, in our project, this was very painful because we were talking about 6 or 7 variables and functions. Further it was painful to create another editor, but most painful to frequently change all these places. (Yes, I’ve used the word painful three times in a row here, you get it.)

Conceptually, we have made it worse, if you look at this picture:

Coupling Hell

 

The red dashed arrow shows the most problematic coupling of AbstractEditor and EditorShellComponent, because they are non-obviously coupled. If you look into EditorShellComponent‘s code you won’t see it. Their interaction only works when they are correctly wired each and every time again.

With this construct you get all defects resulting from coupling and non-obvious coupling: change propagation, unexpected breaking of “unrelated” code, tedious, error-prone repetition.

But what about a solution?

First, I want to tell you: At this point, there is no easy way out of this. That’s why we call this approach a DON’T (thumb down emoji 👎). The best thing to do is not ever getting here. But we will discuss approaches and get back to this example in further articles

A good strategy to come up with a solution, is to first enhance our toolbelt with advanced transclusion (next article), dynamic component creation and component scoped services…

Seriennavigation<< Angular @ WPS Part 3: Focused Component TestingAngular @ WPS Part 5: Templates can do More >>