Hi, I’m Michele Stieven, a front-end developer and consultant in Italy.
Over the last 2 years I’ve been working 90% of the time with Angular since it’s one of the most requested technologies in this field in my country. I’d like to make a quick recap of the problems I face most as a consultant, and I’d like to address some issues or questions that I get asked maybe too often!
Straight to the points!
I may seem a bit arrogant with this one but it is such an important topic that it drives me crazy when it’s taken lightly: you absolutely cannot underestimate the importance of writing clean code. I don’t care if you’re using a framework and you think to be an advanced developer just because of that: if I can’t understand your code, there’s a problem. I don’t care if you say you understand it: in fact, 90% of the times you’re just lying to yourself (artfully or not) and if you took a look at the code you wrote some weeks before, I bet you couldn’t. Your first job as a professional developer is to write clean and clear code. Forget frameworks, forget libraries: there’s something more important. If you don’t understand that, you’ll deliver poor work to your boss who will need to throw away everything you’ve done in the past months and ask consultants for help. As a general guideline:
- Write comments explaining what a function/class/method/property does. “But we name things with meaningful names, we don’t need comments!”, wrong. If you’re talking about a 4 lines function, maybe. For everything else, make comments.
- Write meaningful comments. Be smart: if you’re using typescript, you can get your software’s documentation almost for free. Write meaningful comments because they WILL BE your documentation (use compodoc for Angular, otherwise typedoc, or any other generator that may work with your tools)
- Put the files in the right directories/modules. Isn’t it strange that something in your CoreModule is using something which is under shared/toolbar/models? Yep, it is. Make clear what must be used as an inner dependency in your project and what not. If you write a module, make an index file exporting only what other modules should see. And even if you technically could import something from a module, ask yourself if it feels right, before doing that.
- Use the linter (eslint, tslint…). I recently had a debate with someone who didn’t use a linter because it was “made for unexperienced devs who don’t know how to name things”. What a nightmare.
Presentational not-so-presentational Components
In your application, 80–90% of your components should be presentational. A presentational component should take Inputs and emit Outputs, nothing else. Yes, it may have its little inner state: that’s not a problem. It’s perfectly fine for a TabsComponent, for example, to accept an index from outside but keeping the currentIndex in a property. As long as it has an indexChange output who tells the parent about the change (bonus: having the index/indexChange pair will let you use the 2-way-binding syntax [(index)] in Angular!). Does that make the component less-stateless? Yes, but it’s still manageable from the outside and it can also work on its own. Win win.
THIS is what you shouldn’t do in your presentational components:
- Having dependencies from other modules (apart from Angular or “really core” ones).
- Modify the Input data directly: if your component is given an array or an object, modifying it will affect the parents’ data, too. I won’t go into details about this one, but let’s say you’re using Redux and that object comes from a Store selector, what do you think would happen? Yep, you’d mutate the Store directly from an “innocent” presentational component. (psssst, using Angular and ngrx? Include ngrx-store-freeze as soon as possible). Should you need to modify the Input data, clone it. Is it too expensive? There might be a design problem.
- Use models from other modules just because they share the same shape: is your FilterComponent using the ToolbarFilterItem interface which comes from another module? It shouldn’t. It should have its own FilterItem interface, even if it happens to be identical. If a developer is working on the ToolbarModule, he/she won’t break your component. Oh, by the way: learn how to work with branches (git or whatever) even if you’re not in a team. Trust me.
- Not using ChangeDetectionStrategy OnPush. You should be able to switch it on painlessly in your presentational components, maybe you’ll need to use the ChangeDetectorRef in order to trigger the CD if your component has its inner state, it depends. It’s a tremendous helper for performance, especially if you are binding functions in your Angular templates (do it only with OnPush!). If you need derived data, apply getters/setters on your Inputs, or use the ngOnChanges lifecycle hook. If however you’re using OnPush, you can do this without worrying too much:
However, you should prefer getters/setters/ngOnChanges, for obvious reasons (in the above example, OnPush triggers Change Detection for every Input change, even if it’s not related to the filtered items).
What’s a container? It’s the component which hosts all the presentational components of that “view”, or route: it’s the only one which should use services to get/update data, which should use the Redux store, which should dispatch actions, and which should pass its inner state (or slices of that) to the child components. All the child components are presentational and should not even know where the data has come from or what the consequences of their Outputs will be. If tomorrow you decide to include a state-management library or to get rid of it, your presentational components won’t break.
Since the containers can “hold” the 99% of the logic of that view, they may become huge and unmaintainable. How do we solve this?
- Delegate responsibilities to a service. Is your InvoiceComponent (container) doing a lot of calculations? Create an InvoiceService which will handle complex operations, transformations, etc. You could do this with presentational components too, and if you need a service instance per component, just declare it with a provider in the component itself, instead of providing it at module-level.
- Your containers shouldn’t make assumptions. Let’s say your component has a button and in order to know what to do when the button is clicked, you’ll have to consider other variables (the url, maybe): don’t let the container decide that! The container’s only job is to say that the button was clicked (by calling a method on a service or dispatching a Redux action like “ButtonClick”). It’s the service, or a Redux Effect, which will decide what to do next, based on the rest of the app’s state (ex. Am I under the route /invoices? The button click may create a new invoice. Am I under /estimates? The same button click may create a new estimate).
- Containers may be split. If your route’s view is simply too large, and has completely different sections, create more than one container, each one with its own data (from services or store selectors) and methods (service methods or dispatched actions).
Components with duplicated logic
If two components look quite the same and behave quite the same, you may not need two components. This is such a big topic and it may deserve an article on its own, but here are some guidelines:
- Use Directives. In the Angular world, we have Directives, which are Components without a template. Instead, they can inject the template of the component they’re applied to! In other words, look at directives as a way to incapsulate behaviors and reuse them. Do you need your CardComponent to open a modal when clicked? Don’t make a CardWithModalComponent, please. Make a directive which listens for clicks on the host element and apply it to specific components. Does your Directive need data? Pass it to the directive or declare some other Inputs in the directive itself and use them on the same element. Example:
- Use Content Projection. It’s used way too rarely, yet it’s an amazing practice and a useful pattern that you can use with every component-based framework or library. Take the CardComponent example from above, and suppose you want to create an element to display multiple cards:
This way we don’t have to duplicate our CardComponent’s structure and we’re free to manage all the cards from outside (inputs, outputs, applying directives, etc…), the wrapper could only provide some styling (putting them in a row? Masonry? Carousel?).
Also, you can use ContentChildren to get a QueryList of all the CardComponents, and react to changes.
Obviously, you may still go for the “Don’t” example if you really need to have a different component.
If your Angular forms become huge, there’s really only one thing you should consider:
- Use ControlValueAccessor to create sub-forms. This is what you need to learn if you’re building complex forms. You’ll find lots of tutorials online, and even though it may seem scary, it really isn’t: it’s just an interface that we can implement in our components to make them act as valid FormControls. It helps keeping the form state united, it’s the best practice, it works with Template-driven and Reactive forms at the same time, and it’s really easy to use, trust me. Once you’ve used it once, you can almost copy-paste the implementation for other components. For example, let’s say you have an Invoice Form, this could become the component’s template when you create custom controls:
Other advantages of this technique are:
- Your sub-forms become reusable in other components!
- They’re components: you can still use Inputs and Outputs to customize them (maybe app-invoice-item needs the list of all your products/services to choose from? Pass it as input! Are you creating a new product directly from the app-invoice-item because the user has typed something which is not in the products list? Emit an event, and the container will create the new product with a service/action!)
- You can validate them from the outside just like a regular input (recommended) or from the inside (if the logic never changes) implementing the Validator interface
- Since the whole form’s state is in one single place (instead of being split between sub-components) it’s really easy to serialize/deserialize and to persist in a service, store, browser storage, cookie, database, etc…
- It’s satisfying ;)
Not studying RxJS enough
Angular is built upon 2 things: TypeScript and RxJS. Ignoring them isn’t a wise option if you want to use this framework comfortably. But if TypeScript is quite simple for someone who used to develop with a statically typed language (C, C++, C#, Java…), RxJS could be a tough topic even for the experienced developer. Reactive Programming requires a shift in your brain, but once you grasped it, it really, really pays back. Don’t just look at the surface, go deep and you’ll find that the Angular Team didn’t choose to include it just for fun. In my humble opinion, it’s maybe the greatest choice ever made by the team. You may find that the team has given you a Ferrari, and maybe you’ve always used it like it was a subcompact.
Here’s what you need to learn to be able to overcome even the toughest tasks:
- Learn how to use flattening operators (mergeMap, switchMap, concatMap, exhaustMap) and how they suit different scenarios
- Learn how to combine multiple streams (combineLatest, withLatestFrom, forkJoin, merge, race…)
- Learn how to manage subscriptions and how to filter streams (unsubscribe, filter, take, takeWhile, takeUntil…)
- Look at all the operators: you’ll find that you don’t need to remember them all, as you’ll be able to guess the ones you already saw even months before. You’ll also be able to guess some of them’s behavior just by looking at their name (ex. if you know what takeUntil does, would you guess what skipUntil does?)
- Learn the difference between hot and cold observables and finally realize why this naming convention doesn’t really cover all the scenarios (might deserve another article, what do you think?)
- Learn how to use Subject, ReplaySubject, BehaviorSubject and AsyncSubject (tip: although BehaviorSubjects are absolutely not and will never in the universe be an alternative to Redux, you might consider them as a sort of “state” with an initial value ;))
Also don’t forget that RxJS is a library on its own, and you could use it tomorrow with any other framework or library (or even language!). The reward is guaranteed.
I hope this article was useful, if something else pops into my mind I’ll update it as soon as possible :) If you have any question… well, I’m sorry if it took me ages to respond in other articles, I’ll try my best this time! Cheers!
Post Scriptum: are you from Italy?
I recently decided to open a new YouTube channel which will contain some VLOGs and will soon contain tutorials, tips and tricks, lessons and other funny stuff. Here it is. I hope to find the time not only to make videos, but to make english subtitles available!