Building Custom Angular Libraries to Write DRY Code
As software developers, writing code is a fundamental part of our daily routine. At Halodoc, our team of developers works on our codebase every day to enhance and maintain our software. However, without proper coding practices, our codebase can become excessively large and overly complex. To avoid this, we have implemented the DRY (Don't Repeat Yourself) principle, which means we strive to avoid writing repetitive code by creating reusable code snippets.
Since we work on multiple projects, it's crucial for us to make our code adaptable and reusable. To achieve this in our Angular-based web applications, we develop Angular libraries that enable us to share code across different projects. In this blog post, we'll discuss the process of creating an Angular library, including how we extract shared code from our web projects and integrate it into the library.
What is the DRY pattern?
The DRY pattern is a principle that emphasizes the importance of reducing redundancy in code. Instead of writing the same code over and over again, we strive to create reusable code snippets that can be applied across different parts of the codebase. By avoiding repetitive code, we can improve the readability, maintainability, and scalability of our software.
There are many benefits to using the DRY pattern in software development. One of the most significant advantages is that it can save time and reduce errors. When we write code that can be reused, we don't need to spend as much time writing new code, and we're less likely to introduce bugs or errors. Additionally, the DRY pattern can make it easier to modify and update code since changes only need to be made in one place.
There are many ways that the DRY pattern can be applied in code. For example, we can create functions or methods that perform common tasks or calculations, or use variables or constants to store values that are used multiple times in the code. By creating these reusable code snippets, we can improve the efficiency and readability of our codebase.
What are Angular libraries?
Angular libraries are reusable code modules that can be shared across multiple Angular projects. They can contain components, services, directives, and other types of code, and can be easily imported and used in other projects as needed.
The Advantages of Implementing Angular Libraries
By implementing Angular libraries, we can achieve several advantages, including:
-
Improved maintainability
Because we are no longer duplicating code across multiple projects, it is easier to maintain and update our code over time. We can make changes to a component or service in a single library and have those changes automatically propagate to all of the projects that used that library. -
Faster development
By reusing common code and functionality across multiple projects, we are able to develop new applications faster and with fewer errors. We can focus on building the application's features, rather than spending time duplicating code and functionality. -
Consistency
By using the same set of libraries across multiple projects, we are able to ensure that our applications are consistent in terms of functionality and declaration. This helps us build a more cohesive set of applications.
How Halodoc Implements Angular Libraries
At Halodoc, we faced a challenge in managing multiple projects that shared similar functionalities and components, such as authentication, common config, common UI elements, etc. We used to duplicate the code across multiple projects, making it difficult to maintain and update.
To solve this issue, we developed a set of Angular libraries that contained the common code. These libraries were imported into different projects, allowing our team to reuse the same code across multiple applications. This approach not only helped us avoid repeating code but also reduced the risk of errors and bugs. Additionally, the self-contained nature of Angular libraries made it easier for our team to maintain and update the code over time.
The above diagram shows us the high-level workflow of how we implement Angular libraries. In many cases, we encounter similar or duplicate code in different projects, including services, components, utilities, or assets like images, fonts, or localization files.
To avoid repetition and achieve the DRY code principle, we extract these common elements and create a shared library. This may require modifying the code to ensure it is reusable. Once the library is created, we publish it on our local npm registry, which we have set up using Verdaccio. Finally, we can install and use the published library in both Project A and Project B, enabling us to reduce errors and bugs while improving code maintenance and scalability.
Creating a Custom Angular Library
At Halodoc, we create our libraries using Angular CLI which comes with ng-packagr. Angular CLI provides the necessary commands to generate and manage the libraries, while ng-packagr handles the packaging and publishing of the library to npm. In Angular, we can create a workspace that contains multiple libraries. The following is a command to generate a workspace.
ng new halodoc-commons --no-create-application
This will create a workspace project that includes configuration files and the necessary node_modules
.
In angular.json
we can find newProjectRoot
which defines where our library will be declared in this workspace. By default, the libraries will be located in the projects
folder.
In package.json
we can find scripts to run, build, and test the project, along with the required dependencies.
With the workspace set up, we can then generate a new library project using the following command:
ng generate library eslint-config
This will create a new library project named eslint-config
in the projects
folder within the workspace. The project will include an initial component, service, and module, which can be modified or deleted as needed.
To export our library code so that it can be used by other projects, we use the public-api.ts file
located in the src/lib
folder. This file specifies which components, services, and modules are part of the library's public API.
Extracting Commons to an Angular Library
After an Angular library is set up properly, we are ready to extract and maintain our common code into a shared library. Below is an example of how we extract our linter configurations from Project A and Project B into a shared library project. At Halodoc, we use ESLint as our code linter. It has similar configurations declared in our projects. We will use this case as an example to write DRY code for the ESLint configurations. We will refactor the configurations into the created shared library.
1. In each Project A and Project B we have file .eslintrc.js
. It contains the configuration and rules of the ESLint.
2. First, we redeclare the ESLint configuration files in a shared library project, splitting the code into two parts: the rules and other configurations.
3. Once the code is ready, we update the package version.
4. Publish the shared library to Verdaccio.
npm publish
5. Now that the library is published, we can consume it in Project A and Project B and replace the redundant code in .eslintrc.js
with eslint-rules.ts
and eslint-config.ts
.
5. Finally the redundant code in Project A and Project B is resolved. With the current ESLint configuration that is declared in one place, we can maintain it easier. Anytime we need an update, we just need to update the config in one place. It is also still flexible whenever we need to add special rules for a specific project only.
Publishing an Angular Library
Based on the Angular documentation, there are 2 types of distribution formats in publishing a library, Partial-Ivy and Full-Ivy. Partial-Ivy is the recommended format because it will be supported by different Angular versions across projects which use Ivy, while Full-Ivy will require the same Angular version between the library and the project that consumes it.
Initially, Angular CLI will use Partial-Ivy. We can see the configuration in tsconfig.lib.prod.json
, the "compilationMode"
value is "partial"
. If we want to use Full-Ivy, we can change it to "compilationMode": “full”
.
Publishing an Angular Library can be a complex process that involves potential challenges and issues. One common issue is version conflicts between the library and the project that consumes it. This can lead to runtime errors and unexpected behavior. To avoid this, it is important to follow Angular's recommended distribution formats, such as Partial-Ivy or Full-Ivy. Additionally, it is important to test the library thoroughly before publishing it to ensure that it works as expected.
Another challenge that can arise is publishing the library from a development environment. It can be difficult to ensure that the changes made to the library are properly reflected in the project. The following section outlines the strategies we employ to tackle this particular issue.
Publishing in The Development Environment
Modifying a library in a development environment and then publishing a version can be a tricky business. Picture this scenario: you make changes to a shared library and want to ensure that it's working properly. The common practice is to publish the library and then install it in the project for testing. But what if you need to repeat this process multiple times? It quickly becomes a cumbersome and inefficient process.
To overcome this challenge at Halodoc, we came up with a custom script that allows us to directly implement our changes to the library in the target project. This script helps us to test our changes much more efficiently and effectively.
In the code above, we take advantage of the Node.js child_process
to run the commands using the exec()
method, and a dependency known as chalk
to format the text displayed on the terminal. The cleanDistFolder
code block, as the name suggests, removes the compiled code in the dist
folder. Then it will run ng build -c=production
to build the specified library in the workspace. Once the build process is finished, deleteLibrary
is called to remove the library from the targeted project's node_modules
. Finally, replaceLibrary
is executed, moving the compiled code from the dist
folder to the node_modules
of the targeted project.
We incorporate this script into the package.json
file to execute the above-mentioned steps:
"build-replace": "node ./build-scripts/replace.js"
Then in the terminal, we can run this command:
npm run build-replace [LIBRARY_NAME]
Now the changes that we made in the library will be directly implemented in the targeted project.
Library Versioning
When it comes to publishing an Angular library, versioning plays a vital role in keeping track of changes and ensuring that projects are utilizing the correct library version. At Halodoc, we implement Semantic Versioning (SemVer) as our standard versioning convention for libraries. SemVer employs a three-digit format separated by dots: MAJOR.MINOR.PATCH.
-
MAJOR version: When the MAJOR version is incremented, it means that there are breaking changes to the library's interface, which may cause incompatibility issues with code that relies on the old API. In other words, code that works with the old version may no longer work with the new version.
-
MINOR version: When the MINOR version is incremented, it means that new features have been added to the library, but they are backward-compatible with the previous version. This means that code that works with the old version should also work with the new version.
-
PATCH version: When the PATCH version is incremented, it means that there are bug fixes or other minor changes that do not affect the library's interface. This type of change should not cause any compatibility issues, and code that works with the old version should also work with the new version.
Using SemVer helps us to communicate to our users what changes they can expect when they update to a new version of our library. This also ensures that our users have a consistent experience across all our projects and applications.
There are other versioning schemes like calendar versioning, date versioning, and sequential versioning. However, these schemes may not provide enough information to users about the nature of the changes in a new version. SemVer provides more clarity and predictability for our users, which is why we prefer it at Halodoc.
Conclusion
Implementing custom Angular libraries can offer numerous benefits for developers and organizations alike. By reusing code across multiple projects, we can reduce duplication and ensure consistency while also saving time and effort. The DRY (Don't Repeat Yourself) principle is a key aspect of this approach, as it encourages us to avoid duplicating code and functionality whenever possible. At Halodoc, we have seen firsthand the advantages of using Angular libraries and are excited to continue exploring this approach in our development process.
In conclusion, we highly recommend that developers consider using custom Angular libraries to improve their coding workflow. By investing time in creating reusable components and services, developers can save time in the long run and produce more consistent and maintainable applications. We hope this blog has provided useful insights and recommendations for those looking to get started with custom Angular libraries.
Join us
Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.
About Halodoc
Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.