The Hardcoded, Dynamic and Hybrid Approach in Angular Apps

Share on facebook
Share on google
Share on twitter
Share on linkedin

There is a human tendency to start small, then overshooting by doing too much of one thing and then try to find the golden middle way. This is happening in basically everywhere in life, eg. you start to work out, you work out too much and get injured and then you find just the right amount that you can stick to. Knowing this pattern in life can help you predict the future and save yourself from the painful overshoot by having awareness of the two extremes and try to position yourself in a place in-between.

In Angular development, it is no different. We keep jumping over a new shiny object (RxJS, Redux, PWA, functional programming, you name it), over-abuse it and then sometimes toss it away because it didn’t work as intended anyway. But the tool actually had a purpose in itself but stopped working because it got used like an all-purpose tool everywhere.

A common scenario in Angular apps which includes a questionnaire is to start with hardcoding. Then, as hardcoding becomes harder to maintain, the team tries to render the questionnaire dynamically with data from an API. This is, in theory, a good idea, which is why this solution is commonly implemented as it provides “easy” changes and centralization of the questionnaire. It also supports that the questionnaire can be used by multiple clients using reusable components. Then reality hits you and you are now having a hard time doing simple changes because everything is tied up in the questionnaire rendering engine. You start longing for the good old times when the questionnaire was hardcoded and simple changes were easy.

This post is going to cover how you can find the perfect balance between these two paradigms that will give you the maintenance benefits of a centralized questionnaire while having the flexibility of being able to design each question as you like.

 

Let’s go back to where it all begins.

Starting out by hardcoding

In the example with the questionnaire application, you might have a system where you need one app for answering the questionnaire and then one app for a back-office to see the answered questionnaire. To begin with, the questionnaire is hardcoded in both apps and all is good. As the questionnaire gets modified it becomes tiring for the developers to update both the client and the back-office app. Then one of the smart developers suggests that you centralize the questionnaire on a separate service and then the two apps are being generated dynamically by “simply” making changes to the questionnaire service. All sounds good and the teams build the questionnaire service and start rendering the questionnaire dynamically using the questionnaire data.

The monster grows

The business and UX’ers keep asking for changes and the developers keep expanding the question rendering engine in the Angular apps to handle new question types, validation types, conditional rules and such, making it more and more complicated to maintain. The poor developers making the question rendering engine more and more complicated until the UX team asks you to do some simple change to the UI but you can’t because your apps are too coupled to the rendering engine which doesn’t allow for individual styling for specific questions. The initial idea with having this questionnaire service and the questionnaire rendering engine on the apps was to spend less time on changes, but now you are actually spending a lot more time than if you were hardcoding.

This guy will hunt you in the night if you over-engineer

Fighting the monster by doing a hybrid approach

Now it becomes clear that you must be able to handle individual changes for the app by making it more flexible. In my post about the painful trap with reusable Angular components I explain how you, instead of making the component interface more and more complicated as the complexity of the reusable component increases, should consider transclusion, by passing a templateRef to the component which will determine how some part of the reusable component will be rendered in a flexible manner.

Enable hardcoding in your dynamic questionnaire app

For demonstration purpose, I am going to expand upon my questionnaire render demo which is where I show you how to create a dynamically generated questionnaire like we have been talking about until now. What we want to do is add support for hardcoding specific questions, so we can both enjoy the centralization of the questions and dynamic rendering while having flexibility when we need it. If you need to create a new question, which is not supported by the question rendering engine, you can simply code it as a component for that specific question. You can find the complete code on my Github.

The app structure will end up looking like this:

Here we have created a module for the hardcoded questions, which are being registered in a service (HardcodedQuestionsService) as a map of templateRefs. This is then used in the question component, which if it is a hardcoded question, will lookup in the hardcoded questions map and instantiate the corresponding templateRef. The hardcoded question is added to the questionnaire JSON data, but the app will only use the question id and type, as the question will not be dynamically rendered like the other ones.

This is easy to do and it will save us for a lot of headage in the long run. Let’s dive into the specifics.

Creating and registering the hardcoded questions

First, we create some question we want to hardcode to look a specific way:

When this question gets added to the hardcoded questions component, which will set up the templateRefs for all the hardcoded questions. Note how let-question="question" is enabling us to pass the question using ngTemplateOutletContext.

In HardcodedQuestionsComponent the hardcoded questions templateRefs get selected using ViewChild and they are being added to the hardcodedQuestionsMap.

This makes these hardcoded questions globally available in the app through the HardcodedQuestionsMapService.

Let’s look at how to render these hardcoded questions.

Support hardcode questions in the question renderer

The hardcoded question is being rendering by checking if the question answerType is “hardcoded” and then it will get the hardcoded questions templateRef from the hardcodedQuestionsMap.

Add the hardcoded question to the Questions file

The hardcoded questions simply have a type indicating that this is hardcoded, the question id and text:

Now the questionnaire supports both renderings of standard question types as well as the flexibility to render hardcoded question.

Conclusion

In this post, we went through a common cycle in Angular development that goes as follows: start by hardcoding, then over-engineering with dynamic rendering but sacrificing flexibility and then lastly a correction to this by supporting the flexibility of hardcoding specific parts of the app while keeping the dynamic rendering engine. Having this gives us the best of both worlds and we can generalize the hardcoded parts and integrate them in the dynamic rendering engine if they become a common reused pattern.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

How to Set up a CI pipeline with Azure Pipelines and Nx

It goes without saying that having a CI pipeline for your Angular apps is a must. Setting one up for regular Angular apps is fairly straightforward but when you have an Nx monorepo there are certain other challenges that you have to overcome to successfully orchestrate a “build once, deploy many” pipeline. This post will

Read More »

How to Set Up Git Hooks in an Nx Repo

Git hooks can be used to automate tasks in your development workflow. The earlier a bug is discovered, the cheaper it is to fix (and the less impact it has). Therefore it can be helpful to run tasks such as linting, formatting, and tests when you are e.g. committing and pushing your code, so any

Read More »

The Stages of an Angular Architecture with Nx

Long gone are the times when the frontend was just a dumb static website. Frontend apps have gotten increasingly complex since the rise of single-page application frameworks like Angular. It comes with the price of increased complexity and the ever-changing frontend landscape requires you to have an architecture that allows you to scale and adapt

Read More »

The Best Way to Use Signals in Angular Apps

Since Angular 16, Angular now has experimental support for signals and there is a lot of confusion in the community about whether this is going to replace RxJS or how it should be used in an app in combination with RxJS. This blog post sheds some light on what I think is the best way

Read More »

High ROI Testing with Cypress Component Testing

Testing is one of the most struggled topics in Angular development and many developers are either giving up testing altogether or applying inefficient testing practices consuming all their precious time while giving few results in return. This blog post will change all this as we will cover how I overcame these struggles the hard way

Read More »