Angular @ WPS: Best Practices for Not Getting Lost When Building Real-World Applications

Angular @ WPS: 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.

 

Web Applications are not easy to develop

Client side Web Applications are real software and should be developed like real software.

Using web interfaces has become an extremely popular choice for many applications, for many reasons. First, they are easily accessible. Secondly they have (almost) no rollout and distribution issues and work cross-platform. And of course they can look really nice and may be developed pretty efficiently.

But these benefits don’t come for free. Without a good architecture and appropriate development techniques you may and up in a mess:

  • web browsers have incompatibilities, standards implementation is rather “fluent”
  • javascript is interpreted at runtime, by just those various browsers, and it is dynamically typed
  • managing hundreds of HTML files and keeping potentially hundreds of CSS rules consistent (that is, non-conflicting) is very difficult
  • many problems must be solved in every app (a visual design system, user-friendly form controls, navigation, …)

Angular addresses many of these issues:

  • it hides DOM manipulations and implements the cross-browser-compatibility,
  • introduces components and modularizes their code & styling,
  • allows to test the building blocks (like components and services) separately (with real unit tests),
  • allows to modularize an app and re-use these modules,
  • it incorporates Typescript tooling which allows us to…
    • … let the compiler and static typing help us make fewer mistakes
    • … apply static analysis for keeping the architecture in shape
    • almost forget about Javascript and be more productive
  • and it has ready-to-use solutions for many standard problems

For these reasons, we have embraced Angular and find it suitable for many efforts. Of course, there are other frameworks and tools and a lot of discussion about their benefits and shortcomings, but that is not what I want to discuss here.

 

Angular Web Applications are not easy to develop, too

A fool with a tool is still a fool.

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 as a glimpse into our “Angular Pattern Library”.

I’ll start with best practices concerning the design of components, which may be considered the essential heart of what you do when writing an angular app. Everything else (Services, Routing, Modularization, …) exists in order to make components work.

To get Angular Components right, we think you have to:

  1. embrace their usage as modularization technique: 👍 Factor out Components
  2. take care of visual modularization: 👍 Manage Component Layout Boundaries
  3. unit test components by mocking others: 👍 Mock Components
  4. favor delegation over inheritance: 👎 Avoid Component Class Inheritance with Template Duplication
  5. use advanced techniques: 👍 Use Advanced Template Transclusion

There are quite a few more DOs and DON’Ts in our library around components, services, project structure, testing. We will select and write about them in the future.

 

👍 Factor out Components

Templates are code and code must follow the DRY principle. (Don’t Repeat Yourself)

That one’s obvious, but you cannot stress it enough. There is a tendency to accept duplication when it’s only a few lines of HTML.

Consequence is that components whose typescript class is almost empty can still be a valuable component because it’s value may be in the template. In conclusion, you’re more likely to create more components following this advice.

To illustrate the temptation look at the following example that shows a little duplication of template code…

@Component({
  selector: 'foo',
  template: '<h1>{{headline}}</h1><p>{{text}}</p>'
})
export class FooComponent {
  headline = 'Foo!';
  text = 'foo text';
  constructor(private fs: FooService) {
  this.headline = this.fs.getHeadline();
  }
}

@Component({
  selector: 'bar',
  template: '<h1>{{somethingCompletelyDifferentFromHeadline}}</h1><p>{{textButWeCallItDifferent}}</p>'
})
export class BarComponent {
  somethingCompletelyDifferentFromHeadline = 'Bar!';
  textButWeCallItDifferent = 'bar text';
}

Now if that was the whole application, of course this is a negligible duplication. But be aware as you move on adding more template to these components, you’ll find yourself copying changes back-and-forth.

Extracting a component is not that hard to do at this point, so let’s just do it.

@Component({
  selector: 'headline-and-text',
  template: '<h1>{{headline}}</h1><p>{{text}}</p>'
})
export class HeadlineAndTextComponent {
  @Input() headline: string;
  @Input() text: string;
}

@Component({
  selector: 'foo',
  template: '<headline-and-text [headline]="headline" [text]="text"></headline-and-text>'
})
export class FooComponent {
  headline = 'Foo!';
  text = 'foo text';
  constructor(private fs: FooService) {
  this.headline = this.fs.getHeadline();
  }
}

@Component({
  selector: 'bar',
  template: '<headline-and-text [headline]="somethingCompletelyDifferentFromHeadline" [text]="textButWeCallItDifferent"></headline-and-text>'
})
export class BarComponent {
  somethingCompletelyDifferentFromHeadline = 'Bar!';
  textButWeCallItDifferent = 'bar text';
}

They key here is that FooComponent and BarComponent are indeed different but have (partially) a common template that they refer to. Now, imagine we wanted to always add a standard footer to a headline-and-text structure.

@Component({
  selector: 'headline-and-text',
  template: '<h1>{{headline}}</h1><p>{{text}}</p><footer>Call {{number}}!</footer>'
})
export class HeadlineAndTextComponent {
  @Input() headline: string;
  @Input() text: string;
  number: string;

  constructor(ps: PhoneService) {
  this.number = ps.determinePhoneNumber();
  }
}

Now even though we’ve added another service dependency, there is no change to both FooComponent and BarComponent. Therefore, adding more markup and likely CSS to style the headline can be done in one place.

 

up next

Factoring out components can introduce broken layouts, read about how to Manage Component Layout Boundaries in the next article.

2018-12-07T15:34:48+00:007. December 2018|