Skip to content

3 Tips For Building Better Angular Components (And a Bonus One)

Don’t reinvent the wheel

Build on top of existing work. If you need a good starting point for your components, use Angular CDK. It’s a set of behavior primitives for building UI which will save you a lot of time. Besides saving you time, It will also serve as a good example of well-implemented behavior.

If you need a date picker component for your feature, start with an existing component like Angular Material Datepicker or Ng-Bootstrap Datepicker (or any other fitting date-picker that is already implemented), even if they only partially fit your requirements. Make your feature work, then fine-tune, not the other way around.

You can always replace the implementation or make adjustments to all your instances you will follow the following advice:

Wrap outside components

Depend on abstraction, not on concrete implementation - this is what the D from the SOLID tells us.

This advice is especially important when you are working on a cross-platform application. You don’t want to pollute your codebase with ramifications, containing platform-specific code (I imagine doing so would create such a mess in your code that it would become obvious very quickly).

You could replace the implementation of your date-picker in a matter of hours instead of weeks when it’s wrapped, and you won’t even need to touch the usages (as long as your abstraction fits well enough - you will quickly find out when it doesn’t).

Another argument for wrapping is that you can fine-tune the components for the exact requirements of your app. Maybe your selects always use an external API to fetch the elements - just wrap your select component into another layer, so your colleagues won’t need to fetch items manually every time they use the select. The higher the level of abstraction, the easier to avoid bugs and assure consistency.

The last argument I want to bring to the table: wrappers make respecting the Responsibility Principle easier. Taking the same select example, the hierarchy can be constructed in the following way:

  1. <select>: Low-level browser selects - the lowest level component. These components serve as building blocks for your high-level components
  2. <app-select>: Project-level select - the select wrapper. It can wrap either low-level browser selects or selects from outside libraries. This component assures consistent API and allows users to specify the list of items and the current item. That way, the developers don’t have to create a loop in the DOM to render every item - they use a list of items and specify the current one.
  3. <user-select>: Highly specialized select. This select does not need the list of items, since it is already responsible for fetching the items. It does not leak details about how it fetches users - through an api call or by reading a JSON file. All you need to know to work with it is its output. It might require only the currently selected user or nothing at all.

From the structure above you can see that sometimes is useful to wrap already wrapped components, just to respect the SRP. If your view is responsible for handling form validation, fetching select items, and handling the value of a flag - it might have too many responsibilities. By creating highly specialized wrappers, you can ensure that each component knows exactly what to do, even if it won’t be reused.

On the other side, your components should handle everything they are advised to do, therefore:

Make sure your component does not leave its responsibilities to the user

Let’s examine an example. You are building a 12-hour time picker and it looks something like this:

12-Hour Time Picker

I won’t include the template, but let’s assume that the template just binds to the inputs directly and uses the output to emit a new value:

@Component({...})
export class TwelveHourTimePicker {
	@Input() hour: string;
	@Input() minute: string;
	@Input() second: string;

	@Input() meridiem: 'AM' | 'PM';
	@Output() meridiemClick: Observable<void>;

	// ...
	@Output() hourChange: Observable<string>;
	// ...

	//The remaining code and the rest of the outputs were omitted for simplicity
}

To update the meridiem, the user of this component has to:

  1. Provide the meridiem value (which is fine, but if the user does not provide an initial value, the meridiem button will be empty)
  2. Handle the click on the meridiem button
  3. Based on the previous meridiem value - update the meridiem

So, to use this component, the user has to make sure it provides an initial value for meridiem and have in the code something like:

toggleMeridiem() {
	if (this.meridiem === 'PM') {
		this.meridiem = 'AM';
	} else {
		this.meridiem = 'PM';
	}
}

The users are in a position where to use the TwelveHourTimePicker they need to wrap it inside another component and move the logic like the toggleMeridiem method inside since toggling the meridiem is clearly not the responsibility of a component that uses a time picker inside.

Hopefully, you can see the point I’m trying to make - don’t make the users do the job for your component.

Bonus: Use the bright theme

Everyone is using the dark theme thinking they are cool. The truth is: that the bright theme is so much better than the dark theme. Here are my arguments before you hit the *dislike button and close the page:

  1. It’s easier to see the text. Dark themes don’t usually use 100% black and 100% white. It’s more likely they would use a dark gray for the background and a brighter shade of gray for the text, which makes the contrast ratio lower (especially for your aging eyes).
  2. Increase awareness. You probably use the dark theme while scrolling before bed, therefore the dark theme might be associated with relaxation and sleepiness. You won’t fix your components if you want to sleep all the time.
  3. The dark theme is mainstream. After almost every service has a dark theme, it’s not that exciting anymore.
  4. You can’t use this lovely wallpaper with the dark theme - it’s too bright

Bright, minimalistic wallpaper, with drawn clouds and a blue background


*The dislike button might be absent on this page