This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Tutorial

Build and ship one complete AngularTS application from an empty directory.

This tutorial assumes basic HTML and JavaScript but no AngularTS knowledge. You will build a browser-owned task board with reactive state, form validation, routing, server data, tests, and a production checklist.

This application deliberately uses AngularTS’s optional typed programmatic views because its model and rendering live in TypeScript. If your server renders the HTML, follow the first application guide instead and keep the HTML you already have.

What you will build

The finished application can list tasks, add a task, open a task route, load initial data, and expose loading and error states.

Follow the tutorial

  1. Create the application
  2. Add the task board’s typed view
  3. Add reactive state
  4. Build the form
  5. Add routing and server data
  6. Test the behavior
  7. Prepare production
  8. Run the complete application

Complete the steps in order. Every page states the expected result before moving on.

1 - Create the application

Install AngularTS, define the application module, and bootstrap the page.

Before you start

Use a current Node.js release and an application bundler that supports TypeScript and browser ESM.

Install

npm install @angular-wave/angular.ts

Create the application entrypoint:

import { angular } from '@angular-wave/angular.ts';

export const app = angular.createModule('taskBoard', []);
angular.bootstrap(document, ['taskBoard']);

Create the host page:

<!doctype html>
<html lang="en">
  <body>
    <task-board></task-board>
    <script type="module" src="/src/app.ts"></script>
  </body>
</html>

Checkpoint

Start the development server. The page should load without a bootstrap or missing-module error. The custom element is empty until the next step.

Next step

Add the optional typed component view used by this tutorial.

2 - Add a typed view

Register a component whose model and DOM bindings are checked by TypeScript.

A typed view constructs real DOM and keeps controller reads reactive. Its main advantage over string templates is type safety between the component model and its bindings.

import { h1, main, p } from '@angular-wave/angular.ts';

class TaskBoard {
  title = 'Task board';
  tasks = [{ id: 1, title: 'Read the guide', done: false }];
}

app.component<TaskBoard>('taskBoard', {
  controller: TaskBoard,
  view: ({ controller }) =>
    main(
      h1(() => controller.title),
      p(() => `${controller.tasks.length} task(s)`),
    ),
});

The view is registered on the normal application module. It does not need a separate mount operation. Functions used as children or property values are reactive readers; plain values render once.

Checkpoint

The page should display “Task board” and one task count.

Next step

Render and update the collection.

3 - Add reactive state

Render a keyed collection and update it without replacing unrelated DOM.

Add a method to the controller:

class TaskBoard {
  nextId = 2;
  tasks = [{ id: 1, title: 'Read the guide', done: false }];

  add(title: string) {
    this.tasks = [...this.tasks, { id: this.nextId++, title, done: false }];
  }
}

Render the collection with stable keys:

import { each, li, ul } from '@angular-wave/angular.ts';

view: ({ controller }) =>
  ul(
    each(
      () => controller.tasks,
      (task) => task.id,
      (task) => li(() => task().title),
    ),
  ),

The item renderer receives a reader. Call task() inside reactive bindings so a same-key item replacement updates its row.

Checkpoint

Calling controller.add("Ship it") appends one row while retaining the existing row.

Next step

Build the form.

4 - Build the form

Bind typed DOM events, validation state, and a reactive disabled property.

Add draft and a submit method:

draft = "";

submit() {
  const title = this.draft.trim();
  if (!title) return;
  this.add(title);
  this.draft = "";
}

Add the form before the list:

import { button, form, input, label } from '@angular-wave/angular.ts';

form(
  {
    onsubmit: (event) => {
      event.preventDefault();
      controller.submit();
    },
  },
  label({ htmlFor: 'new-task' }, 'Task'),
  input({
    id: 'new-task',
    value: () => controller.draft,
    oninput: (event) => {
      controller.draft = (event.currentTarget as HTMLInputElement).value;
    },
  }),
  button({ disabled: () => !controller.draft.trim() }, 'Add'),
);

The callback expresses reactive intent directly. No separate computed wrapper is required.

Checkpoint

The button is disabled for blank input. Submitting valid text adds a row and clears the input.

Next step

Add routing and initial server data.

5 - Add routing and server data

Give the task board a URL and expose explicit loading and error states.

Register the component as a route:

app.router({
  name: 'tasks',
  url: '/tasks',
  component: 'taskBoard',
});

Place the route outlet in the host page:

<main ng-view></main>

Load initial data from a controller method:

loading = true;
error = "";
tasks: Task[] = [];

async onInit() {
  try {
    const response = await fetch("/api/tasks");
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    this.tasks = await response.json();
  } catch (error) {
    this.error = error instanceof Error ? error.message : "Load failed";
  } finally {
    this.loading = false;
  }
}

Render loading, error, empty, and populated states deliberately. An empty array does not prove loading succeeded.

Checkpoint

Opening /tasks renders the board. A failed request produces a readable error state.

Next step

Test the application behavior.

6 - Test the application

Separate model tests from browser-level rendering checks.

Start with a fast controller test:

it('adds a trimmed task', () => {
  const board = new TaskBoard();
  board.draft = '  Write tests  ';
  board.submit();

  expect(board.tasks.at(-1)?.title).toBe('Write tests');
  expect(board.draft).toBe('');
});

Then verify the user-visible contract in a browser:

test('adds a task', async ({ page }) => {
  await page.goto('/tasks');
  await page.getByLabel('Task').fill('Write tests');
  await page.getByRole('button', { name: 'Add' }).click();
  await expect(page.getByText('Write tests')).toBeVisible();
});

Test behavior, not internal scope fields or generated DOM comments. Keep network outcomes deterministic.

Checkpoint

Both the model test and browser test pass, including the loading failure case.

Next step

Prepare the application for production.

7 - Prepare production

Apply the minimum production, security, and operability checks to the task board.

Before deployment:

  • Build with production optimization and an intentional source-map policy.
  • Serve hashed assets with long-lived caching and HTML with revalidation.
  • Keep API URLs and secrets out of the browser bundle.
  • Enforce HTTPS and an explicit Content Security Policy.
  • Exercise loading, empty, error, offline, and slow-network states.
  • Test keyboard navigation and visible focus.
  • Record the AngularTS version in the lockfile.
  • Run type checking, unit tests, browser tests, and integration checks in CI.

Expected result

The application can be deployed repeatedly, reports failures without exposing secrets, and remains usable when requests fail.

Continue with Production and deployment and Best practices.

8 - Run the complete application

Open the maintained task-board example and compare it with your implementation.

The repository contains a browser-runnable version of the task board:

  • docs/examples/task-board/task-board.ts is compiled as a package consumer.
  • docs/static/examples/task-board/task-board-demo.html provides the host page.
  • docs/static/examples/task-board/task-board.js registers the component view.

Open the task-board example.

The example keeps data local so it runs without a backend. The routing and server-data step shows where application-owned transport behavior belongs.

Compare behavior

Verify that both versions:

  1. Render the initial keyed collection.
  2. Disable submission for blank text.
  3. Trim accepted task titles.
  4. Clear the input after submission.
  5. Preserve existing rows when a task is appended.

CI type-checks the TypeScript application and runs the browser interaction contract. Documentation checks also validate its AngularTS API usage.