Design better Angular components
On the path to perfection we thrive.
Don’t create wrappers
How many times have we written a component that had just one top-level div with some styles?
@Component({
selector: ’my-card’,
template: `
<div class=“wrapper”>
...some other markup inside
</div>
`,
styles: [`
.wrapper {
border: 1px solid black;
border-radius: 25px;
background: white;
padding: 15px;
}
`]
})
There is a better solution — use host
selector instead of creating yet another DOM element.
@Component({
selector: ’my-card’,
template: `
...same markup
`,
styles: [`
:host {
…
}
`]
})
This way it’s clearer to read and a tiny bit easier for the browser to process the tree.
Don’t subscribe
Most of the subscriptions don’t need to be there. It’s much shorter and clearer to use async
pipe instead. When subscribed manually you would:
- Create a prop holding data in the component
- Imperatively subscribe to a stream
- Assign values from the stream to the prop
- Store subscription/Subject to unsubscribe later
- On component destroy unsubscribe
- Use the prop in the template
Not only it contains lots of steps, but also is verbose and imperative. If used async
pipe you would only need:
- Assign observable to a prop in the component
- In template write something like
<div *ngIf="stream$ | async as data">
- Reference
data
in the template to access the data
That’s it! Much simpler and subscribe/unsibscribe is controlled by the pipe for your.
Be careful with RxJS
Whenever you subscribe you should always unsubscribe. Regardless whether you think Angular will finish observable or not. It’s better to double check than regret later.
Whenever you create subjects you should always complete them on destroy.
If not followed these two simple rules you will cause memory leaks due to hang subscriptions and possibly observe an unexpected behavior (e.g. component got destroyed before the request was finished, then data arrives and subscription callback gets executed).
The only case you don’t need to manually unsubscribe is when you are using the async
pipe. It does that for you.
Always cover your changes with unit tests
Unit tests are written to help ourselves avoid regressions, ensure the business logic works the way it was designed. Furthermore, unit tests enable you to develop your front-end before back-end is ready.
Instead of waiting for back-end API endpoint to be ready write unit tests. Gradually from a service fetching the data straight to the place the data is being used. This way your code is ready and when back-end arrives it simply works! No need to wait for anyone. You only need a contract (agreement on the data format it will give you and the endpoint path) and that’s it.
Furthermore, well-covered code with tests is an easy target for refactorings as tests enable us to verify the code works as expected after changes are done.
Always mock your dependencies in tests
It’s vital that you mock, at least your constructor’s, injections. This way when the external API (read services) changes your tests don’t break. Furthermore, it’s easier to test different behaviors when working with mocks, because you simply need to write .mockReturnValue(...)
and that’s it. No need to manipulate real services to alter the whole app’s state to achieve the desired result.
Also, the point of tests is to test a specific piece of code. If you don’t mock dependencies then you are testing them as well which is not a good idea. You might want to do so in rare cases to double ensure some functionality. But as a rule of thumb — you mock all external deps as unit tests are supposed to be numerous, simple and quick. Having other parts of code taking part in it will significantly slow your tests development, execution and perhaps reliability.
Keep semantics in mind
When working in SPAs it’s easy to forget that we actually write HTML and which can be read by assisting technologies (screen readers etc). All our code can become DIVs which is a terrible sign.
- It’s harder to navigate
- It shows that developer doesn’t know any other tags
- It makes it hard or impossible to use the website for assistive technologies users
- It can significantly worsen the UX (user experience) as mimicking other elements is hard and also is pointless as you can get it at no cost by simply using the correct tags
- Your SEO suffers as search engines account for that as well
To improve the situation strive to use correct tags e.g. nav > ul > li
for navigation tabs, h1
for page titles, button
for clickable elements that do some action on the page, a
for links and so on.
Headings
Also, it’s worth mentioning that you should use headings according to their semantics and not to how it looks or named in Figma. To choose the heading level you should follow simple rules:
- one top level heading
h1
per page. Not more, not less. You could make it visible only for screen readers if it’s absent in the UI by design. - headings if collected should represent a table of content akin those in the books — no in heading levels gaps or whatsoever
This will make navigation for screen readers much easier and also users who don’t want to be distracted from the content could switch to a nice view of a “reader” mode in the browser.
Buttons and links
Keep in mind that button is a button. It only performs actions on the page. If it’s a navigation button, then it’s not a button but a link. Same goes for the link — it only does the navigation, if you need to show a modal or make any other action which is not navigation — you should use button instead. Just write a couple of styles to make it possible for button look like a link and for link to look like a button.
This way not only you use correct tags (and show that you understand how things should be), but also improving the UX since buttons and anchors behave differently. If an anchor is clicked with a Ctrl (Cmd) or middle mouse button it would open the link in a new tab which button cannot do.
Alts
I often see developers struggle around alts (alt
attribute of the img
tag). They often skip the attribute, leave empty when it shouldn’t be, or make it have a content when it shouldn’t, struggle with what should be written there.
However, there is an easy rule to follow which will answer all questions about it and will make it simple once and for all. Imagine that you are reading the page to your friend over the phone (that’s a very similar experience for the users of screen readers) — how would you read the page so that your friend doesn’t miss anything?
You will definitely skip the icons in the menu. What’s the point of reading that “there is a house icon next to the link called “home” as it doesn’t convey any meaning by itself, it’s just a visual helper that can be removed and page would lose no meaning to the consumer. However, if there is an image in the article you would want to describe it to your friend in the most expressive and shortest way possible. Let’s take a photo below for an example.
While this might be not the best explanation of the image it’s definitely better than “a man” or similar developer like to write if ever.
Keep constructor simple
Sometimes, our components become so complex that their constructors inject 7 services or more. The code quickly becomes cumbersome and hard to test, hard to modify. If you see such thing in your project — it’s a sign that there is something wrong. Either your component does too much and should be broken down into a few smaller components, or the logic should be extracted into a service.
Small simple components are easy to work with, test and are a positive experience to maintain.
Separate concerns
It’s easy to forget in Angular where everything is built with OOP that we have services if we can do everything in components. It’s an error prone approach and is not a scalable one either.
It’s encouraged by the Angular team that components contain only logic necessary to render the data (representation logic). Everything else should go into services. This way you split your code into smaller pieces which makes it easier to test, the code is more reusable (you can quickly inject the service into another component and use existing methods) and working with small files is simpler as well.
Use OnPush
With Default change detection strategy it’s easy to do data changes in the code, however it causes unnecessary change detection cycles for the users. While we as developers usually don’t notice it, users don’t have such powerful machines and thus are more likely to experience UI lags caused by heavy computations.
Furthermore, if you are developing a library and it’s not adapted to working with OnPush strategy — it becomes painful to use it for those projects which do use the strategy as library components stop re-rendering.
Moreover, less rounds of change detection cycles mean that you use less CPU and RAM and hence more energy efficient which is crucial for mobile users.
Use Storybook
It’s easy to discover in teams where there is more than 3 developers that the same component can be implemented more than once, simply because there is no awareness that it was already implemented.
Storybook works like a catalogue where you can check for all reusable components, see how they look, how should be used what can be adjusted and so on.
Make sure everyone knows that there is a Storybook available in your repo, it’s maintained and should be checked for any components encountered first time.
Write components, not classes
For people used to working with Bootstrap or any other UI framework the first thing that comes to mind when you need to create a button (or any other simple one block component) to create a global class. While it works, a better approach would be to create a component which uses a selector like button[my-button]
and projects the content right away.
This way the styles are encapsulated in the component. You don’t need to go over the project looking if a sub style of the button is used anywhere. Developers don’t need to remember all classes and their combinations available — they simply need to set inputs of the component and those are autocompleted by the IDEs.