Skip to main content
Published / updated

Angular Fundamentals and Project Structure

Before you start

You need: HTML, CSS, JavaScript and TypeScript (Track 09), and REST concepts.

You need installed: Node 20 LTS, the Angular CLI (npm install -g @angular/cli) and VS Code. Angular work is done in VS Code, not Visual Studio.

Time: about 50 minutes, plus the practice.

Learning objective

Create an Angular project, explain every file the CLI generates, and build a component that renders data.

Topics

  • What Angular is, and where it fits
  • Installing the CLI
  • Creating a project
  • Project structure
  • Bootstrapping and app.config.ts
  • Components
  • Standalone components versus NgModules
  • The CLI commands you will use daily

What Angular is

Angular is a full application framework: components, routing, forms, HTTP, dependency injection and a build system, all supplied and all opinionated. React is a library that renders views and leaves the rest to you.

That difference decides which you pick more than any feature comparison.

AngularReact
ScopeFull frameworkView library
LanguageTypeScript, enforcedJavaScript or TypeScript
Routing, HTTP, formsBuilt inChosen per project
Dependency injectionBuilt inNot a concept
StructurePrescribedYour decision
Learning curveSteeper up frontGentler start, more decisions later

Angular suits teams and long-lived business applications, where a prescribed structure means every developer finds their way around any project. Its dependency injection and TypeScript-first design also feel familiar coming from C#.

This track assumes the Web Development Foundation track — HTML, CSS, JavaScript, TypeScript and REST concepts. Angular is difficult without them, because every error message assumes you know which layer failed.

Installing

node --version # 20 or later
npm --version

npm install -g @angular/cli
ng version

Node and npm are frontend tooling here, not a backend you are learning. They run the compiler, the dev server and the package manager. Nothing in this track uses Node to serve an application — the backend is ASP.NET Core.

Creating a project

ng new school-portal --routing --style=css --standalone
cd school-portal
ng serve --open
FlagEffect
--routingAdds routing configuration
--style=cssPlain CSS rather than SCSS
--standaloneStandalone components, the modern default
--skip-testsNo spec files — do not use this

ng serve starts a dev server on http://localhost:4200 with hot reload. Leave it running while you work.

Project structure

school-portal/
├── src/
│ ├── main.ts bootstraps the application
│ ├── index.html the single HTML page
│ ├── styles.css global styles
│ ├── app/
│ │ ├── app.component.ts root component
│ │ ├── app.component.html
│ │ ├── app.component.css
│ │ ├── app.config.ts application providers
│ │ ├── app.routes.ts route table
│ │ ├── core/ services, interceptors, guards
│ │ ├── shared/ reusable components and pipes
│ │ └── features/ feature areas
│ │ └── students/
│ └── environments/
│ ├── environment.ts
│ └── environment.development.ts
├── angular.json build and serve configuration
├── package.json
└── tsconfig.json

core / shared / features is the structure to adopt from the first commit. Everything in one folder works until about twenty components and then stops working entirely.

FolderHolds
coreServices, interceptors, guards — one instance, application-wide
sharedReusable presentational components, pipes, directives
featuresOne folder per business area, each self-contained
<!-- index.html -->
<body>
<app-root></app-root>
</body>

The whole application renders inside that one element. This is a single-page application: the browser loads one HTML file, and Angular replaces content as the user navigates.

Bootstrapping

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

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

import { routes } from './app.routes';
import { authInterceptor } from './core/interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor]))
]
};

app.config.ts is Angular's Program.cs — everything the application needs, registered once. Coming from ASP.NET Core, providers is builder.Services.

Components

A component is a class with a template. It is the unit everything else is built from.

// features/students/student-card.component.ts
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
selector: 'app-student-card',
standalone: true,
imports: [CommonModule],
templateUrl: './student-card.component.html',
styleUrl: './student-card.component.css'
})
export class StudentCardComponent {
@Input({ required: true }) student!: Student;
}
<!-- student-card.component.html -->
<article class="student-card">
<h3>{{ student.name }}</h3>
<p class="roll">{{ student.rollNumber }}</p>
<p>{{ student.className }} - {{ student.section }}</p>
</article>
/* student-card.component.css */
.student-card {
padding: 1rem;
border: 1px solid var(--colour-border);
border-radius: 0.5rem;
}
MetadataPurpose
selectorThe element name in a template
standalone: trueNo NgModule needed
importsWhat this template uses
templateUrl / templateThe markup
styleUrl / stylesComponent styles

Component styles are scoped by default. Angular adds a generated attribute to the elements and rewrites your selectors to match, so .student-card here cannot affect a .student-card elsewhere. That is a genuine advantage over plain CSS and it removes an entire class of styling conflict.

Prefix every selector — app- by convention — so a component name never collides with a real HTML element or a library's.

Standalone versus NgModules

// Modern: the component declares what it needs
@Component({
standalone: true,
imports: [CommonModule, RouterLink, StudentCardComponent],
// ...
})
// Legacy: an NgModule declares components and their dependencies
@NgModule({
declarations: [StudentListComponent, StudentCardComponent],
imports: [CommonModule, RouterModule],
exports: [StudentCardComponent]
})
export class StudentsModule { }

Standalone components have been the default since Angular 17 and are simpler: a component's dependencies are listed on the component, so there is no indirection through a module file.

You will meet NgModules in any project older than 2023. They still work, and the two styles coexist — a standalone component can be imported into an NgModule and vice versa. Do not convert a working NgModule application as an incidental change.

Importing what the template uses is the part people forget:

imports: [CommonModule, RouterLink, StudentCardComponent]

An unimported directive or component produces:

app-student-card is not a known element

which is Angular telling you the import is missing, not that the component does not exist.

A component with data

// features/students/student-list.component.ts
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { StudentCardComponent } from './student-card.component';

export interface Student {
publicId: string;
name: string;
rollNumber: string;
className: string;
section: string;
}

@Component({
selector: 'app-student-list',
standalone: true,
imports: [CommonModule, StudentCardComponent],
templateUrl: './student-list.component.html'
})
export class StudentListComponent {
students: Student[] = [
{ publicId: 'a1', name: 'Ravi Kumar', rollNumber: 'NCA-2024-0012', className: '10th', section: 'A' },
{ publicId: 'b2', name: 'Priya Sharma', rollNumber: 'NCA-2024-0018', className: '10th', section: 'A' },
{ publicId: 'c3', name: 'Arjun Reddy', rollNumber: 'NCA-2024-0031', className: '9th', section: 'B' }
];
}
<h1>Students</h1>

@if (students.length === 0) {
<p class="muted">No students have been added yet.</p>
} @else {
<div class="card-grid">
@for (student of students; track student.publicId) {
<app-student-card [student]="student" />
}
</div>
}

@if and @for are the modern control-flow syntax (Angular 17+). track is required on @for and tells Angular how to identify each item — the next article covers why it matters.

The older *ngIf and *ngFor still work and appear in every existing project.

CLI commands

ng serve # dev server with hot reload
ng serve --port 4300 # different port
ng build # production build into dist/
ng build --configuration development # unminified, with source maps
ng test # unit tests
ng lint

ng generate component features/students/student-list
ng g c features/students/student-list # shorthand
ng g s core/services/student # service
ng g interface core/models/student # interface
ng g guard core/guards/auth # route guard
ng g interceptor core/interceptors/auth # HTTP interceptor
ng g pipe shared/pipes/class-section # pipe

ng update # check for updates
ng update @angular/core @angular/cli # apply them

Use ng generate rather than creating files by hand. It produces the right file names, the right class name, the right selector, and the spec file — and it matches what every other Angular developer expects to find.

ng update runs migration schematics that rewrite your code for a new version. It is the only sane way to upgrade Angular; hand-editing across a major version is not.

Environments

// environments/environment.ts — production
export const environment = {
production: true,
apiUrl: 'https://api.nexcoding.in'
};
// environments/environment.development.ts
export const environment = {
production: false,
apiUrl: 'https://localhost:7099'
};
import { environment } from '../../environments/environment';

const url = `${environment.apiUrl}/api/students`;

The build swaps the file based on configuration — always import the base environment, never the development one directly.

Environment files are compiled into the bundle and shipped to the browser. Anyone can read them. They hold URLs and feature flags; never an API key, a secret, or a connection string. There is no such thing as a client-side secret.

Errors you will hit

MessageCauseFix
ng: command not foundCLI not installed, or not on PATHnpm install -g @angular/cli; reopen the terminal
NG0100: ExpressionChangedAfterItHasBeenCheckedErrorA value changed after change detection ranMove the change earlier, or use OnPush
NG0303: Can't bind to 'ngModel'FormsModule not importedImport it in the component
NullInjectorError: No provider for HttpClientprovideHttpClient() missingAdd it to the app config
Cannot find module '@angular/...'Dependencies not installednpm ci
The page is blank with no errorAn error in the console before renderOpen DevTools first, always

Read the NG number. Angular's errors are numbered and searchable, and the message usually names the fix.

Common mistakes

  • Everything in one folder, with no core / shared / features split
  • Forgetting to import a component or directive the template uses
  • No app- prefix on selectors
  • Importing environment.development directly
  • Putting a secret in an environment file
  • Creating files by hand instead of ng generate
  • Upgrading Angular without ng update
  • --skip-tests, leaving no spec files
  • Treating Node as a backend rather than tooling
  • Converting a working NgModule application as an incidental change

Practice

  1. Create a project with --standalone --routing. Run it and confirm localhost:4200.
  2. Read every generated file and write one line explaining each.
  3. Create the core / shared / features folders.
  4. Generate StudentCardComponent with the CLI. Note every file it created.
  5. Generate StudentListComponent and render three hard-coded students through the card.
  6. Remove StudentCardComponent from the list component's imports. Record the exact error.
  7. Add a .student-card style in the card component and another in the list component. Confirm they do not affect each other.
  8. Inspect a rendered card in DevTools and find the generated scoping attribute.
  9. Add an @if empty state and set the array to empty to confirm it.
  10. Remove track from @for. Record the error.
  11. Add environment.apiUrl and log it. Run with ng serve and then ng build, and compare which file was used.
  12. Run ng build and look at dist/. Find your apiUrl in the bundle — this is why secrets cannot live there.

Exercise 12 is the one to remember.

You can now

  • Create an Angular project and explain its structure
  • Say what a component, template and module each do
  • Read an NG error number and act on it
  • Run the dev server and find errors in the browser console
  • Use the Angular CLI to generate a component

Review questions

  1. What does a standalone component's imports array control?
  2. Why are component styles scoped, and how does Angular achieve it?
  3. What is the difference between environment.ts and environment.development.ts at build time?
  4. Why can an environment file never hold a secret?

Next: Templates and data binding