Throughout my career as an Angular consultant, I have had occasions where I had to start with an Angular project from scratch which is a pipe dream for every software developer. Do everything the right way at the get-go, no legacy, you follow me…
So, how do you get started with the project from scratch, and what would I do? This blog post will guide you through setting up an Angular project from scratch to a setup that supports scalability and a productive workflow.
Nx Workspace
First, regardless if it was one app or multiple apps I would use Nx. Simply Nx expands the Angular CLI tools and provides you with executors for different tasks such as build/serve the Angular app, Vitest and Playwright. Besides if you have multiple apps/backends when the monorepo tools make this very manageable with the dependency graph and affected
commands.
Creating your Nx repo is as simple as running:
npx create-nx-workspace@latest angular-monorepo --preset=angular-monorepo
Here I would pick Esbuild
, Tailwindcss
, SCSS
, ViTest
and Playwright
when being prompted.
Now we have a project setup, let’s see how we can organize our code in a scalable way.
Organizing the architecture
An Nx architecture is scalable because you can split your code in projects (apps or libs) that are only executed tasks for if they are affected (based on the git diff and dependency graph). IMO it is premature optimization to split everything into libraries immediately so I like to just keep things in folders within the app project in the beginning but follow the following structure.
Grouping folders and layer folders
In the architecture, there are grouping folders and layer folders.
Grouping folders are folders that group a certain subdomain or are shared (they can be shared within a grouping folder context or globally). Having grouping folders ensures our architecture becomes domain-centric (ensuring cohesion) rather than technology-centric (have you ever seen a huge components folder on the top level in an Angular app? Don’t do that).
Layer folders split the responsibilities within the grouping folders to ensure a one way dependency flow which is important for avoiding cyclic dependencies.
The layers are as follows:
Shell: Routing config for features
Feature: Smart components
UI: Domain-agnostic dumb components (mostly in shared folders)
Domain: Services, domain logic, state management, data fetching and models
Util: Domain-agnostic utilities (mostly in shared folders)
So for each domain grouping folder you will most often just have a feature and a domain folder and then for the shared grouping folder you will most often have a shared UI, domain and util. Shared can also contain grouping folders like auth so you eg. have shared/auth/feature for eg. a shared auth page.
Tailwind CSS
I recommend handling 90% of your styling with Tailwind CSS as it is simply a more efficient and manageable way of doing styling:
- Apply CSS with CSS classes
- Reference design tokens (t-shirt sizes)
- Responsive design by applying styles based on device viewport size
- Create grid and flexbox layout easily
Apply CSS with CSS Classes
One of the core principles of Tailwind CSS is to apply styles directly using utility CSS classes, rather than writing CSS with semantic classes in the HTML. This approach aligns perfectly with Angular’s template-driven development model. Instead of defining styles in a separate CSS file, you can simply add the necessary Tailwind utility classes directly to your Angular component templates.
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Click Me </button>
This not only makes your styles more maintainable and modular, but it also reduces the amount of custom CSS you need to write and manage.
Reference Design Tokens (T-Shirt Sizes)
Tailwind CSS provides a comprehensive set of pre-defined design tokens, often referred to as “t-shirt sizes”, that you can use to consistently apply styles across your application. These tokens cover a wide range of properties, such as colors, font sizes, spacing, and more.
In your Angular components, you can leverage these design tokens to ensure a cohesive visual design:
<div class="text-2xl font-bold text-gray-800 mb-4"> Welcome to Our App </div> <p class="text-gray-600 text-base"> This is a paragraph of text with a subtle gray color. </p>
By using these pre-defined tokens, you can avoid the need to manually specify exact pixel or rem values, making it easier to maintain and update your app’s design.
Responsive Design with Utility Classes
Tailwind CSS provides a powerful set of utility classes that make it easy to apply responsive styles based on the user’s device viewport size. You can use these classes directly in your Angular component templates to create responsive designs.
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"> <div class="bg-white shadow-md rounded-lg p-4"> <h3 class="text-lg font-bold">Card 1</h3> <p class="text-gray-600">This is the first card.</p> </div> <!-- Additional cards --> </div>
In this example, the grid layout will switch from a single column on small screens to two columns on medium screens and three columns on large screens, all without the need to write custom media query-based CSS.
Create Grid and Flexbox Layouts Easily
Tailwind CSS provides a comprehensive set of grid and flexbox utility classes that make it easy to create complex layouts in your Angular components. You can use these classes to quickly define the structure and alignment of your UI elements.
<div class="flex justify-between items-center"> <div class="flex items-center"> <img src="logo.png" alt="Logo" class="h-8 mr-2" /> <span class="text-lg font-bold">My App</span> </div> <nav class="flex space-x-4"> <a href="#" class="hover:text-gray-700">Home</a> <a href="#" class="hover:text-gray-700">About</a> <a href="#" class="hover:text-gray-700">Contact</a> </nav> </div>
In this example, we’ve used Tailwind’s flexbox utilities to create a responsive header with a logo, app title, and navigation links. The layout and alignment of these elements are defined using the utility classes, without the need for custom CSS.
Common argument against Tailwind CSS is that you are repeating styling (rather than reusing the same semantic CSS class) but the solution to this is just to break up the component in dumb components as you should anyways.
State management
Regarding state management, I have over the last couple of years seen a shift away from NgRX Store towards the lightweight state management frameworks. Especially since Signals I prefer to use the NgRx SignalsStore now as it is a nice lightweight state management implementation that uses Signals.
In combination with that, I prefer to create a facade service for each sub-domain that is used by the smart components for the domain-related logic and data access.
Zone-less
Signals and local change detection have now made zone-less apps feasible without a lot of explicit changeDetectorRef.DetectChanges
calls. Simply, you just have to use signals for all the dynamic data in the views and you are sure that it will update when the signal updates as signals trigger change detection locally to where they are used in the view on changes. Make sure to set all components to OnPush change detection as well, and you setting yourself up for optimal change detection.
Git Hooks
Git hooks can’t help us follow some quality standards as we are developing our apps before the code is committed and pushed to the git repository.
Husky is a tool to trigger Nodejs commands on certain Git hooks. I use it to trigger ESLint, Prettier, StyleLint, commitlint on commit and run tests locally on push.
You can read more about how to do that here.
DevOps
It’s not enough to run the quality assurance tools locally, we need to also run them on the DevOps agent as well as building and publishing our app.
You can read how I recommend setting up your DevOps pipeline here.
Full type safety
Type safety is the cheapest way to ensure quality in your code. Within the frontend app this includes using Typescript with strict mode and ensure any
types in your code so everything is explicitly typed.
Furthermore, it’s beneficial to have type safety between your frontend and backend so you either automatically generate types for the backend (eg. based on Swagger config) or you embed the backend in the Nx monorepo and infer the typing with eg. tRPC.
For the tRPC approach you can see my post about the SPARTAN stack here.
Logging and error handling
I recommend you set up some sort of logging and error handling. If you already have a cloud solution that supports logging you might want to use that. Otherwise, tools like LogRocket gives you session replay which is helping you see what’s failing in the app.
Furthermore, I recommend following a pattern for your pages to show errors to the users in a consistent way. Check the blog post about the page pattern here.
Conclusion
We saw how to take an app from scratch to a production-ready setup with developer tools optimized for productivity and scalability.
Doing this will give you a strong foundation for a setup that can keep you productive as it scales.
Do you want to become an Angular architect? Check out Angular Architect Accelerator.