Where to put your Angular models?

Giancarlo Buomprisco
5 min read

Before we go into detail - it's worth understanding what we mean by models:

  • entity classes?
  • enums?
  • interfaces?

It could be all of them - depending on who you ask.

I personally like to differentiate between interfaces and models as two distinct things:

  • interfaces are used to define the shape of my Typescript entities
  • models are used as to define the actual value of my interfaces

For example, we can have the following interface:

interface User {
  name: string;
  id: string;
  photoURL: string;
}

and the following model:

const USER: User = {
  name: `Giancarlo`,
  id: `0`,
  photoURL: `/assets/photos/giancarlo.webp`
};

The question set out in this article seems very simple, but it's not. Organizing your code in an organized manner is a much harder task than it seems.

How to organize your models in your Angular Project Structure

The answer to the question of where to place and how to organize the Angular models and interfaces within your application depends entirely on the structure of your project's structure.

Let's try to answer these questions first:

  • Is your team using a Angular CLI project structure or a collection of projects across different repositories?
  • Is your team using a Nx Monorepo, splitting apps in the same repository?

As you can imagine, depending on the answer, you have different choices to make.

Scenario: A simple Angular CLI app

In the case of a simple Angular app, you're not required to share types with different applications and libraries. It's the simplest scenario.

This is not entirely the case, as your company may develop other applications or reusable libraries, so you should keep this in mind.

So, how can we organize models?

Solution: Module-Scoped Entities

Every Angular Module should have a set of domain entities (models, enums, etc.) that are either private or public.

Let's assume we have a module called UsersModule. We want to define, for every entity, a sensible folder structure:

- src
  - modules
    - users
      - components
      - services
      - enums
          - roles.enum.ts
      - interfaces
          - user.interface.ts
      - models

As you may have noticed: enums, interfaces and models all have their own folder, and it's important not to mix them to keep them well-organized.

Solution: Globally Available Entities

This solution is probably the simplest, and possibly the one you should make if you're in the early development stages, where your domain is not yet well-defined.

You have a couple of ways to achieve this:

  • a globally scoped Typescript namespace, in which you would define all your interfaces
  • a root folder (ex. types) from which you can export your types

In the first scenario, you can do the following. Declare a file global.d. ts in your root folder, add it to your Typescript configuration (using the files property), and then add your global types by declaring a global scope:

declare global {
  interface User {
    id: string;
  }
}

You could also scope each domain within a namespace:

declare global {
  namespace auth {
    interface User {
      id: string;
    }
  }
}

And you can now access your types using auth.User, without the need of importing them. Bear in mind, this is not necessarily a good thing. In fact, I'd argue it's not: at the same time, it's not a bad way to start coding your application while you figure out the domain model.

In the second scenario, you can keep a root folder src/types and export your types just as you do with your other Typescript files. This is preferable to using globally scoped types, but ultimately you may decide to choose the former for increasing your development speed, which is understandable.

Solution: Sharing type with different repositories

Consider creating a typescript repository to expose your global entities to different repositories.

This can have the advantage of letting you use your types with various applications and libraries, but will also result in the worst development experience due to the fact you have to manage a different repository and ultimately link this repository with the others using npm link.

Organize Models using a Monorepo

Imagine we have a monorepo (built with Nx or Turborepo, for example) with other applications or libraries written in different technologies (ex. Express, Stencil, React, etc.): you may not want to import your types from a different technology.

Monorepositories allow us to share code as libraries with other applications or other libraries within the same repository.

This is one of the best set-ups if you need to share code.

馃挕 Solution: Use a shared library for entities used outside of your modules

Create a separate library (for example, called @enterprise/interfaces) that exposes your global entities.

The setup above is particularly recommended if you're using Nx to structure your project.

Admittedly, this is not great - but if you have a large team of teams using the same interfaces, it could be really important to keep them in sync.

Rules to keep your interfaces and models organized

Never export an interface from a Service

This is a pattern that I quite dislike - and I see used very often.

Defining an interface within a Service (or a Component) is generally fine - although not something I normally do. It's all good - as long as it is not exported.

Why is that?

  • A component should not import a service simply to get an Interface
  • A component may simply use Typescript inference instead of using that Interface
  • If the interface is reused and is used in a way that inference could not work, then it should defined in its own file

Hopefully this answered your questions - but if not, please do send me an email and I'd love to expand on the subject.

Thank you for reading, I hope you enjoyed this article. If you did, consider follow me on Twitter or sign up to the Newsletter using the form below!


Learn more about
AngularAngular