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.
| Angular | React | |
|---|---|---|
| Scope | Full framework | View library |
| Language | TypeScript, enforced | JavaScript or TypeScript |
| Routing, HTTP, forms | Built in | Chosen per project |
| Dependency injection | Built in | Not a concept |
| Structure | Prescribed | Your decision |
| Learning curve | Steeper up front | Gentler 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
| Flag | Effect |
|---|---|
--routing | Adds routing configuration |
--style=css | Plain CSS rather than SCSS |
--standalone | Standalone components, the modern default |
--skip-tests | No 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.
| Folder | Holds |
|---|---|
core | Services, interceptors, guards — one instance, application-wide |
shared | Reusable presentational components, pipes, directives |
features | One 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;
}
| Metadata | Purpose |
|---|---|
selector | The element name in a template |
standalone: true | No NgModule needed |
imports | What this template uses |
templateUrl / template | The markup |
styleUrl / styles | Component 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-cardis 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
| Message | Cause | Fix |
|---|---|---|
ng: command not found | CLI not installed, or not on PATH | npm install -g @angular/cli; reopen the terminal |
NG0100: ExpressionChangedAfterItHasBeenCheckedError | A value changed after change detection ran | Move the change earlier, or use OnPush |
NG0303: Can't bind to 'ngModel' | FormsModule not imported | Import it in the component |
NullInjectorError: No provider for HttpClient | provideHttpClient() missing | Add it to the app config |
Cannot find module '@angular/...' | Dependencies not installed | npm ci |
| The page is blank with no error | An error in the console before render | Open 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/featuressplit - Forgetting to import a component or directive the template uses
- No
app-prefix on selectors - Importing
environment.developmentdirectly - 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
- Create a project with
--standalone --routing. Run it and confirmlocalhost:4200. - Read every generated file and write one line explaining each.
- Create the
core/shared/featuresfolders. - Generate
StudentCardComponentwith the CLI. Note every file it created. - Generate
StudentListComponentand render three hard-coded students through the card. - Remove
StudentCardComponentfrom the list component'simports. Record the exact error. - Add a
.student-cardstyle in the card component and another in the list component. Confirm they do not affect each other. - Inspect a rendered card in DevTools and find the generated scoping attribute.
- Add an
@ifempty state and set the array to empty to confirm it. - Remove
trackfrom@for. Record the error. - Add
environment.apiUrland log it. Run withng serveand thenng build, and compare which file was used. - Run
ng buildand look atdist/. Find yourapiUrlin 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
NGerror 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
- What does a standalone component's
importsarray control? - Why are component styles scoped, and how does Angular achieve it?
- What is the difference between
environment.tsandenvironment.development.tsat build time? - Why can an environment file never hold a secret?