Skip to main content
Advertisement

16.2 Environment Setup — Angular CLI, Project Structure, Standalone Components

Prerequisites

Before starting Angular development, you need Node.js and npm.

# Check Node.js version (18.19+ or 20+ recommended)
node --version
# v20.11.0

# Check npm version
npm --version
# 10.2.4

Angular 19 supports Node.js 18.19.1 or later or 20.x / 22.x.


Installing Angular CLI

Angular CLI is an essential tool that automates project creation, component/service generation, building, and testing.

# Install globally
npm install -g @angular/cli

# Check version
ng version

Sample output after installation:

Angular CLI: 19.x.x
Node: 20.x.x
Package Manager: npm 10.x.x
OS: ...

Angular: 19.x.x
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Creating a Project

# Create a new project with Standalone components (Angular 17+ default)
ng new my-angular-app

# Interactive prompts
# ? Which stylesheet format would you like to use? CSS
# ? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No

You can also specify options upfront:

# Style: SCSS, no SSR, with routing
ng new my-app --style=scss --no-ssr --routing

# Or non-interactive
ng new my-app \
--style=scss \
--routing=true \
--skip-tests=false \
--no-ssr

Project Directory Structure

my-angular-app/
├── src/
│ ├── app/
│ │ ├── app.component.ts ← Root component (class)
│ │ ├── app.component.html ← Root component (template)
│ │ ├── app.component.scss ← Root component (styles)
│ │ ├── app.component.spec.ts ← Root component (tests)
│ │ └── app.config.ts ← App config (providers, router)
│ │ └── app.routes.ts ← Route definitions
│ ├── assets/ ← Static files (images, fonts)
│ ├── index.html ← App entry point HTML
│ ├── main.ts ← TypeScript entry point
│ └── styles.scss ← Global styles
├── public/ ← Files copied as-is during build
├── angular.json ← Angular CLI configuration
├── tsconfig.json ← Base TypeScript configuration
├── tsconfig.app.json ← TypeScript config for app build
├── tsconfig.spec.json ← TypeScript config for tests
└── package.json ← Dependencies and scripts

Key Files Explained

// src/main.ts — App entry point
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
// src/app/app.config.ts — Global app configuration
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(), // Register HttpClient globally
]
};
// src/app/app.routes.ts — Route definitions
import { Routes } from '@angular/router';

export const routes: Routes = [
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
},
{
path: 'home',
loadComponent: () =>
import('./home/home.component').then(m => m.HomeComponent)
}
];
// src/app/app.component.ts — Root component
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'my-angular-app';
}

angular.json Key Settings

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"projects": {
"my-angular-app": {
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/my-angular-app",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": [
{ "glob": "**/*", "input": "public" }
],
"styles": ["src/styles.scss"],
"scripts": []
},
"configurations": {
"production": {
"optimization": true, // Minify code
"outputHashing": "all", // Cache busting
"sourceMap": false, // Disable source maps
"namedChunks": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB", // Bundle size warning
"maximumError": "1MB" // Bundle size error
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true // Enable source maps for dev
}
}
}
}
}
}
}

Running the Development Server

# Default dev server (http://localhost:4200)
ng serve

# Use a different port
ng serve --port 4201

# Allow external access
ng serve --host 0.0.0.0

# Serve with production config
ng serve --configuration production

The browser auto-refreshes when you modify code (Hot Module Replacement).


Angular CLI Key Commands

Code Generation

# Generate component
ng generate component features/user-list
# Shorthand: ng g c features/user-list

# Generate service
ng g service services/user

# Generate directive
ng g directive directives/highlight

# Generate pipe
ng g pipe pipes/format-date

# Generate guard
ng g guard guards/auth

# Generate interface
ng g interface models/user

# Generate enum
ng g enum models/user-role

Build

# Development build
ng build

# Production build (optimization, tree-shaking)
ng build --configuration production

# Bundle analysis (separate tool required)
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/browser/stats.json

Testing

# Unit tests (Karma + Jasmine)
ng test

# Run once (for CI)
ng test --no-watch --browsers ChromeHeadless

# e2e tests (requires Playwright, Cypress, etc.)
ng e2e

Lint & Format

# Run ESLint
ng lint

# Auto-fix
ng lint --fix

ExtensionPurpose
Angular Language ServiceTemplate autocomplete and error highlighting
ESLintCode quality checks
PrettierCode formatting
Angular SnippetsCode snippets
Material Icon ThemeFile icons
# Install extensions from command line
code --install-extension angular.ng-template
code --install-extension esbenp.prettier-vscode
code --install-extension dbaeumer.vscode-eslint

Standalone Components vs NgModule

Before Angular 17, all components had to belong to an NgModule.

Legacy NgModule Approach

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserListComponent } from './user-list/user-list.component';

@NgModule({
declarations: [
AppComponent,
UserListComponent, // All components must be declared here
],
imports: [
BrowserModule,
AppRoutingModule,
],
bootstrap: [AppComponent]
})
export class AppModule { }
// user-list.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
selector: 'app-user-list',
standalone: true, // ← Just add this — no NgModule needed
imports: [CommonModule], // ← Import only what you need directly
template: `...`
})
export class UserListComponent { }

Comparison

AspectNgModuleStandalone
Setup complexityHighLow
Amount of codeMoreLess
Tree shakingModule-levelComponent-level (more efficient)
Lazy LoadingModule-levelComponent-level also possible
RecommendedLegacyCurrent standard

Real-World Example — Base App Component Structure

// src/app/app.component.ts
import { Component, signal, computed } from '@angular/core';
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';

interface NavItem {
path: string;
label: string;
icon: string;
}

@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive],
template: `
<div class="app-shell">
<!-- Header navigation -->
<header class="header">
<nav class="nav">
<a class="brand" routerLink="/">{{ appName() }}</a>
<ul class="nav-links">
@for (item of navItems; track item.path) {
<li>
<a
[routerLink]="item.path"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: item.path === '/' }"
>
{{ item.icon }} {{ item.label }}
</a>
</li>
}
</ul>
<div class="user-info">
@if (isLoggedIn()) {
<span>{{ userName() }}</span>
<button (click)="logout()">Logout</button>
} @else {
<a routerLink="/login">Login</a>
}
</div>
</nav>
</header>

<!-- Main content -->
<main class="main-content">
<router-outlet />
</main>

<!-- Footer -->
<footer class="footer">
<p>© {{ currentYear() }} {{ appName() }}. All rights reserved.</p>
</footer>
</div>
`,
styleUrl: './app.component.scss'
})
export class AppComponent {
// Reactive state management with Signals
appName = signal('My Angular App');
isLoggedIn = signal(false);
userName = signal('');

// computed — derived state
currentYear = computed(() => new Date().getFullYear());

navItems: NavItem[] = [
{ path: '/', label: 'Home', icon: '🏠' },
{ path: '/products', label: 'Products', icon: '📦' },
{ path: '/about', label: 'About', icon: 'ℹ️' },
];

logout() {
this.isLoggedIn.set(false);
this.userName.set('');
}
}

tsconfig.json Key Settings

{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"strict": true, // Strict type checking (recommended)
"noImplicitOverride": true, // Enforce override keyword
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, // Enforce return on all code paths
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "bundler",
"experimentalDecorators": true, // For @Component etc.
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022", "dom"],
// Path aliases (optional)
"paths": {
"@app/*": ["src/app/*"],
"@env/*": ["src/environments/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true // Enable template type checking
}
}

Pro Tips

Tip 1: Path Aliases for Cleaner Imports

// After adding paths to tsconfig.json
// Before
import { UserService } from '../../../services/user.service';

// After
import { UserService } from '@app/services/user.service';

Tip 2: Use Environment Files

// src/environments/environment.ts (development)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};

// src/environments/environment.production.ts (production)
export const environment = {
production: true,
apiUrl: 'https://api.myapp.com'
};

Tip 3: Manage Monorepos with Nx

For large-scale projects, use Nx to manage multiple Angular apps and libraries in a monorepo with build caching and dependency analysis.

npx create-nx-workspace@latest my-workspace

Tip 4: Use Schematics

The ng g command uses Schematics internally. External libraries like Angular Material also provide schematics:

# Install Angular Material (auto-configured)
ng add @angular/material

# Install NgRx Store
ng add @ngrx/store@latest

Summary

CommandPurpose
ng newCreate project
ng serveStart dev server
ng buildProduction build
ng g cGenerate component
ng g sGenerate service
ng testRun unit tests
ng lintCode quality check
ng updateUpdate dependencies

The next chapter dives deep into Angular components and template syntax.

Advertisement