Angular Workspace
Introduction
An Angular workspace is a development environment that allows you to manage multiple Angular projects (applications and libraries) within a single repository. This approach, often called a "monorepo" structure, is particularly useful for teams working on related applications that might share code, styles, or other resources.
In this tutorial, we'll explore what Angular workspaces are, how they work, and when you should consider using them for your projects. By the end, you'll understand how to create, configure, and manage an Angular workspace effectively.
What is an Angular Workspace?
An Angular workspace is essentially a folder structure with a set of configuration files that contains one or more Angular projects. Each project can be:
- An application (something that runs in a browser)
- A library (reusable code that can be shared across applications)
The key advantage of a workspace is that it creates a consistent environment where multiple related projects can be developed together, sharing code and configurations.
Workspace Structure
When you create a new Angular project using the Angular CLI, you're actually creating a workspace with a single application in it. The basic structure looks like this:
my-workspace/
├── node_modules/ # Shared dependencies
├── projects/ # Additional projects (apps, libraries)
├── src/ # Default application source code
├── angular.json # Workspace configuration
├── package.json # npm configuration
├── tsconfig.json # TypeScript configuration
└── ...other config files
Creating a New Workspace
Let's create a new workspace using the Angular CLI:
ng new my-workspace --create-application=false
The --create-application=false
flag tells the CLI to create a workspace without an initial application. This gives us a clean workspace where we can add multiple projects as needed.
After running this command, you'll have the following structure:
my-workspace/
├── node_modules/
├── .editorconfig
├── .gitignore
├── angular.json
├── package.json
├── package-lock.json
├── README.md
├── tsconfig.json
└── ...other config files
Adding Projects to a Workspace
Now that we have a workspace, let's add some projects to it:
Adding an Application
cd my-workspace
ng generate application client-app
This creates an application called "client-app" in the workspace:
my-workspace/
├── projects/
│ └── client-app/ # Our new application
│ ├── src/
│ ├── e2e/
│ └── ...config files
├── ...other workspace files
Adding a Library
Libraries are an essential part of workspace management. They allow you to share code between applications:
ng generate library shared-components
This creates a library project:
my-workspace/
├── projects/
│ ├── client-app/
│ └── shared-components/ # Our shared library
│ ├── src/
│ └── ...config files
├── ...other workspace files
Running Projects in a Workspace
To serve, build, or test a specific project in a workspace, you need to specify the project name:
# Serve the client-app
ng serve client-app
# Build the client-app
ng build client-app
# Build the shared-components library
ng build shared-components
The angular.json File
The angular.json
file is the heart of your workspace configuration. It defines all projects in the workspace and their settings. Here's a simplified example:
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"client-app": {
"projectType": "application",
"root": "projects/client-app",
"sourceRoot": "projects/client-app/src",
"prefix": "app",
"architect": {
"build": { /* build configuration */ },
"serve": { /* serve configuration */ },
"test": { /* test configuration */ }
}
},
"shared-components": {
"projectType": "library",
"root": "projects/shared-components",
"sourceRoot": "projects/shared-components/src",
"prefix": "lib",
"architect": {
"build": { /* build configuration */ },
"test": { /* test configuration */ }
}
}
}
}
Using Libraries in Applications
One of the main benefits of a workspace is the ability to share code between projects. Here's how to use your shared-components
library in your client-app
application:
- First, build the library:
ng build shared-components
- Import and use components from the library in your application:
// In client-app/src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { SharedComponentsModule } from 'shared-components';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
SharedComponentsModule // Import your library module
],
bootstrap: [AppComponent]
})
export class AppModule { }
// In client-app/src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>My Application</h1>
<lib-shared-button>Click Me</lib-shared-button>
`
})
export class AppComponent { }
Real-World Example: Creating a Dashboard System
Let's consider a practical example: building a modular dashboard system with multiple applications sharing components.
First, create a workspace:
ng new dashboard-system --create-application=false
cd dashboard-system
Next, add a UI library for shared components:
ng generate library ui-components
Now, create a simple button component in the library:
// In projects/ui-components/src/lib/button/button.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'ui-button',
template: `
<button [class]="'ui-button ui-button-' + type">
<ng-content></ng-content>
</button>
`,
styles: [`
.ui-button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}
.ui-button-primary {
background-color: #007bff;
color: white;
}
.ui-button-secondary {
background-color: #6c757d;
color: white;
}
`]
})
export class ButtonComponent {
@Input() type: 'primary' | 'secondary' = 'primary';
}
Export it in the module:
// In projects/ui-components/src/lib/ui-components.module.ts
import { NgModule } from '@angular/core';
import { ButtonComponent } from './button/button.component';
@NgModule({
declarations: [ButtonComponent],
exports: [ButtonComponent]
})
export class UiComponentsModule { }
Update the public API:
// In projects/ui-components/src/public-api.ts
export * from './lib/ui-components.module';
export * from './lib/button/button.component';
Now, create two applications that will use this shared library:
ng generate application admin-dashboard
ng generate application user-dashboard
Build the library:
ng build ui-components
Use the library in both applications:
// In projects/admin-dashboard/src/app/app.module.ts
// and projects/user-dashboard/src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UiComponentsModule } from 'ui-components';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
UiComponentsModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
And use the button component in both applications:
// In projects/admin-dashboard/src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>Admin Dashboard</h1>
<ui-button type="primary">Admin Action</ui-button>
`
})
export class AppComponent { }
// In projects/user-dashboard/src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>User Dashboard</h1>
<ui-button type="secondary">User Action</ui-button>
`
})
export class AppComponent { }
Now you can serve either application:
ng serve admin-dashboard
# or
ng serve user-dashboard
This example demonstrates how a single UI component library can be shared across multiple applications within the same workspace, maintaining consistency while allowing for specialized implementations.
Workspace Configuration Best Practices
-
Organize related projects together: Group projects that share code or are part of the same product family.
-
Create focused libraries: Keep libraries focused on specific functionality (e.g., UI components, authentication, data models).
-
Use path aliases: Configure TypeScript path aliases in
tsconfig.json
to make imports cleaner:
{
"compilerOptions": {
"paths": {
"@ui/*": ["projects/ui-components/src/lib/*"],
"@models/*": ["projects/data-models/src/lib/*"]
}
}
}
-
Establish clear dependency direction: Libraries should not depend on applications; instead, applications should depend on libraries.
-
Versioning: Consider using tools like Nx or Lerna to help with versioning in more complex workspaces.
Summary
Angular workspaces provide a powerful way to organize multiple related projects in a single repository. Key benefits include:
- Code sharing: Easily share code between projects through libraries
- Consistent configuration: Maintain consistent settings across projects
- Efficient development: Test and build multiple projects together
- Better organization: Logically group related projects
Workspaces are particularly valuable for teams building enterprise applications or product suites where multiple applications share common functionality, styles, or business logic.
Additional Resources
- Angular Workspace Official Documentation
- Angular Library Documentation
- Nx: Extensible Dev Tools for Monorepos
- Lerna: A tool for managing JavaScript projects with multiple packages
Exercises
- Create a workspace with two applications and a shared library for authentication.
- Build a theme library that can be used across multiple applications in a workspace.
- Configure TypeScript path aliases for cleaner imports in your workspace.
- Create a workspace with a main application and feature libraries for different sections of the application.
- Implement a state management solution that can be shared across applications in your workspace.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)