Agile System SoftwareLearn

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.

  • LevelBeginner
  • Durationself-paced ~20–25 h
  • Size8 sessions · 40 mini steps · ~326 min of lessons
  • EnvironmentA 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
  • PrerequisitesNone. If you already know JavaScript or TypeScript, take the placement step 8.5 or start at NG100.
  • CertificateAgile System Software Certificate — Angular from zero (NG050)

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).

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. 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_dom seat_file

    #s1-1

  2. 1.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_dom

    #s1-2

  3. 1.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_dom

    #s1-3

  4. 1.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_file

    #s1-4

  5. 1.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_dom seat_file

    #s1-5

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
  1. 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_dom seat_file

    #s2-1

  2. 2.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_file web_dom

    #s2-2

  3. 2.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_file

    #s2-3

  4. 2.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_dom seat_file

    #s2-4

  5. 2.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_file web_dom

    #s2-5

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
  1. 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_build seat_file web_dom

    #s3-1

  2. 3.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_build seat_file craft

    #s3-2

  3. 3.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_build seat_file seat_file

    #s3-3

  4. 3.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_build seat_file web_dom

    #s3-4

  5. 3.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_build seat_test web_dom craft

    #s3-5

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
  1. 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_file

    #s4-1

  2. 4.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_build seat_file seat_file

    #s4-2

  3. 4.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_build seat_file seat_file

    #s4-3

  4. 4.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_build web_dom seat_file

    #s4-4

  5. 4.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_build seat_file web_dom

    #s4-5

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
  1. 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_build seat_file

    #s5-1

  2. 5.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_build seat_file seat_file

    #s5-2

  3. 5.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_build seat_file seat_file

    #s5-3

  4. 5.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_build seat_file seat_file

    #s5-4

  5. 5.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_build seat_test

    #s5-5

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
  1. 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_build seat_file

    #s6-1

  2. 6.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_build seat_file web_dom

    #s6-2

  3. 6.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_build seat_file craft

    #s6-3

  4. 6.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_build seat_file seat_file

    #s6-4

  5. 6.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_build seat_file seat_file seat_test

    #s6-5

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
  1. 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_build seat_file seat_file

    #s7-1

  2. 7.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_build seat_file seat_file

    #s7-2

  3. 7.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_build seat_file seat_file

    #s7-3

  4. 7.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_file seat_test

    #s7-4

  5. 7.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_build web_dom seat_test

    #s7-5

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
  1. 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_file

    #s8-1

  2. 8.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_build seat_file web_dom

    #s8-2

  3. 8.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_file seat_test seat_file craft

    #s8-3

  4. 8.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_build seat_file web_dom

    #s8-4

  5. 8.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_build seat_test craft

    #s8-5

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.