Discover the process of setting up a new Angular project with a structure that is neat, easy to sustain, and simple to extend—and see what advantages come with this approach.

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan

@tomastrajan

Feb 25, 2020

10 min read

Universal Angular Architecture
share

Original photo by Grant Lemons

🎙 ️EDIT: Tune into this Angular Air episode where I break down the ideas from this post together with the fantastic Justin Schwartzenberger, Alyssa Nicoll, and Bonnie! 🎙️ 🎉🎉🎉

This guide will show you how to set up a fresh Angular application with a clean, maintainable, and extensible architecture in no time—and why it’s worth doing. Along with plenty of practical advice, we’ll cover where to place recurring elements like reusable services, feature-specific components, and other common pieces.

Over the past 18 months, I’ve been consulting for a big Swiss insurance firm running more than 90 Angular apps. Yes, ninety—crazy, but also quite cool!
🤫 Curious how we keep such a massive setup from driving us insane? 😵 Check out Omniboard!😉

💎 This write-up condenses all those lessons into a neat, practical format so you can shorten your learning curve and take your Angular SPAs to the next level!

The Creation

In the beginning, there was the CLI—not just any CLI, but the Angular CLI, and it was good

As of February 2020, Angular 9 is the current release, and I’d highly recommend generating new projects with version 9 (or anything newer) to take advantage of features like IVY and the --strict flag from day one!
TIP: Verify your installed @angular/cli version by typing ng --version in your terminal, and if needed, upgrade it with npm i -g @angular/cli@latest!

To kick things off, we need to create a brand-new Angular workspace, which we do by executing

ng new angular-architecture-example --create-application false --strict

  • --create-application false results in an empty workspace

  • --strict tweaks several Typescript compiler settings to steer us toward best practices

After the dependencies finish installing, we move into the workspace directory with cd angular-architecture-example, then browse the full list of available schematics via ng g.

The Application

One of the schematics in that list is called application, and we’ll use it to spin up our first app inside the workspace with

ng g application my-epic-app --prefix my-org --style scss --routing

This creates my-epic-app under the projects folder, complete with the Angular Router and Sass styling using the .scss extension.

The --prefix becomes part of every component tag and directive selector, giving us things like <my-org-user-list>—handy for telling our own components apart from third-party ones at a glance!

To make things simpler, we can leverage an existing component library. Angular Material offers a wide range of polished, high-quality components.

We bring it in with Angular Schematics using ng add @angular/material. This installs the library via npm and asks us a few setup questions:

  • a custom theme lets us apply our own brand colors effortlessly

  • Material's typography ensures a polished, uniform look across the app

  • animations will make Angular Material components shine

After these steps, we're ready to bring Angular Material components into our project—details coming up.

The Tooling

Prettier has been a game-changer for frontend work. It delivers FLAWLESS and UNIFORM code formatting with just a few keystrokes in your editor. Even an entire codebase can be tidied up through a simple npm command.

We'll install it via npm i -D prettier. Following that, we'll add a .prettierrc file to tweak several formatting settings.

My personal preferred Prettier config. Config can also use other than JSON formats…

After that's finished, we need to configure Prettier to work alongside the tslint that ships with the Angular CLI. This is accomplished by installing npm i -D tslint-config-prettier and then appending it to the end of the "extends": [] array in the root tslint.json file...

Universal Angular Architecture - Angular Experts — figure 4

Beyond that, a pair of helpful npm scripts can be added to the root package.json—one to format the entire codebase, the other to verify that formatting is correct.

Universal Angular Architecture - Angular Experts — figure 5

Webpack Bundle Analyzer is another highly valuable tool in this context. It gives us visibility into the exact contents of the JavaScript bundles generated during the production build—an essential capability when verifying that the application’s internal structure and architecture are correctly set up.

Tip: This tool is particularly effective at surfacing mistakes such as unintended cross-imports between lazy-loaded modules or the inclusion of lazy content in the eagerly loaded part of the app. When such issues occur, the affected bundles will contain code that should not be there. The built-in search functionality makes it easy to investigate these cases by highlighting exactly where the matched code appears.

To start, install it with npm i -D webpack-bundle-analyzer, and then add a dedicated npm script to the root package.json file.

Universal Angular Architecture - Angular Experts — figure 6

The build command compiles the application in production mode, thanks to the --prod flag. Additionally, we’re capturing detailed metrics on every module involved in the compilation process by passing the --stats-json flag, which outputs a stats.json file alongside the compiled JavaScript bundles. A recent Angular update altered this behavior, as the default .browserslistrc configuration now targets only contemporary browsers, requiring manual adjustments to enable builds with differential loading for legacy ones.

As the final step, we invoke webpack-bundle-analyzer, pointing it to the file we just generated.

TIP: On Windows, the && operator won’t function within cmd, so you may have to break the analyze script into two distinct commands and chain them using a utility like npm-run-all. Alternatively, you could switch to WSL, CygWin, or GitBash 😉 For the record, I work on a Macbook Pro with Windows 10 and CygWin 🤦, yeah, I know…

Executing npm run analyze brings up a fresh page that appears roughly like this…

Our bundle chart contains mostly vendor code which is understandable as we have just generated and analyzed new (empty) Angular application. As we implement more an more lazy features, the amount of surface covered by the eager (brown) part will reduce and give way to colorful lazy bundles!

Winding Down

You can try it straight away with either npm start or ng serve -o. In this case, the -o shorthand means “open”, and it triggers your default browser to navigate to the right address automatically as soon as the application is up and running…

Initial placeholder layout generated by the Angular CLI

The Angular CLI provides a starter layout packed with helpful hints and links pointing to the official documentation.

🔖 TIP: You should definitely bookmark these resources. Even after working with Angular.js since version 1.1, I still regularly consult the official Angular Docs for API references and best practices.

We can clear out all the bootstrapped template content at once, since it all lives in the app.component.html file and can be tidily discarded 👍

Our initial configuration is complete! We now have a workspace with a blank Angular project and some extra tooling that enhances our day-to-day development workflow.

SIGNUP can be relocated to the chosen section within the article

The Architecture

Let’s pause before we start coding to look at the full picture, no pun intended 😬😹

Psst, this diagram is from my [3 day Angular Mastery workshop](https://tomastrajan.com/workshops/angular-mastery) so let me know if you and your team would like to learn some Angular in a bit more efficient way 😉

The application splits into two distinct segments…

  • The eager segment, delivered upfront through the main.js bundle. This encompasses the AppModule, its primary routes, the CoreModule handling the foundational layout, and all core singleton services intended for application-wide usage.

  • The lazy-loaded features, fetched on demand when a user navigates to them. These modules also leverage the SharedModule. This setup stems from a deliberate balance between minimizing the initial bundle size and ensuring a comfortable development workflow.

Follow me on Twitter for alerts on new Angular posts and other cool frontend topics!😉

The Core

Angular Schematics provide a speedy route to scaffold the base structure using a few CLI commands. To start, execute ng g m core to create a fresh CoreModule inside the core/ directory.

TIP: It’s a good idea to keep two terminals open (or use split tabs/panes in one). This lets you run ng serve for the app while simultaneously executing other schematics or CLI commands.
⚠️ Be aware that the Angular CLI might not immediately detect newly generated files. If you encounter unexpected errors, a restart of the dev server with ng serve often solves the issue.

Next, register BrowserModule and BrowserAnimationsModule inside the imports: [] array of the CoreModule. Then, proceed to remove those two modules from the AppModule's imports, and add the CoreModule there instead. The resulting code should be similar to the following snippet…

The **CoreModule** will be importing most of things needed from start to keep our **AppModule** almost empty
Tip: Since the BrowserModule already re-exports all built-in Angular structural directives and pipes (such as *ngIf and ngClass) that come from CommonModule, you can safely drop CommonModule from the CoreModule imports without any functional loss. Including both modules would only create redundant code — harmless but unnecessary.
…while the **AppModule** will stay virtually empty for the whole life-time of the project, everything that needs to be available from start will be added to the **CoreModule**

Now we’ll put together a simple page structure featuring a top toolbar and a few navigation controls…

Navigation toolbar implemented using components provided by Angular Material component library

The implementation goes in the core module, inside the nested core/layout/ directory. The nice part: there’s no need to create those folders by hand, as Angular Schematics handles it automatically.

Run the command ng g c core/layout/main-layout to generate the “main-layout” component. This will register it automatically in the declarations: [] array of CoreModule, but registering it in the exports: [] array must be done manually. After that, the component can be dropped into app.component.html via its selector <my-org-main-layout></my-org-main-layout>.

From here, the layout code goes into the main-layout.component.html template file…

Yes, I am aware that this code can NOT be copied as is just an image. On the other hand, typing, or even better, using amazing code completion capabilities of your IDE should get you there in no time while building that skill simultaneously, win win!

The template above makes use of a handful of components and directives that the CoreModule currently doesn't include. If we skip adding them, the compilation step will break.

Tip: In Angular, a module acts as a container for the components it defines. Consequently, any component referenced in a template, such as <mat-toolbar>, needs to be accessible within the module that declares the host component. This accessibility is achieved in two ways: the component can be listed directly in the declarations: [] of that module, or it can be re-exported via the exports: [] of an imported module. For instance, since <mat-toolbar> is exposed through the exports of MatToolbarModule, we must include MatToolbarModule in the imports: [] of our CoreModule, as demonstrated below…

Please notice helpful import grouping and comments like **// vendor** and **// material** … They are NOT mandatory but nice to have because they let your colleagues (or even you in the future) get a quick overview of the module structure compared to long randomly sorted list of imports!

We can now introduce some styles in main-layout.component.scss, so that the layout closely mirrors the one from our earlier demonstration…

Universal Angular Architecture - Angular Experts — figure 15

The Lazy Features

With the main layout in place, it's time to build out the lazy-loaded features!

Tip: We'll generate a lazy feature for each top-level route in our app. This serves as a solid baseline, but keep in mind that you can also lazy load nested routes should a feature grow too large!

To start, we'll generate a module for the home feature by running ng g m features/home --route home --module app.module.ts. This command accomplishes two tasks:

  1. it creates the module, routing module, and component files under /features/home

  2. it registers the lazy route in the primary app-routing.module.ts file

Now, let's inspect the app-routing.module.ts file to see the generated home route. We'll then add an initial "empty" route that redirects to home, ensuring something displays right away.

Tip: You may also want to include a final "catch all" (**) route. This route redirects to home when a user hits a URL that doesn't match any existing route. Alternatively, you could build a dedicated "not found" page instead of redirecting…

Angular routing config example with generated “home” lazy route, initial redirect and catch all route

We can apply the same approach to our admin route by running ng g m features/admin --route admin --module app.module.ts. Make sure it gets placed ahead of the “catch all” route, otherwise it will never get matched!

Mark the current route

Now that both navigation and routing are set up, we can launch the app and observe its behavior. Clicking the buttons in the top toolbar will move us between routes as expected.

Highlighting the active route is considered good UX

To do that, open main-layout.component.html and attach the routerLinkActive="active" directive to each of the navigation buttons.

Next, define the .active class in main-layout.component.scss, for instance by applying filter: brightness(<amount>);. That rule is handy because it avoids forcing a specific color, making it theme-agnostic!

Universal Angular Architecture - Angular Experts — figure 17

Excellent — our app already supports two lazily loaded features, and scaling further is just a matter of repeating the same workflow!

Why Lazy Loading Pays Off

It’s common knowledge that lazy loading shrinks the initial JavaScript payload, which in turn accelerates boot time. That’s already a big win, but the advantages go far beyond mere startup performance!

  • faster developer feedback (i.e., quicker DEV-mode rebuilds) — when you touch a file, the Angular CLI only recompiles the much smaller lazy chunk that contains it (hundreds of KBs), rather than the entire monolithic main bundle (several MBs). Depending on project size, this can trim seconds off every single rebuild cycle!

  • module isolation — since feature A’s code isn’t even loaded in the browser, it’s impossible for it to accidentally reference or reuse logic from feature B; any attempt would fail at runtime. This makes it trivial to extract features into standalone libraries or drop them entirely without collateral damage to the rest of the app.

  • stronger guarantees — building on the isolation point, we can be confident that edits inside feature A won’t ripple out and break other features, giving us more freedom to evolve the codebase safely.

  • quicker application startup — most folks cite this as the headline benefit, and it certainly matters, but don’t overlook the other perks we just listed!

What belongs inside a lazy feature

Lazy modules house the declarables (components, directives, pipes) that are unique to that feature — such as specific views or bespoke components that aren’t meant to be shared elsewhere.

Note: Running ng g s / produces a service with providedIn: ‘root’ by default, which isn’t ideal for feature-scoped services. That approach still allows other features to import the service, thereby breaking the isolation we strive for.

To keep a service confined to its feature, strip out the providedIn: 'root' from its @Injectable() decorator, and instead register it in the lazy module’s providers: [ ] array!

Introducing the Shared Module

With the core module and two lazy modules (home and admin) in place, you might notice that multiple lazy features need the same component, directive, or pipe…

That’s exactly where the SharedModule shines! Spin one up with ng g m shared, and decide what goes in it:

  • declarables (components, directives, pipes) that multiple lazy features rely on

  • library components (vendor, Material, or any UI framework)

  • re-export CommonModule (brings in essentials like *ngFor, *ngIf, etc.)

Example of the **SharedModule** structure

With SharedModule in place, we can start using it inside our lazy loaded feature modules, dropping CommonModule since the shared module already re-exports it.

Universal Angular Architecture - Angular Experts — figure 19

⚠️ NOTE: Since the SharedModule is imported by multiple lazy-loaded features, it should never define providers (i.e., providers: []) and only expose declarables — components, directives, and pipes — plus modules that themselves only contain declarables.

Why? Every lazy-loaded module creating its own service instance is rarely desired, as services are generally expected to act as global singletons.

If you need services that are shared across the app, place them in the /core folder and use providedIn: 'root' — do not register them in any module's providers array…

With our architecture set, we can now concentrate purely on building user-facing features!

Trade-offs

No solution is one-size-fits-all, and this approach has both strengths and weaknesses. The architecture aims to balance bundle size with developer experience (DX), drawing from real-world insights across many projects…

Feel free to tweak it according to your own preferences and your project's specific requirements — those should always guide your architectural decisions.

Your mileage may vary depending on the structure, scope, and tree-shakeability (quite the frontend jargon, right? 😅) of the libraries you adopt for your project.

Application size vs Developer experience (DX)

  • minimum bundle size: omit the SharedModule entirely; each module (CoreModule and lazy features) imports precisely what it needs. Bundles are as small as possible and highly optimized, but developers juggle lengthy import lists — especially painful in tests, since each component spec must assemble its own TestBed context with the required imports.

  • best possible DX: a single AppModule imports everything, so no context management is needed and all symbols are globally available — but the bundle balloons and you end up with a "big ball of mud".

  • small bundles, decent DX: adopt the architecture we outlined, using SharedModule. It imports and re-exports components from third-party UI libraries plus reusable local ones, and we include it in every lazy feature and component test.

    This yields reasonable bundle sizes with agreeable DX — no more pasting 50 lines of imports in every feature module or test file, which is a win!

And that's it!

In no time, we've assembled an Angular app with a clean, scalable architecture. Be sure to check out the example repository built from this guide!

Angular CLI and Schematics handled the scaffolding, letting us generate the project and its foundational structure effortlessly.

  1. Our app includes CoreModule for global singleton services, base layout, and anything needed right at startup.

  2. We also created SharedModule for reusable components, pipes, and directives (declarables) used by lazy features — but not by core.

  3. Finally, we set up several lazy-loaded modules (with their routes) to host feature-specific business logic (services) and views (components)…

  4. From here on, we stick to this architecture and keep adding features for our users!

Best of luck with your projects!

Give this article some 👏👏👏 to help it reach a broader audience 🙏 and follow me on Twitter for updates on upcoming posts!

If you have any questions, just reach out via the article comments or DM me on Twitter @tomastrajan.

Remember, the future is bright

Universal Angular Architecture - Angular Experts — figure 20 Actually, the path ahead is promising (📷 by [Oliver Sjöström](https://unsplash.com/@ollivves?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText))

Are you digging the style of our code samples? Check out the brand-new theme we've developed

Skol - the perfect theme for your IDE

Skol - the ultimate IDE theme

Experience the aurora right in your editor. This dark theme is straightforward yet potent—easy on the eyes and visually pleasing.

Craft smarter interfaces by combining Angular with AI

Video Course: Angular and AI

Angular + AI Video Course

This practical course demonstrates how to embed AI capabilities directly into Angular projects, leveraging Hash Brown to craft responsive, intelligent interfaces.

Work through dynamic chat streams, function invocation, generative interface elements, predefined output schemas, and further topics—each explored incrementally.

Seeking a straightforward reference on structuring Angular Signal Forms, handling validation, and migrating existing code?

Angular Signal Forms eBook

Angular Signal Forms eBook

Adopt a model-first workflow to construct typed, validated, and production-ready Angular forms using signals.

Discover schema-driven validation, form-state signals, custom control creation, migration from Reactive Forms, and straightforward API mapping techniques.

Enjoying this content and eager to get comfortable with Angular's newest Signal Forms?

Signal Forms in Angular: An Interactive Workshop

Angular Signal Forms: Hands-On Masterclass

Dive into Angular's cutting-edge Signal-Forms across 12 step-by-step modules, blending core concepts with practical exercises.

Explore form fundamentals, validation logic, bespoke form controls, nested forms, and transition tactics, among other topics.

Win win deal illustration

Stay in the loop
with future articles

Subscribe to the Angular Experts Content Updates & News feed, and we’ll let you know the moment a fresh post goes live on Angular, Ngrx, RxJs, or any other compelling Frontend subject.

Your email stays strictly confidential, and you are free to opt out whenever you like!

Occasional promotional material might be included in these emails—check our Privacy policy for all the specifics.

Join the conversation

Feel free to ask for clarification, offer your own insights, and share what you’ve learned on this subject

Tomas Trajan - GDE for Angular & Web Technologies

Tomas Trajan

Google Developer Expert (GDE)
for Angular & Web Technologies

Google Developer Experts logo X logo LinkedIn logo Github logo Github logo Spotify logo Medium logo public

Assisting development teams in shipping high-quality Angular applications is my core focus, achieved via mentoring and advisory services centered on system design and NgRx-powered state handling!

As a Google Developer Expert for Angular & Web Technologies, I operate as a consultant and Angular coach. My current work involves empowering global enterprise teams by crafting mission-critical features and scalable architectures, enforcing best practices, sharing expertise, and refining delivery pipelines.

Tomáš is dedicated to consistently generating significant value for clients and the wider development community. This commitment is demonstrated through a strong portfolio of widely-read industry publications, speaking opportunities at global conferences and user groups, and ongoing contributions to open-source initiatives.

52

Published articles

4.7M

Article reads

3.5K

GitHub stars

612

Developers trained

39

Conference talks

8

Capacity to eat another cake

Explore further articles published by Angular Experts to deepen your knowledge on adjacent subjects like Angular !

Top 10 Angular Architecture Mistakes You Really Want To Avoid

Top 10 Angular Architecture Mistakes You Really Want To Avoid

In 2024, Angular keeps changing for better with ever increasing pace, but the big picture remains the same which makes architecture know-how timeless and well worth your time!

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan

@tomastrajan

Sep 10, 2024

15 min read

Angular Signal Inputs

Angular Signal Inputs

Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Jan 24, 2024

6 min read

Improving DX with new Angular @Input Value Transform

Improving DX with new Angular @Input Value Transform

Embrace the Future: Moving Beyond Getters and Setters! Learn how to leverage the power of custom transformers or the build in booleanAttribute and numberAttribute transformers.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer

@nivekcode

Nov 18, 2023

3 min read

Empower your team with our extensive experience

Angular Experts has accumulated years of expertise through consulting assignments with both established enterprises and emerging startups, delivering corporate workshops and educational sessions, and contributing to a vibrant ecosystem of open source projects. Our deep familiarity with contemporary front-end development is something we are very proud of, and we would be excited to assist you in driving your business forward