Workshop · Angular · Beginner
NG050 Angular from zero
Web pages first, Angular second. The first three sessions make you fluent in HTML, CSS and just enough TypeScript to make a page react; the next five turn that into Angular 22 components with signals, lists, inputs and a typed form, each step visible in the App Preview the moment you save. Forty steps across eight sessions end in Tally, an expense-tracker page with a validated form and totals per category, checked by tests the machine runs on your own code.
Open Coding Tutor at codingtutor.agilesystemsoftware.com, pick NG050 from the course button in the chat and press Tutor me. Every lesson, demo and hands-on step is also published below.
Who it is for
Adults and teens who have never programmed and want to build things that show up in a browser. You can type and you are patient with a page that does not look right yet. No TypeScript or JavaScript assumed: the course teaches the little it needs on the way to your first components.
Where it leads. JV100 Java I · NG100 Angular I · DN100 C# I: the working programmer’s toolkit · PY100 Python I · RS100 Rust I
What you will be able to do
- Write an HTML page with sound structure and read the browser console when it goes wrong
- Style and lay out a page with CSS selectors, the box model and flexbox so it works at 360 px
- Hold values in typed TypeScript variables, write functions and react to a click in the DOM
- Create a standalone Angular component with a template, a class and styles, and build it
- Bind values and events, keep state in signals and show or hide parts with @if
- Render lists with @for and pass data down to a child component with input()
- Build a typed reactive form with validation and write a Vitest component test
- Keep money as integer cents and compute totals per category without floating-point error
Syllabus
Every session ends in a lab the machine checks on your own code. Open a session's mini steps to read each lesson, demo and hands-on task; the badge shows how far the engine can verify it: checked · T3 a check runs over your files, proven · T4 the grader also tries inputs you did not see, tutored · T0 done on your word. Each step has its own link (for example #s1-1).
- 1. The web page
- 2. Style and layout
- 3. The page reacts: TypeScript basics
- 4. Your first component
- 5. Binding and events
- 6. Lists and inputs
- 7. Forms and testing
- 8. Capstone: Tally
Session 1 · The web page
Five steps that make the seat and the browser yours. A web page is a text file the browser turns into boxes on a screen. The App Preview tab shows a folder's index.html and refreshes when you save. Elements, nesting and attributes; headings, paragraphs, lists, links and images; why structure matters before looks. Reading the browser console when something is off. Live in the seat: a page built tag by tag, a link that goes nowhere until its href is fixed, an unclosed tag swallowing the rest of the page, and the console's complaint about a missing image.
Lab Build a profile page for a made-up shop, Mango Repairs: a heading, two paragraphs, a list of three services, a link to a second page and an image with alt text, all valid and nested correctly. Session 1 checkpoint.
5 mini steps · ~39 min
1.1A page is a text file the browser draws7 minchecked · T3
Predict
index.html contains <h1>Hello</h1>. You change Hello to Welcome and save. What does the App Preview show?
Lesson
A web page is a text file. The browser reads it, turns each element into a box and draws the boxes. You will work in one rhythm all course: change the file, save, look at the App Preview, read what changed.
Demo
The tutor opens node/ng050/demo/index.html beside the App Preview, changes the heading text, saves, and the preview updates. Then the tutor deletes the closing </h1> and shows the whole page turning into a heading.
On screen
Explorer → node/ng050/demo/index.html → App Preview tab pointed at node/ng050/demo → edit → save → preview reloads.
Hands-on
Open node/ng050/s1/index.html (a starter with one heading). Change the heading to the name of a made-up shop, add a <p> sentence under it, save, and confirm the preview shows both.
Explain
In one sentence, what did the browser do with your file?
Check
index.html has your own heading and a paragraph — checked (T3):
web_domseat_file1.2Elements, nesting and attributes8 minchecked · T3
Predict
<a href="about.html">About</a> — which part is the attribute?
Lesson
An element is an opening tag, content and a closing tag. Elements nest like boxes in boxes and must close in reverse order. Attributes inside the opening tag configure an element: href on a link, src and alt on an image.
Demo
The tutor adds a link and an image to the demo page, deliberately crosses two tags (<p><b>text</p></b>), shows the odd rendering, fixes it, then removes the image's src and reads the console error.
Hands-on
In s1/index.html add a link to about.html with the text About us, and an <img> of node/ng050/samples/s1/shop.svg with alt text that describes it. Nest everything inside <body> correctly.
Explain
Why must tags close in reverse order?
Check
A link with href and an image with alt — checked (T3):
web_dom1.3Headings, lists and the shape of a page8 minchecked · T3
Predict
A page has three <h1> elements. Is that a problem?
Lesson
Headings form the outline of a page: one h1, h2 for sections, h3 inside them. Lists are ul/ol with li items. Use the element that means what you mean (a list for a list) and the page is readable by people, screen readers and machines.
Demo
The tutor restructures the demo page: one h1, an h2 "Services" with a ul of three li, an h2 "Opening hours" with a p; then shows the outline the browser derives from it in the developer tools.
Hands-on
Give s1/index.html one h1, an h2 Services with a ul of three services, and an h2 Hours with a paragraph. Make sure no second h1 is left.
Explain
What is the difference between an h2 and a p styled big?
Check
One h1, two h2 sections, a three-item list — checked (T3):
web_dom1.4Reading the browser console7 minchecked · T3
Predict
The console says "GET shop.sgv 404 (Not Found)". What is wrong?
Lesson
The browser console is where the page tells you what went wrong: a file it could not find, a script error, a warning. Read the message, find the file and line it names, fix that first.
Demo
The tutor opens the console in the App Preview, misspells the image path, reads the 404 line, then adds a <script> with a typo and reads the SyntaxError with its line number.
Hands-on
Copy node/ng050/samples/s1/broken.html to s1/fixed.html. It has three problems the console or the rendering reveals: a bad image path, an unclosed list, a link with no href. Fix all three.
Explain
Where do you look first when a page shows no image?
Check
fixed.html: image path, list and link repaired — checked (T3):
seat_file1.5Lab: the Mango Repairs page9 minchecked · T3
Predict
Where does <title>Mango Repairs</title> appear?
Lesson
Everything from the session in one page: head with a title and charset, one h1, structured sections, a list, a link, an image with alt, every tag closed. Valid structure is the foundation the CSS and Angular sessions build on.
Demo
The tutor builds the demo shop page from an empty file in order — doctype, head, body, h1, sections — saving after each piece, then shows the check reading the elements.
Hands-on
Make s1/index.html the Mango Repairs page: title and charset in head; one h1 "Mango Repairs"; a p intro; h2 Services with a ul of three li; h2 Find us with a p and a link to about.html; the shop.svg image with alt text.
Explain
Name two things in your page that help a screen reader.
Check
The Mango Repairs page is complete and well-formed — checked (T3):
web_domseat_file
Session 2 · Style and layout
Five steps on how a page looks. A CSS rule is a selector and declarations; a stylesheet file linked from head. The box model: content, padding, border, margin. Classes for reusable looks. Flexbox for rows and wrapping, and the 360 px rule: a page must work on a narrow phone before a wide screen. Live in the seat: one rule turning every link the brand colour, a box growing padding by padding, three cards jumping into a row, and the preview narrowed to 360 px to find the overflow.
Lab Style the Mango Repairs page with an external stylesheet: a brand colour, a card for each service laid out with flexbox, readable spacing, and nothing wider than the screen at 360 px. Session 2 checkpoint.
5 mini steps · ~39 min
2.1A rule, a selector and a stylesheet7 minchecked · T3
Predict
styles.css has a { color: teal; }. Which elements turn teal?
Lesson
A CSS rule picks elements with a selector and sets declarations on them: a { color: teal; }. Rules live in a stylesheet linked from head with <link rel="stylesheet" href="styles.css">, so every page can share them.
Demo
The tutor creates demo/styles.css, links it, writes body, h1 and a rules, saves, and the preview restyles. A typo in the selector shows how a rule silently matches nothing.
Hands-on
Create node/ng050/s2/styles.css and link it from s2/index.html (a copy of your session 1 page). Give body a font-family, h1 a colour and a a colour of your choice.
Explain
Why put styles in a separate file?
Check
styles.css is linked and styles body, h1 and a — checked (T3):
web_domseat_file2.2The box model8 minchecked · T3
Predict
A box is 200px wide with padding 20px and border 2px, box-sizing content-box. How wide on screen?
Lesson
Every element is a box: content, then padding, then border, then margin. Padding is space inside the border, margin is space outside. box-sizing: border-box makes width include padding and border, which is what you want.
Demo
The tutor gives a demo div a border and raises padding and margin in steps, pointing at the gap inside and outside; then sets box-sizing: border-box on everything and shows the widths settle.
Hands-on
In s2/styles.css add a universal rule setting box-sizing: border-box, and a .card class with padding, a border, a border-radius and a margin. Put each service li inside the card look by giving the li class card.
Explain
Padding or margin: which one pushes neighbours away?
Check
border-box everywhere and a .card with padding, border and margin — checked (T3):
seat_fileweb_dom2.3Flexbox: rows that wrap8 minchecked · T3
Predict
A ul has display: flex and flex-wrap: wrap. The screen is too narrow for three cards. What happens?
Lesson
display: flex on a parent lays its children in a row. gap spaces them, flex-wrap: wrap lets them drop to the next line when the row is too narrow, and flex: 1 1 200px lets each grow and shrink around a 200 px base.
Demo
The tutor sets the demo ul to display: flex with gap, removes the bullets, then narrows the preview until the cards overflow; adds flex-wrap: wrap and they drop neatly.
Hands-on
Make the services ul a flex row with a gap, list-style none and padding 0; give .card flex: 1 1 200px and set flex-wrap: wrap so the cards wrap on narrow screens.
Explain
Which element gets display: flex, the cards or their parent?
Check
The services list is a wrapping flex row — checked (T3):
seat_file2.4The 360 px rule7 minchecked · T3
Predict
An img has width="800" and no CSS. On a 360 px phone it will…
Lesson
Design for the narrow screen first. Images get max-width: 100% and height auto; text gets a readable size and line-height; nothing has a fixed width wider than 360 px; the viewport meta tag tells phones not to zoom out.
Demo
The tutor narrows the App Preview to 360 px, finds the overflowing image, adds img { max-width: 100%; height: auto; }, then checks the viewport meta in head and reads the body text size.
Hands-on
Add <meta name="viewport" content="width=device-width, initial-scale=1"> to s2/index.html, and rules so img has max-width 100% and height auto, and body has a line-height of at least 1.5. Check the preview at 360 px.
Explain
Why does the viewport meta matter on a phone?
Check
Viewport meta, fluid images, readable line-height — checked (T3):
web_domseat_file2.5Lab: the styled card grid9 minchecked · T3
Predict
Two rules both set h1 colour: one in styles.css, a later one with the same selector. Which wins?
Lesson
The whole session on one page: a linked stylesheet, border-box, a brand colour used consistently, service cards in a wrapping flex row, fluid images, a viewport tag. Looks are now reusable classes rather than one-off edits.
Demo
The tutor restyles the demo shop page in five minutes from a blank styles.css, narrates each rule, then narrows to 360 px to prove nothing overflows and shows the check reading the files.
Hands-on
Finish s2: a :root custom property --brand used by h1 and a; three li.card in a wrapping flex ul; .card with padding, border and radius; fluid img; viewport meta; body font and line-height. Nothing wider than 360 px.
Explain
What does a CSS custom property like --brand buy you?
Check
Styled, fluid Mango Repairs page with a card grid — checked (T3):
seat_fileweb_dom
Session 3 · The page reacts: TypeScript basics
Five steps of TypeScript, just enough for Angular. A .ts file is compiled by tsc into a .js file the page loads. Variables with types, strings and numbers, why 0.1 + 0.2 is not 0.3 and why money is kept in cents. Functions with typed inputs and a return value. Finding an element in the page and changing it, and running a function when a button is clicked. Live in the seat: a type error caught before the browser ever sees the code, a total computed in cents and formatted at the end, and a counter button wired by hand before Angular does it for you.
Lab Build a reacting page: a tip calculator in TypeScript that reads a bill amount and a percentage from inputs, computes in cents, and writes the formatted total into the page when a button is clicked. Session 3 checkpoint.
5 mini steps · ~41 min
3.1A .ts file becomes a .js file8 minchecked · T3
Predict
let count: number = "five"; — what happens when you run tsc?
Lesson
Browsers run JavaScript. TypeScript is JavaScript with types; tsc checks the types and writes plain .js. A type is a promise about what a value is, and tsc catches broken promises before the page runs.
Demo
The tutor writes demo/src/main.ts with a typed variable and console.log, runs tsc -p demo, loads the generated main.js from index.html, and reads the message in the console. Then breaks the type and shows tsc refusing.
On screen
node/ng050/demo → src/main.ts → terminal: tsc -p . → dist/main.js → <script src="dist/main.js"> → console.
Hands-on
In node/ng050/s3 (a tsconfig is supplied) write src/main.ts declaring shop: string and year: number, logging a sentence built from both. Run tsc -p . and load dist/main.js from s3/index.html.
Explain
What does tsc do that the browser does not?
Check
main.ts compiles and declares two typed variables — checked (T3):
seat_buildseat_fileweb_dom3.2Numbers, text and money8 minchecked · T3
Predict
console.log(0.1 + 0.2 === 0.3) prints?
Lesson
number holds whole numbers exactly but decimals only approximately. Hold money as whole cents (1999 for 19.99), add and multiply in cents, and format to a string only when you show it: (cents / 100).toFixed(2).
Demo
The tutor logs 0.1 + 0.2, then 19.99 * 3, then the same sum in cents (1999 * 3 = 5997) formatted with toFixed, and writes a formatCents(cents: number): string helper in the demo.
Hands-on
Add to s3/src/main.ts a function formatCents(cents: number): string returning a string like "59.97", and log formatCents(1999 * 3). No division before the final formatting step.
Explain
Why keep money in cents rather than in a decimal number?
Check
formatCents returns 59.97 for 5997 — checked (T3):
seat_buildseat_filecraft3.3Functions with typed inputs8 minchecked · T3
Predict
function tip(cents: number, pct: number): number { return Math.round(cents * pct / 100); } — tip(5000, 15)?
Lesson
A function names a piece of work, takes typed inputs and returns a typed result. A pure function depends only on its inputs, so you can reason about it and later test it. Rounding happens once, at the cents boundary.
Demo
The tutor writes tip(cents, pct) and total(cents, pct) in the demo, calls them with fixed numbers, and shows tsc complaining when a string is passed. Then exports both for a later test.
Hands-on
Write in s3/src/calc.ts and export tip(cents: number, pct: number): number and total(cents: number, pct: number): number (bill plus tip, both rounded to whole cents). Import them in main.ts and log total(5000, 15).
Explain
What makes tip() a pure function?
Check
calc.ts exports tip and total that compile — checked (T3):
seat_buildseat_fileseat_file3.4Finding elements and reacting to a click8 minchecked · T3
Predict
document.querySelector("#total") returns what type in TypeScript?
Lesson
The DOM is the page as objects. querySelector finds an element, which may be null, so check it. textContent changes its words. addEventListener("click", fn) runs fn when a button is pressed. This is what Angular will wrap for you.
Demo
The tutor adds a button and a span to the demo page, finds both in main.ts, guards the nulls, and increments a counter on each click, writing it into the span.
Hands-on
Add <button id="go">Count</button> and <span id="n">0</span> to s3/index.html. In main.ts find both (handle null), and on click add one to a number and write it into the span.
Explain
Why does TypeScript make you check for null here?
Check
A click handler updates a span — checked (T3):
seat_buildseat_fileweb_dom3.5Lab: the tip calculator page9 minchecked · T3
Predict
The bill input holds "42.50". What should the code do first?
Lesson
The session's pieces as one page: two inputs, a button, a pure calc module in cents, a click handler that parses, computes and formats. The calc module is tested with Vitest, your first test file: the same functions, called with fixed numbers.
Demo
The tutor assembles the demo tip page, then writes demo/src/calc.test.ts with two expect() lines and runs vitest run in the terminal, pointing at the green report and then at a deliberately wrong expectation going red.
Hands-on
Build s3/index.html with inputs #bill and #pct and a button #calc that writes the formatted total into #total via calc.ts (cents). Write src/calc.test.ts asserting total(5000, 15) is 5750 and tip(4250, 10) is 425. Run vitest.
Explain
What does the test prove that running the page does not?
Check
Tip calculator compiles, its tests pass, the page has the controls — checked (T3):
seat_buildseat_testweb_domcraft
Session 4 · Your first component
Five steps into Angular. What the framework does for you: it owns the DOM so you describe the page instead of poking it. The tutor runs ng new once and walks the folders. A standalone component is a class with a decorator, a template and styles; the selector places it; interpolation shows class values. ng build turns it into files the App Preview can show. Live in the seat: the generated app read file by file, a second component made by hand and placed inside the first, a build error read back to its line, and the production build opened in the preview.
Lab Make a Shop component with a title and tagline from the class, a Hours child component placed inside it, and a passing build shown in the App Preview. Session 4 checkpoint.
5 mini steps · ~40 min
4.1What Angular does for you8 minartefact · T2
Predict
In session 3 you wrote textContent = ... by hand. In Angular, who updates the DOM?
Lesson
Angular lets you describe what the page should show for a given state and keeps the DOM in step. An app is a tree of components; the CLI creates and builds it. You will not call querySelector again in this course.
Demo
The tutor runs ng new tally --standalone --style=css --skip-git in node/ng050/demo (offline toolchain), opens the folders, and reads main.ts, app.config.ts, app.ts and app.html, explaining one line each.
On screen
terminal: ng new … → node/ng050/demo/tally/src/main.ts → app/app.ts → app/app.html → angular.json.
Hands-on
Read node/ng050/s4/shop (an app the course supplies, generated the same way). Write in s4/shop/NOTES.md one line each for what main.ts, app.ts and app.html do.
Explain
What is the job of main.ts?
Check
NOTES.md names the three files — artefact (T2):
seat_file4.2Anatomy of a standalone component8 minchecked · T3
Predict
A component has selector: 'app-shop'. Where does it appear?
Lesson
A component is a class with @Component({ selector, templateUrl, styleUrl }). The selector is the tag that places it; the template is its HTML; the styles apply only to it. Standalone means it declares what it uses itself.
Demo
The tutor creates demo/tally/src/app/hours.ts by hand with a selector, template and styles, and places <app-hours> inside app.html after adding it to the imports array; builds; the preview shows it.
Hands-on
In s4/shop/src/app create shop.ts with selector app-shop, templateUrl shop.html and styleUrl shop.css; the template shows an h1 "Mango Repairs". Import it into app.ts and place <app-shop> in app.html.
Explain
Name the three parts of your component.
Check
ShopComponent exists, is imported and placed — checked (T3):
seat_buildseat_fileseat_file4.3Interpolation: values from the class7 minchecked · T3
Predict
title = 'Mango Repairs' in the class and <h1>{{ title }}</h1> in the template. The h1 shows?
Lesson
{{ expression }} in a template shows a value from the class. Keep the words in the class as properties (title, tagline) and the template just says where they go. Change the property, the view follows.
Demo
The tutor moves the hard-coded demo heading into a title property and a tagline, interpolates both, builds, then shows that {{ 2 + 3 }} and {{ title.length }} are also valid expressions.
Hands-on
Give ShopComponent properties title and tagline (strings) and show both with interpolation in shop.html (h1 and p). Build and confirm in the App Preview.
Explain
Why move the words from the template into the class?
Check
title and tagline interpolated — checked (T3):
seat_buildseat_fileseat_file4.4Build, read the error, open the preview8 minchecked · T3
Predict
The template reads {{ tittle }} (misspelt). What happens?
Lesson
ng build compiles templates and classes together and fails early on mistakes, naming file and line. The output in dist/<app>/browser is plain HTML, CSS and JS the App Preview can serve.
Demo
The tutor misspells a property in the demo template, runs ng build, reads the error to its line, fixes it, builds again and opens dist/tally/browser in the App Preview.
Hands-on
Run ng build in s4/shop, read any error back to its line and fix it, then open dist/shop/browser in the App Preview. Add to NOTES.md the exact folder the preview served.
Explain
Where did the build error point you?
Check
The production build exists and renders the shop heading — checked (T3):
seat_buildweb_domseat_file4.5Lab: the Shop and its Hours9 minchecked · T3
Predict
HoursComponent is written but not added to ShopComponent's imports. <app-hours> in shop.html gives?
Lesson
Components compose: Shop places Hours by importing it and writing its tag. Each keeps its own template, class and styles. This is the shape of every Angular app: a tree of small bricks.
Demo
The tutor builds HoursComponent in the demo with a list of three opening-time strings interpolated into li elements, places it in the shop, forgets the import on purpose, reads the error, fixes it and builds.
Hands-on
Create hours.ts (selector app-hours) whose template shows a heading "Opening hours" and three li from class properties. Import it in ShopComponent and place <app-hours> under the tagline. Build and preview.
Explain
Why does Shop need to import Hours?
Check
Shop renders title, tagline and the Hours child — checked (T3):
seat_buildseat_fileweb_dom
Session 5 · Binding and events
Five steps that make a component react. Property binding with square brackets sets attributes and styles from values; event binding with round brackets runs class methods on clicks and input. A signal holds state the view tracks; set and update change it; computed derives from it. @if shows and hides parts of the template. Live in the seat: a disabled button bound to a value, a counter in a signal, a computed label that follows it, and an @if that reveals a message only when it should.
Lab A Stamp Card component: a Stamp button increments a signal, the count and a computed "reward ready" message appear with @if at ten stamps, and a Reset button clears it. Session 5 checkpoint.
5 mini steps · ~39 min
5.1Property binding7 minchecked · T3
Predict
<button [disabled]="count === 0">Undo</button> with count 0. The button is…
Lesson
[property]="expression" binds a DOM property or style to a class value: [disabled], [src], [class.active], [style.color]. Interpolation is for text; property binding is for everything else.
Demo
The tutor binds [disabled] on a demo button to a boolean property, flips the property in the class and rebuilds; then binds [style.background] to a colour string and [class.big] to a flag.
Hands-on
In node/ng050/s5/card (supplied app) give CardComponent a property stamps = 0 and bind a Reset button's [disabled] to stamps === 0. Bind [class.full] on the card div to stamps >= 10.
Explain
When would you use [ ] instead of {{ }}?
Check
[disabled] and [class.full] are bound — checked (T3):
seat_buildseat_file5.2Event binding7 minchecked · T3
Predict
<button (click)="stamp()">Stamp</button>. What is stamp?
Lesson
(event)="expression" runs code on a DOM event: (click)="stamp()", (input)="onName($event)". The method lives in the class; the template only names it. $event carries the DOM event when you need it.
Demo
The tutor adds (click) to the demo button calling a method that increments a plain property, builds, clicks, and shows the number changing; then adds (input) on a text box reading $event.target.value.
Hands-on
Add a Stamp button with (click)="stamp()" and a stamp() method that adds one to stamps; a Reset button with (click)="reset()" setting it to 0. Show {{ stamps }} in the card.
Explain
Compare (click) here with addEventListener from session 3.
Check
Stamp and Reset handlers exist — checked (T3):
seat_buildseat_fileseat_file5.3Signals: state the view tracks8 minchecked · T3
Predict
stamps = signal(3); this.stamps.update(n => n + 1); what does {{ stamps() }} show?
Lesson
A signal is a value with a change notification: stamps = signal(0). Read it with stamps(); change it with set(v) or update(fn). The template reads it as stamps() and Angular re-renders only what depends on it.
Demo
The tutor converts the demo counter from a plain property to a signal, changes the template reads to count(), and shows the same clicks working; then logs the signal in an effect to show when it fires.
Hands-on
Convert stamps to a signal(0). Make stamp() use update and reset() use set(0). Change every template read to stamps() including the two bindings from 5.1.
Explain
Why read a signal with brackets?
Check
stamps is a signal used with set/update — checked (T3):
seat_buildseat_fileseat_file5.4computed and @if8 minchecked · T3
Predict
ready = computed(() => this.stamps() >= 10). stamps goes 9 → 10. What happens to @if (ready()) { … }?
Lesson
computed derives a value from other signals and stays in step with them. @if (cond) { … } @else { … } renders a block only when the condition holds. Together they express "show this when" with no manual DOM work.
Demo
The tutor adds a computed remaining() to the demo and an @if that shows "Free coffee!" at ten, with an @else showing how many stamps remain; clicks to ten and the message flips in.
Hands-on
Add ready = computed(() => this.stamps() >= 10) and remaining = computed(...). In the template, @if (ready()) show <p class="reward">Reward ready</p>, @else show "{{ remaining() }} to go".
Explain
Why compute remaining instead of storing it?
Check
computed ready/remaining and an @if/@else — checked (T3):
seat_buildseat_fileseat_file5.5Lab: the Stamp Card9 minchecked · T3
Predict
A test clicks Stamp ten times then reads the page. What must it find?
Lesson
The card complete: signal state, update/set, two computed values, bindings for disabled and class, an @if message. A supplied component test clicks the buttons and reads the DOM, the way a user would.
Demo
The tutor copies the supplied card.spec.ts into the demo, runs ng test, reads the Vitest output, then breaks the threshold to 11 and shows the failing assertion naming what it expected.
Hands-on
Finish CardComponent: Stamp, Reset (disabled at 0), the count, .full class at ten, the @if reward message. Copy node/ng050/samples/s5/card.spec.ts into src/app and run ng test; make it pass.
Explain
What state does the card have, and what is derived?
Check
Stamp Card passes the supplied component test — checked (T3):
seat_buildseat_test
Session 6 · Lists and inputs
Five steps from one thing to many. A typed array of records in a signal; @for with track to render one element per item; adding and removing by making a new array. A child component that receives one item through input() and tells its parent something through output(). Live in the seat: a list of repairs rendered from an array, a record type that catches a misspelt field, a RepairRow child receiving its repair, and a Done event bubbling up to remove the row.
Lab A Repairs list: an array of typed repair records in a signal, each rendered by a RepairRow child that receives its repair via input() and emits done via output(); the parent removes the row. Session 6 checkpoint.
5 mini steps · ~40 min
6.1Typed arrays of records8 minchecked · T3
Predict
type Repair = { item: string; cents: number }. Is { item: 'Kettle', cents: '1200' } a Repair?
Lesson
A record type names the shape of one thing: type Repair = { id: number; item: string; cents: number }. An array of them in a signal, signal<Repair[]>([...]), is the list the view renders. The type catches a wrong field at build time.
Demo
The tutor declares Repair in the demo, fills a signal with three repairs, misspells cents as cent in one and shows the build error; fixes it; shows {{ repairs().length }}.
Hands-on
In node/ng050/s6/repairs (supplied app) define type Repair with id, item and cents, and repairs = signal<Repair[]> with three repairs. Show the count in the template.
Explain
What did the type buy you when you misspelt a field?
Check
Repair type and a signal of three repairs — checked (T3):
seat_buildseat_file6.2@for with track7 minchecked · T3
Predict
@for (r of repairs(); track r.id) { <li>{{ r.item }}</li> } with three repairs renders?
Lesson
@for (r of repairs(); track r.id) { … } renders the block once per item. track names a stable identity so Angular moves rather than rebuilds rows when the list changes. @empty renders when the list has nothing.
Demo
The tutor renders the demo repairs as li with @for, adds @empty with a message, empties the array and shows the message; then formats cents with the formatCents idea from session 3.
Hands-on
Render repairs() with @for and track r.id as <li class="repair"> showing item and the amount formatted from cents. Add an @empty block with "No repairs yet".
Explain
What is track for?
Check
@for with track r.id and an @empty block — checked (T3):
seat_buildseat_fileweb_dom6.3Adding and removing immutably8 minchecked · T3
Predict
this.repairs().push(newRepair) versus this.repairs.update(l => [...l, newRepair]). Which re-renders reliably?
Lesson
Change a list in a signal by making a new array: add with [...list, item], remove with list.filter(r => r.id !== id). Angular sees a new value and the @for updates. Next ids come from a counter, never from the list length.
Demo
The tutor adds an Add button that appends a fixed repair with a fresh id and a Remove button per row that filters, showing the rows appear and disappear; then shows push failing to update.
Hands-on
Add add() appending { id: nextId++, item: 'Umbrella', cents: 1500 } and remove(id: number) filtering the list; an Add button and a Remove button in each li.
Explain
Why filter into a new array rather than splice?
Check
add() spreads, remove() filters — checked (T3):
seat_buildseat_filecraft6.4A child component with input()8 minchecked · T3
Predict
RepairRow has repair = input.required<Repair>(). Parent writes <app-repair-row /> with no binding. Result?
Lesson
A child component receives data through input(): repair = input.required<Repair>(). The parent binds it with [repair]="r" inside the @for. The child reads repair().item. Data flows down, one way.
Demo
The tutor extracts the demo li into RepairRowComponent with a required input, binds it from the parent loop, and shows the build error when the binding is omitted.
Hands-on
Create repair-row.ts (selector app-repair-row) with repair = input.required<Repair>() and a template showing item and formatted cents. Use it inside the @for with [repair]="r".
Explain
Which way does data flow through input()?
Check
RepairRow has a required input bound from the parent — checked (T3):
seat_buildseat_fileseat_file6.5Lab: the Repairs list with output()9 minchecked · T3
Predict
done = output<number>() in the child; this.done.emit(this.repair().id). The parent listens with?
Lesson
Events flow up: the child declares done = output<number>() and emits its id when its Done button is clicked; the parent binds (done)="remove($event)". Down with inputs, up with outputs: the parent stays the owner of the list.
Demo
The tutor adds the output to the demo row and the (done) binding to the parent, clicks Done on a row and watches it vanish; then runs the supplied spec that renders three rows and clicks one.
Hands-on
Add done = output<number>() and a Done button in RepairRow; bind (done)="remove($event)" in the parent. Copy samples/s6/repairs.spec.ts into src/app and make ng test pass.
Explain
Why does the child emit an id instead of removing the item itself?
Check
Rows render from inputs and a Done output removes a row — checked (T3):
seat_buildseat_fileseat_fileseat_test
Session 7 · Forms and testing
Five steps on taking input properly. A typed reactive form: FormGroup and FormControl with types, bound to inputs with formGroup and formControlName. Validators for required, min and pattern; showing an error only once a field is touched. Submitting and resetting. Then your own component test: render, type, click, assert. Live in the seat: a form that rejects an empty item, an amount entered in pounds parsed into cents on submit, an error message that waits until the field is touched, and a Vitest test that fills the form and checks the list grew.
Lab An Add Repair form: typed controls for item and amount, validation with messages, submit adds a record in cents to the list from session 6 and resets the form; a component test fills and submits it. Session 7 checkpoint.
5 mini steps · ~42 min
7.1A typed reactive form8 minchecked · T3
Predict
form = new FormGroup({ item: new FormControl('', { nonNullable: true }) }). form.value.item has type?
Lesson
Reactive forms live in the class: a FormGroup of FormControls with types. The template binds [formGroup] and formControlName. ReactiveFormsModule is imported by the component. getRawValue() returns the fully typed value.
Demo
The tutor builds the demo form group with item and amount controls, binds two inputs, types into them and logs form.getRawValue() on a button click to show the typed object.
Hands-on
In node/ng050/s7/repairs (your session 6 app, supplied as a starter) add a FormGroup with item (string) and amount (string) controls, nonNullable, and a form with two inputs bound by formControlName. Import ReactiveFormsModule.
Explain
Where does the form state live, and why is that useful?
Check
A FormGroup with item and amount bound in the template — checked (T3):
seat_buildseat_fileseat_file7.2Validators and error messages8 minchecked · T3
Predict
item has Validators.required. The user has not touched it yet. form.invalid is…
Lesson
Validators.required, minLength and pattern mark a control invalid. Show the message only when the control is touched or the form was submitted: @if (form.controls.item.touched && form.controls.item.invalid). The submit button follows [disabled]="form.invalid".
Demo
The tutor adds required to item and a pattern of digits with an optional two decimals to amount, shows the messages appearing after blur, and disables the submit button while invalid.
Hands-on
Add Validators.required to item and Validators.pattern(/^\d+(\.\d{1,2})?$/) to amount. Show a <p class="error"> for each when touched and invalid; bind the submit button's [disabled] to form.invalid.
Explain
Why wait for touched before showing the error?
Check
required and pattern validators with touched-gated messages — checked (T3):
seat_buildseat_fileseat_file7.3Submit, parse to cents, reset8 minchecked · T3
Predict
amount is "12.5". Which gives 1250 cents without float error?
Lesson
(ngSubmit)="save()" runs on submit. Read getRawValue(), convert amount to cents (Math.round(Number(s) * 100)), add a record to the list signal, then form.reset(). Guard with if (this.form.invalid) return.
Demo
The tutor wires (ngSubmit) in the demo, types Kettle and 12.50, submits, and a new row for 1250 cents appears while the fields clear; then submits with an empty item and nothing happens.
Hands-on
Add (ngSubmit)="save()" on the form. save() returns early if invalid, converts amount to cents with a toCents() helper, appends a Repair with a fresh id, and resets the form.
Explain
Why convert to cents on submit rather than store the string?
Check
save() guards, converts to cents, appends and resets — checked (T3):
seat_buildseat_fileseat_file7.4Your first component test9 minchecked · T3
Predict
A test sets an input's value with el.value = "Kettle". The form control still reads "". Why?
Lesson
A component test with TestBed: configure, create the fixture, detectChanges, query the DOM, dispatch events, assert on what the user would see. Arrange, act, assert. Vitest runs it headless under ng test.
Demo
The tutor writes demo repairs.spec.ts: renders the component, counts li.repair (3), fills item and amount with input events, clicks submit, detectChanges, expects 4 rows; runs ng test; then breaks an expectation to show the red.
Hands-on
Write src/app/repairs.spec.ts: a test named "adding a repair through the form grows the list" that fills the two inputs, dispatches input events, submits, and expects li.repair count 4. Run ng test.
Explain
Name the three parts of your test.
Check
The learner's own form test passes — checked (T3):
seat_fileseat_test7.5Lab: the Add Repair form, tested9 minchecked · T3
Predict
A test submits with item empty. What should the row count do?
Lesson
The form end to end: typed controls, validators, touched-gated messages, submit that parses to cents, reset, and two tests: the happy path and the invalid submit that must not add a row.
Demo
The tutor adds a second test, "invalid form does not add a repair", to the demo spec, runs both, and then shows the check reading the Vitest JSON report.
Hands-on
Add a second test "invalid form does not add a repair" (empty item, submit, still 3 rows). Make sure the built page shows the form with two inputs and a submit button, and both tests pass.
Explain
What does the invalid-submit test protect against?
Check
Form renders, both tests pass — checked (T3):
seat_buildweb_domseat_test
Session 8 · Capstone: Tally
Five steps to a finished page. Tally is an expense tracker: a typed Expense record with a category, a form with validation, a list rendered by a child row with input and output, totals per category computed from the list in cents, and a grand total. Reading a spec before coding; building the walking skeleton first; the pure totals function tested on its own; the page assembled and tested through the DOM; then the hidden tests the grader brings. Live in the seat: the spec turned into a component plan, a computed Map of totals, and the hidden Vitest file running against the finished component with expenses you never typed.
Lab Build Tally: ExpenseForm, ExpenseRow and a Tally parent with a computed per-category total and grand total, all in cents, with your own tests; then pass the grader's hidden tests. Course placement step. Session 8 checkpoint.
5 mini steps · ~46 min
8.1Reading the spec8 minartefact · T2
Predict
The spec says totals must be exact to the cent. Which type holds an expense amount?
Lesson
Read node/ng050/samples/s8/TALLY-SPEC.md: Expense { id, item, category, cents }, categories fixed (Food, Travel, Tools, Other), per-category totals and grand total, a validated form, a Remove per row. Plan the components before writing one line.
Demo
The tutor reads the spec aloud, draws the component tree in a comment block — Tally → ExpenseForm, ExpenseRow* — names the state (one signal of Expense[]) and the derived values (two computed), and writes that into PLAN.md.
Hands-on
Write node/ng050/s8/tally/PLAN.md: the component tree, the one state signal, the computed values, and which session each piece comes from. No code yet.
Explain
What is the single source of truth in Tally?
Check
PLAN.md names the components and the state — artefact (T2):
seat_file8.2The walking skeleton9 minchecked · T3
Predict
type Category = 'Food' | 'Travel' | 'Tools' | 'Other'. category: 'Fod' is…
Lesson
Build the smallest thing that builds and renders: the Expense type with a Category union, the three empty components placed in the tree, a seed list of three expenses rendered as rows. No form, no totals yet.
Demo
The tutor creates the three components in the demo with minimal templates, the Expense type and Category union, a seed signal, builds, and shows three bare rows in the App Preview.
Hands-on
In node/ng050/s8/tally (supplied empty app) create expense.ts with Category and Expense, and TallyComponent, ExpenseFormComponent, ExpenseRowComponent wired in the tree; seed three expenses; render rows. Build.
Explain
Why build a skeleton before any feature?
Check
Skeleton builds with three components and three rows — checked (T3):
seat_buildseat_fileweb_dom8.3Totals as a pure function, tested first9 minchecked · T3
Predict
totalsByCategory([Food 250, Food 150, Travel 1000]) returns a Map. Map.get("Food")?
Lesson
Totals are a pure function over the list: totalsByCategory(expenses): Map<Category, number> and grandTotal(expenses): number, in cents. Write the tests first in totals.spec.ts, then the function; then wrap them in computed() in Tally.
Demo
The tutor writes three failing tests for the demo totals, implements with a reduce into a Map, runs them green, then adds totals = computed(() => totalsByCategory(this.expenses())) in the component.
Hands-on
Write src/app/totals.spec.ts with tests for an empty list, one category, two categories; then totals.ts exporting totalsByCategory and grandTotal. Expose both as computed signals in TallyComponent.
Explain
Why test the totals outside the component?
Check
totals.ts is pure and its three tests pass — checked (T3):
seat_fileseat_testseat_filecraft8.4Form, rows and totals on the page10 minchecked · T3
Predict
ExpenseForm emits a new Expense through output(). Who assigns the id?
Lesson
Assemble: ExpenseForm (typed form with item, category select, amount; validators; emits add) → Tally (owns the signal, appends with an id, removes on row output) → ExpenseRow (input expense, output remove). Totals and grand total rendered with @for over the Map entries and formatted from cents.
Demo
The tutor wires the demo form's add output into Tally, renders the totals table with @for over totals().entries() and a <strong class="grand">, and adds an expense live while the Food total updates.
Hands-on
Wire the form's add output into TallyComponent, render rows with remove, and a totals table (tr.total per category) plus <strong class="grand"> for the grand total, formatted with two decimals. Build and preview.
Explain
Trace what happens from submit to the grand total changing.
Check
The page shows form, rows, totals and grand total — checked (T3):
seat_buildseat_fileweb_dom8.5Lab: Tally proven10 minproven · T4
Predict
The grader seeds Tally with expenses you never saw and reads strong.grand. What makes your code pass?
Lesson
Tally complete and tested: your tally.spec.ts covers add through the form, remove through a row and the grand total; the grader adds a hidden spec that seeds different expenses and checks the totals table and the invalid-form guard.
Demo
The tutor runs the demo's tally.spec.ts, then copies in the course's example hidden spec from a previous term, shows it seeding four expenses and reading tr.total texts, and explains that the real hidden file differs.
Hands-on
Write src/app/tally.spec.ts with three tests: "adds an expense through the form", "removes an expense from a row", "grand total sums all cents". Build, run ng test, and ask the tutor to run the capstone check.
Explain
Why can the hidden test trust your totals without seeing your code?
Check
Tally passes your tests and the grader's hidden tests — proven (T4):
seat_buildseat_testcraft
Outcome
By the end of the course the tutor's checks will have verified: a page for every hands-on step; eight session labs that each grow the last — the profile page, the styled card grid, the reacting counter page, the first component, the signal-driven card, the list with a child component, the tested form; and the Tally capstone, an expense-tracker component whose list, category totals and form validation pass tests you did not see.
Certificate
Agile System Software Certificate — Angular from zero (NG050) attests that you can build, style and test a small Angular 22 application with components, signals, lists and a validated form, from a plain web page up, verified by a machine from your own seat.
To earn it:
- Pass the lab check of every session (steps 1.5, 2.5, 3.5, 4.5, 5.5, 6.5 and 7.5) on your own code.
- Pass the Tally capstone check (8.5) including the hidden Vitest tests the grader supplies.
- Score 80% or better on the 20-question reading quiz.
Gates (step · tier required):
Plus a 20-question reading quiz at 80%, retake after 48 h. Certificates are issued once the seat check runner is live; until then every step records what the engine saw.
Environment
A VS Code seat in your browser with Node 22, TypeScript 6, Angular 22 and Vitest, plus the App Preview tab that shows your page; all offline, nothing to install. Your work lives in node/ng050; the tutor's demo files live apart from yours and the tutor never edits your hands-on file while you are working on it.
Open Coding Tutor at codingtutor.agilesystemsoftware.com, pick NG050 from the course button in the chat and press Tutor me. Every lesson, demo and hands-on step is also published below.