Skip to content

[Angular |] Single Project Workspace Structure

Georg Höller
Georg Höller
4 min read
[Angular |] Single Project Workspace Structure
Photo by Remy_Loz / Unsplash

The concept of a monorepo is great but not always doable because of all sorts of circumstances. For me the critical point always is git - I want each project to have its own repository!
But this doesn't mean you can't use a monorepo framework for a single project. We even can reuse libraries for the sake of the DRY principle. I will explain this further in a separate post.

[ | Angular] Libraries as Git Submodule - DRY Principle
Recently I published a post about my single project workspace structure using I mention that we are able to stick to the DRY principle even if we don’t use as a mono repo! [Angular |] Single Project Workspace StructureThe concept of a monorepo is great

In this tutorial, I will show you my single project workspace structure and the CLI commands to create them. Afterward, we going to add the needed routing that everything is working together as expected. Let's dive into it!



The workspace creation will take some time so we will do this before looking at the structure:

npx create-nx-workspace@latest

Select the Angular preset and choose a name for the workspace and your first app.
I will continue with the workspace gh-dev and the app node-note.

Folder Structure

This part gave me some headaches but I think I found a long-lasting structure for a single project workspace:

  • gh-auth (grouping folder)

    • data-access-auth
      contains the ngrx state for user entity and handles api communication
    • feature-login-user
      business logic for user login
    • ui-user-pw-form
      contains dumb login form component
  • nn-notes (grouping folder)

    • data-access-notes
      contains the ngrx state for notes entity and handles api communication
    • feature-note-crud
      business logic for CRUD operations
    • feature-note-list
      business logic for displaying notes in a list
    • node-note-feature-shell
      starting point - handles routing for our application
    • ui-layout
      contains our basic layout
  • shared (grouping folder)

    • ui-markdown-editor
      basic ui for a markdown editor
    • ui-markdown-viewer
      basic ui for a markdown viewer
    • ui-quill-editor
      basic ui for a quill editor

Now let's create the main area of the app together. The grouping folders gh-auth and shared are not part of this tutorial. I think you will get it fast enough to create them on your own ;-).

Using the nx CLI to create the nn-notes libraries: (append --dry-run for testing)

nx g lib data-access-notes --directory=nn-notes --simpleModuleName=true
nx g lib feature-note-list --directory=nn-notes --simpleModuleName=true
nx g lib feature-note-crud --directory=nn-notes --simpleModuleName=true
nx g lib ui-layout --directory=nn-notes --simpleModuleName=true

App Routing

To be able to run our application I added some basic routing - let's begin with the main app:

  declarations: [AppComponent],
  imports: [
    NodeNoteFeatureShellModule, //added this line
  providers: [],
  bootstrap: [AppComponent],
export class AppModule {}

Now I updated the Feature Shell Module which I imported in the previous step:

const routes: Routes = [
    path: '',
    component: BaseLayoutComponent,
    children: [
        path: 'notelist',
        loadChildren: () =>
            (m) => m.FeatureNoteListModule
      { path: '**', redirectTo: 'notelist' },

  imports: [CommonModule, UiLayoutModule, RouterModule.forRoot(routes)],
  declarations: [],
  exports: [],
export class NodeNoteFeatureShellModule {}

The route notelist is loaded with lazy loading. It is defined as child-route because I want to use a component as a base layout.
Let's add the layout component to the previously created ui-layout library - using the following command:

nx g c base-layout --project=nn-notes-ui-layout

Add any HTML to display the base-layout around the main content which is displayed with the router-outlet tag:

<div style="margin-bottom: 1em">---base layout start---</div>
<div style="margin-top: 1em">---base layout end---</div>

Don't forget to import RouterModule in the ui-layout.module.ts

Feature Routing

For the last step, we need to add the routing to the feature library. Let's start with the final component which should be accessible under '/notelist'.

nx g c components/note-overview --project=nn-notes-feature-note-list

Afterwards, add some basic routing to the library module to display the created component:

const routes: Routes = [
    path: '',
    component: NoteOverviewComponent,
    path: '**',
    redirectTo: '',

  imports: [
  declarations: [NoteOverviewComponent],
export class FeatureNoteListModule {}


Run your app with ng s and you should see our fallback routing working - you should be getting redirected to /notelist.

Check your workspace dependencies with the dep-graph with the following command. (more information)

nx graph


In the upcoming blog post, we will add the state management framework to our project. When we have understood how ngrx works we will use it in our feature-note-crud library to add notes and display them in our created note-list.



Related Posts

Members Public

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n

Install packages npm i -D @nx/storybook npm i -D @storybook/angular Create config nx g @nx/storybook:configuration <project-name> I created the config inside the main app to share some configs/modules. My stories are located at libs/shared/ui. That's why I had to

[Angular | Storybook] Tailwind, Directives, Content Projection, Icons and i18n
Members Public

[Angular | RxJS] BehaviorSubject with custom states

Whenever I call an api, often my frontend has to go through some different visualization steps. Imagine you have some kind of search input and a list which displays the results but you want to show an loading indicator, an error hint and something different when the result is empty.

[Angular | RxJS] BehaviorSubject with custom states
Members Public

[Angular] Dynamic App Config and Translations in a Submodule

When I'm setting up a new angular application, I always start with translation support and an external app config json loader. You should never start a new app without an internationalization (i18n) system - even if you only support one language! We are going to use the package

[Angular] Dynamic App Config and Translations in a Submodule