A 5-year-long Angular project from a developer’s perspective – challenges, risks, and experiences

2 years ago
We all know how a perfectly run project should look like in theory, right?

Written from scratch by a team of seniors, with developers on board who ensure excellent cooperation and relationship with the client. Perfect architecture, maybe nrwl/nx, lots of tests – unit tests, integration tests, e2e, a detailed documentation. A team that knows the domain and the whole business logic of the application inside out. And above all… no bugs. Such a project is every programmer’s dream, but what if the project lasts a few years? What if there were more than 30 developers involved? Each of them thinks differently, solves problems in a different way and most importantly, no one ever ENTIRELY knows the business domain and all the dependencies in the code. This is barely ever mentioned.

I will try to present such a project situation from the perspective of an Angular Developer who has worked on a 5-year project for 2 years’ time. This is not a guide on how a project should be run. It’s rather my own point of view and developer’s thoughts on the subject. From this article you will learn how we got the following aspects under control: stability, refactors, updates and team changes.

Changes in the team

We all know how it is when you work in the IT industry – the squad rotation is like in a good soccer club, one transfer after another 😉 Programmers tend to change jobs quite often. Since the beginning of this ongoing project, more than 30 programmers with different seniority levels were involved and as you may know, new people who join the project must be implemented first. They have to get to know the logic and the code, in order to associate what might happen after throwing away one line of code. This takes time. This is where code review and testing comes in handy. Code review needs to be done thoroughly – it can’t just be about reviewing code and adding comments to throw out console.logs.  You need to focus on making the code as readable as possible, all the while thinking about others and what they will understand by reading that method name. Then click through those changes and check if other related things didn’t stop working. It’s also a good idea to review git blame. The name of a commit can tell us a lot if we keep an eye on those names. “Changes” or “bug fixes” are unlikely to tell us anything, but “clear document state on navigate to main list” does. Naming a branch with a task number is also very helpful. We can always take a look at what someone else has fixed in the past.

Cypress

Even if we try very hard, it’s impossible to avoid bugs in our application. We can only reduce the number of new errors that appear and those that have arisen during development by changing the code associated with other parts of the application. Let’s also remember that we need to test our application – in my opinion it is best to write unit tests, which will let you check methods and simple things in templates such as validations, displaying, and additionally add tests in Cypress, which will click through the whole application for us. We will focus rather on those automatic tests, because there are plenty of articles about unit tests on various blogs, and not much information about Cypress.

We can write e2e tests in Cypress – so we can test both frontend and backend or do integration tests – testing frontend and mocking backend. The good thing about iterative tests is that they don’t cause request delays , because all of them are locked in files. We don’t need additional dev overhead (markup) to prepare the environment for testing and cleaning it up after each test. They are also much, much faster than e2e tests.

How did we approach testing in Cypress? Well, first of all we decided to use page objects and integration tests. In page objects we have rather smaller methods that do one thing for us and are designed to be used in only one area. We also decided to type selectors that we can use in Cypress. The tests are arranged as follows: user (e.g. admin, user) > module (e.g. tasks, users) > area (e.g. users list). And how do we know what to test? This is where testers, project managers and more experienced developers from the team play a big role. We meet and discuss what is worth testing, which cases are worth paying attention to, and which parts of the system are not likely to have any problems. 

We use sorry cypress to track test results and reports. Unlike cypress dashboard we host it on our server. It’s free, and it’s of great importance in case we run tests on every pull request (and there are thousands of them). 

With 3 people working on the project and each issuing one pull request per day, we are talking about a thousand tests – this gives us about 3k results per day, and about 60k per month. Cypress dashboard package for 75k tests per month costs $465. It’s easy to calculate: 12*465 = $5580 per year – it’s quite a saving 😉 

Integration tests are fast, but with a large number of queries they can take a relatively long time to execute. This is where parallelizing them comes to our aid – we were able to get down from 1.5 hours tests to only 30 minutes. It’s worth it! Here is an example how it looks like in practice:

Refactors

There is a very fine line between when DRY helps and when it only causes problems. The problem arises when we need to add more if’s and nested conditions to the components. Then there is only one way out – you have to refactor… and you have to make it so that modules are not dependent on others, clean up in shared, isolate logic and write a bulletproof  code. But cleaning up and isolating code is just one step. What does such a refactor look like? First of all, we need to answer the question – why do we want to do it? We need to have it figured out in our head how to arrange the modules, what to throw in shared and what not to. The soft side of refactoring is also important, i.e. meetings and talks with testers and the customer. It is important to answer the following questions: what needs improvement in a given module and what the biggest problem is. It is worth reviewing the code, finding dangerous points, and thinking whether it could be done in a simpler way. Another aspect worth reviewing is jira and bug analysis in this field. It will tell us a lot about what to pay attention to as well as about what is most often used and how the users use our application. Writing tests for edge cases will give us a peace of mind.

Updates

We all know very well how much Angular has changed over the last few years. The application I worked on for this since years ongoing project was created with Angular 2 with this standard set of packages – Angular material, ngrx, jest. In terms of architecture – the application is divided into modules – at that time nrwl did not yet exist. So how was it with these updates? Was it a struggle? Did it pay off?

Personally, in my opinion, Angular should be upgraded just like the rest of the packages we use. There were 5 apps in use in this project, including a pdf generator written in nest js and a mobile app written in ionic. The question is, were the updates time consuming and a struggle for the developers? Well yes, they were. Additionally, there was the matter of how to synchronize the update with project development? If the team is small, it can be done in parallel with the work going on and it is unlikely that big conflicts will arise. It’s more difficult if there are 10 people involved in the front-end of the project. In that case, colleagues had to work over the weekend to complete the update for Monday in order to allow others to continue their work.

The Update from Angular 8 to 9 was especially difficult because Angular introduced a different compiler – IVY. Here are some of its advantages. Not all libraries supported it, there were a lot of changes in the code, tests and configuration.

The Update from Angular 11 to 13 and nrwl 8 to 13 took me 2 weeks.iterally everything that could crash, crashed. Nrwl updates provide migrations. They are ok, most of them work. Some however do not…. and you have to fix a lot of things manually. 


So what exactly did I spend all those hours on, doing updates?

1. Mismatchof rxjs versions between our project and nrwl
https://github.com/nrwl/nx/issues/2421There is a quick workaround for this – adding resolution to the package.json, but is this a good solution? You rather need to look for something else. In our case the solution was adding rxjs to another table in package.json and moving it to devDependencies fixed the issue and we could forget about the problem.

2. Angular material import
They recommend an import in this way: @use ‘~@Angular/material’ as mat; migration fixes the issue and seemingly everything is cool, but then we see this error:


Quick fix – using @use ‘@Angular/material’ fixes the problem for us. Next one is fixed. It’s going nicely.

3. Schematics
At House of Angular we use our own schematics to generate the state: https://www.npmjs.com/package/@valueadd/schematics
We want to generate a state and what do we see?
The ‘An unhandled exception occurred: NOT SUPPORTED: keyword “id”, use “$id” for schema ID’ error occurs.
And we need to update our schemas. The fix is rather simple.

4. The mobile app was written in ionic 5. Angular 13 required typescript version 4.4.2. Ionic 5 did not support this version.
What do we do? We update ionic. After this update there was a problem with two ionic components. Nothing rather difficult, but we need to keep it in mind.

5. Another issue –lint
Angular 13 no longer supports tslint. We have to use eslint and that is ok. The problem is that nrwl’s migration does not change the config for each lib. Nrwl provides schematics, but we also have to specify the name of the lib we want to migrate. So who wants to run 300 commands manually? Well… probably no one. 

Here is a script that will take your libs from Angular.json and migrate them to eslint 

jq -r “.projects | keys[]” Angular.json | xargs -I ‘{}’ -n1 echo nx g @nrwl/Angular:convert-tslint-to-eslint {} –ignoreExistingTslintConfig=false –removeTSLintIfNoMoreTSLintTargets=true –skipFormat=false

6. And finally… tests
In Angular we already use is-preset-Angular, not ts-is and this was unfortunately a breaking change. The test configuration changed, so this needs to be fixed for each lib separately.

Summary

To sum up the project which was created years ago, but is still an ongoing project that has to be maintained, it’s not only an organizational challenge for project managers but also for us – programmers. We have to learn business assumptions, understand the code, all the dependencies, and identify risky places. After some time we already know where to look, how to debug, but we still have to cope with the change of developers, versions and reduce the technological debt, which arises constantly. This is a challenge, but also a great opportunity to learn and get to know this kind of project approach. In my opinion, being part of such a project is a very interesting and valuable experience and also a good opportunity to gain new knowledge. I certainly came out of this project much more experienced and wiser than when I started working on it.

About the author

Mateusz

Angular Developer

 A young developer, who works with technologies such as Angular, NgRx and HTML. Programming is his passion, but he is also interested in stock exchange and investments.