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

Return to the regular view of this page.

Task guides

Implement complete AngularTS workflows with explicit state, failure, lifecycle, accessibility, and verification behavior.

These guides extend one task-board application. They explain implementation boundaries rather than presenting isolated snippets.

Start with server-rendered HTML

Do not start with an SPA. Keep the model, rules, URLs, and rendering together on the server. Send useful HTML first, then add AngularTS to the smallest region that needs browser behavior.

Even when using the router, prefer server-rendered route fragments. Move the model into the browser only for the most complex client-owned features. When the browser takes over model consistency and compilation, use typed programmatic views as the primary view style. REST services, explicit caching, and a browser-facing security policy also become primary at that same threshold. Until then, prefer normal HTTP pages, forms, and server-rendered fragments.

Apply this decision per module. A server-rendered login and public site can live beside a dashboard SPA in the same application. The dashboard’s complexity does not require every other module to move into the browser.

Read Keep the application on the server before choosing the application structure.

Build the application

  1. Keep the application on the server
  2. Structure the application
  3. Build and validate a form
  4. Load server data
  5. Add routing
  6. Handle errors
  7. Test behavior

Make it production-ready

Start with the complete tutorial if you have not built an AngularTS component.

1 - Keep the application on the server

Render HTML and keep domain logic on the server until the browser must own a genuinely complex application model.

Start with HTML from the server

Do not start by building an SPA. Render the useful page on the server and use normal links and forms. Keep domain models, validation, authorization, transactions, URLs, and HTML generation together for as long as possible.

This is the simplest way to keep the model consistent. The server reads current data, applies the rules, and renders the result in one place. The browser receives HTML it can display immediately. There is no second model to synchronize, no client startup request for content the server already had, and no duplicated loading, routing, validation, and error behavior.

HTML and server rendering are the default application architecture. AngularTS is there to add behavior where the browser can improve an already useful page.

Add behavior without taking over the page

Put ng-app around the smallest region that needs local interaction. Use scope, events, effects, HTTP directives, and DOM references for immediate browser behavior while the server continues to own the page and its durable model.

Keep these on the server:

  • Initial and durable application state
  • Validation and authorization
  • Data loading and mutations
  • Primary URLs and navigation
  • HTML for complete pages and reusable fragments
  • Error, empty, permission, and maintenance responses

Do not fetch JSON after startup merely to rebuild HTML that the server could have sent in the first response.

Keep router content server-rendered

Using the AngularTS router does not require moving rendering or the model into the browser. Let the router coordinate transitions and component lifetimes, but keep route content as server-rendered HTML fragments while that remains practical.

This preserves one server model while still allowing faster transitions, long-lived browser resources, and local interaction state. A router is not a reason to duplicate server templates as client templates.

Move the model only as a last resort

Move model ownership into the browser only when the feature genuinely needs it. Examples include substantial offline work, high-frequency local editing, complex cross-screen state, or an application-like workspace whose model must remain alive independently of server navigation.

Before making that move, confirm that server-rendered fragments, focused interactive regions, or a small client model cannot solve the problem. Most pages should never cross this line.

Move compilation with the model

Once the browser owns the model, it also owns model consistency and view compilation. Do not keep a complex client model on one side and loosely typed HTML template bindings on the other.

For a client-owned screen, optional typed programmatic views can keep model access, DOM properties, events, and generated structure in the same checked TypeScript code. HTML templates remain available for client-owned screens and are the default for server-owned pages, server-rendered fragments, and small enhancements.

Move the data boundary with the model

Before the browser owns the model, prefer simple HTTP: normal navigation, form submissions, and server-rendered HTML fragments. The server already has the authoritative data and can return the final representation directly.

Once the browser owns a complex application model, REST services become the primary server boundary. At that point the frontend also needs an explicit cache strategy and security policy:

  • Define stable request, response, validation, pagination, and error contracts.
  • Decide which model owns each cached record and how writes invalidate it.
  • Handle stale data, concurrent writes, retries, and offline state deliberately.
  • Define credential handling, authorization failures, CSRF or token policy, cross-origin access, content security, and how client code avoids secrets.

Do not introduce this REST, cache, and security machinery for a page that can submit a form or request a server-rendered fragment. It becomes primary only when the browser has genuinely taken ownership of the model.

Let modules use different architectures

The ownership decision is not application-wide. AngularTS applications are multimodular, and each module or ng-app root can stop at a different point in the progression.

For example:

AreaAppropriate architecture
Sign-in and account recoveryServer-rendered HTML forms and normal HTTP
Public content and checkoutServer pages with focused AngularTS enhancements
Dashboard workspaceClient router, browser-owned model, REST, cache policy, and a chosen view style
Reports and downloadsServer-generated files and normal browser navigation

The login form does not need to become part of the dashboard SPA. The dashboard can start as its own ng-app after authentication and own only the screens whose complexity justifies it. Both modules can share the same server session and authorization rules without sharing a frontend architecture.

This keeps complexity contained. A client-owned dashboard does not force public, authentication, settings, or support pages to adopt its router, model cache, or rendering strategy.

Use this order

Start hereEscalate only when needed
Server-rendered pageSmall AngularTS-enhanced region
Normal links and formsServer-rendered router fragment
Server-owned modelSmall browser interaction model
Simple HTTP pages and fragmentsREST, cache, and browser security policy
Server HTML templatesOptional typed view when stronger checks are useful
One server-rendered moduleOne client-owned module where justified

At every step, stop when the current approach solves the user problem. The last row is not a target architecture. It is the option for the minority of features that truly need the browser to own the application model.

Verify the decision

For each client-owned model, write down why a server-rendered page, server fragment, or focused enhancement is insufficient. Test direct navigation, JavaScript failure, authorization, validation, refresh, Back and Forward, stale data, deployment rollback, and recovery after a failed request.

2 - Structure an application

Separate AngularTS registration, UI state, domain operations, and external resources without adding speculative layers.

Start with server rendering

Follow the server-first guideline. Keep domain models, validation, authorization, navigation, and HTML rendering together on the server. Render the useful document and initial state before AngularTS starts.

Place an AngularTS root around the smallest region that needs browser behavior. Do not create a browser copy of the domain model merely to make the page interactive. Local selection, disclosure, pending state, and DOM behavior can remain small browser concerns while durable state stays on the server.

Do not choose an SPA as a default project shape. If client routing is justified, continue loading server-rendered route fragments while the server can own the model. Move the model into the browser only when complex offline, editing, cross-screen, or long-lived workspace behavior requires independent client ownership.

When that move happens, the browser becomes responsible for model consistency and view compilation. If compile-time checks between that model and its DOM are valuable, the module can use optional typed programmatic views. HTML templates remain supported.

The same threshold changes server communication. Server-owned pages should use normal HTTP navigation, forms, and rendered fragments. A browser-owned model needs REST contracts, explicit cache ownership and invalidation, and a complete browser security policy. Introduce those systems together with frontend model ownership, not before it.

Mix architectures by module

Do not force one rendering architecture across the product. Use separate modules or ng-app roots for areas with different ownership needs.

A sign-in module can remain a server-rendered HTML form. After authentication, a dashboard module can bootstrap a client router, keep a complex model in the browser, and use REST and caching. It can independently choose HTML or typed programmatic views. Public pages, reports, and account recovery can continue using server rendering and normal navigation.

Keep shared concerns on the server: session identity, authorization, domain rules, and durable state. Share frontend services only when two client-owned modules genuinely need the same browser resource or model.

Start from ownership, not file types

A component owns DOM-facing state and its view lifetime. A model owns shared reactive domain state. A service owns an operation or external resource. A module records how those pieces are assembled.

Use one feature directory until a capability is independently reusable:

tasks/
  task-module.ts
  task-list.ts
  task-repository.ts
  task-list.test.ts

Avoid global controllers/, services/, and views/ directories once features evolve independently; they scatter one change across the repository.

Register one feature module

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

export class TaskRepository {
  async list() {
    const response = await fetch('/api/tasks');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }
}

export const tasks = angular
  .createModule('tasks', [])
  .service('taskRepository', TaskRepository);

Keep registration free of request execution and DOM access. Registration describes recipes; runtime methods perform work.

Keep view state local

A task-list controller may own loading, error, selection, and filter text. Move task data to a named model only when multiple component roots need reactive access to the same records. Move loading into a repository only when the repository intentionally deduplicates or caches requests.

Do not introduce “manager” or “facade” layers without a lifetime or policy they own. A forwarding class adds indirection but no boundary.

Verify the structure

A feature passes the ownership test when its component can be destroyed without leaking resources, its domain logic can be tested without a DOM, and its repository can be replaced in a test without changing view code.

3 - Build and validate a form

Model AngularTS form submission, validation visibility, duplicate prevention, and accessible recovery as one workflow.

Separate validity from validation visibility

A field can be invalid before the user has interacted with it. Show field feedback after blur or a failed submit, but always prevent invalid submission.

<form name="taskForm" ng-submit="submit(taskForm)" novalidate>
  <label for="task-title">Task title</label>
  <input
    id="task-title"
    name="title"
    ng-model="draft.title"
    required
    aria-describedby="task-title-error"
  />
  <p
    id="task-title-error"
    role="alert"
    ng-show="submitted && taskForm.title.$invalid"
  >
    Enter a task title.
  </p>
  <button ng-disabled="saving">Add task</button>
</form>

The controller owns submitted, saving, and the editable draft. The repository owns the write.

$scope.submit = async (form) => {
  $scope.submitted = true;
  if (form.$invalid || $scope.saving) return;

  $scope.saving = true;
  try {
    await taskRepository.add($scope.draft);
    $scope.draft = { title: '' };
    $scope.submitted = false;
  } finally {
    $scope.saving = false;
  }
};

Do not clear the draft before the server confirms success. Disable or reject repeated submission while a non-idempotent write is pending. Return focus to the first invalid field after a failed submit and announce server failures separately from field validation.

Use browser constraints for semantics, AngularTS form state for view coordination, and server validation as the authority. A disabled button does not enforce a business rule.

4 - Load server data

Own AngularTS HTTP requests, cancellation, stale-result protection, and explicit screen states.

Represent request state explicitly

Do not use tasks.length === 0 to mean both “not loaded” and “loaded empty.”

type LoadState = 'idle' | 'loading' | 'ready' | 'error';

class TaskList {
  state: LoadState = 'idle';
  tasks: Task[] = [];
  error = '';
}

Put transport behavior in a repository

app.factory('taskRepository', [
  '$http',
  ($http) => ({
    list: (cancelled) =>
      $http
        .get('/api/tasks', { timeout: cancelled })
        .then((response) => response.data),
  }),
]);

The timeout promise should resolve when the component is destroyed or a newer query supersedes the request. Cancellation saves work; a request sequence or identity check still protects against transports that complete while cancellation is racing.

Set loading before starting. On success, replace the collection property so typed readers observe the commit. In finally, clear loading only if the completing request is still current. Render loading, stale data, empty success, recoverable error, and permission failure deliberately.

Use the $http guide for interceptors, defaults, caching, and response contracts.

5 - Add routing

Register AngularTS route trees, choose resolve boundaries, and make navigation cleanup and failure behavior explicit.

Do not default to client-side routing

Use server routes and ordinary links first. They provide addressability, history, streaming HTML, metadata, authorization boundaries, and resilient navigation without a client router.

Introduce the AngularTS router when multiple screens are intentionally owned by one bootstrapped application and preserving client state or resources between those screens materially improves the product. A client router is not required for AngularTS components embedded in server-rendered pages.

Keep route snippets server-rendered for as long as possible. Let the router own the transition, URL state, and component lifetime while the server still renders the route HTML from its authoritative model. Do not move the model and rendering into the browser merely because navigation no longer reloads the document.

If a route eventually needs a complex browser-owned model, move compilation with that model. A typed programmatic view is available when the screen benefits from compile-time checks between the model and DOM; it is not required by the router.

That transition also makes REST services, frontend cache consistency, and browser-facing security policy primary concerns. A router using server-rendered fragments can continue using simple HTTP; client navigation alone does not justify building a separate API and cache layer.

Scope the router to the module that needs it. A dashboard can be a full SPA under its own ng-app while sign-in, recovery, public pages, and downloads remain server-rendered routes. Adding a dashboard router does not make the entire application a client-owned router tree.

Register components as screens

The router is part of the built-in runtime; do not add a speculative ng.router module dependency.

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

const routes = [
  {
    name: 'tasks',
    url: '/tasks',
    component: 'taskList',
  },
  {
    name: 'tasks.detail',
    url: '/{taskId:int}',
    component: 'taskDetail',
  },
] as const satisfies readonly ng.RouterModuleDeclaration[];

app.router(routes);
<nav aria-label="Tasks">
  <a ng-state="'tasks'">All tasks</a>
</nav>
<main ng-view></main>

Use stable state names as application identifiers. URLs may evolve; state names are referenced by links, transitions, tests, and diagnostics.

Resolve identity or authorization data that must exist before the screen can be valid. Load optional panels and large collections inside the component so the route can render a shell. Parent states own shared layout and resolves; children should not refetch parent-owned data.

Test direct URL entry, back/forward, invalid parameters, same-state parameter changes, rejected resolves, and leaving during pending work. Continue with the routing overview.

6 - Handle errors

Classify AngularTS domain, transport, transition, and programming failures and give each one the correct owner.

Classify before rendering

  • Validation and domain rejection are expected outcomes near the action.
  • HTTP timeout, abort, offline, and non-success status belong to the request owner.
  • Route resolve failure belongs to transition policy.
  • Unexpected exceptions belong to $exceptionHandler and diagnostics.

Do not convert every rejection to [], null, or false. That destroys information and commonly renders an empty success state after a real failure.

Wrap low-level errors with the operation name while retaining the original cause. The UI should receive a stable category and recovery action, not a stack trace or server payload. Logs may include release, route, operation, and correlation identifiers but must exclude credentials and private data.

A repository may retry a safe idempotent read. A component should own the retry button. Global interceptors should refresh credentials or normalize protocol behavior, not retry every request indiscriminately.

Unexpected exceptions should reach the $exceptionHandler reference. Test that expected failures remain recoverable and unexpected exceptions are reported.

7 - Test application behavior

Allocate AngularTS tests across plain logic, typed contracts, browser integration, lifecycle, and failure boundaries.

Use the narrowest layer that can fail

Test transformations and domain transitions as plain TypeScript. Compile typed component views to catch invalid model properties, event types, and DOM property names. Use a browser for compilation, directives, focus, routing, and reactive rendering.

test('task form prevents duplicate submission', async ({ page }) => {
  await page.goto('/tasks');
  await page.getByLabel('Task title').fill('Write tests');
  await page.getByRole('button', { name: 'Add task' }).dblclick();
  await expect(page.getByRole('listitem')).toHaveCount(1);
});

Select by role, label, visible text, or stable application identifiers. Scope IDs, generated comments, internal CSS classes, and scheduler timing are implementation details.

For each asynchronous screen, cover loading, stale data, empty success, recoverable failure, retry, cancellation on navigation, and late completion. Control server responses rather than adding sleeps. For resource owners, enter and leave repeatedly and assert listener and connection counts return to baseline.

Run critical browser contracts against the production bundle.

8 - Troubleshoot an application

Locate AngularTS bootstrap, injection, compilation, reactive, routing, and request failures by testing boundaries in order.

Find the first failed boundary

  1. Confirm the JavaScript module loaded.
  2. Confirm registration completed before automatic or manual bootstrap.
  3. Confirm ng-app names the created module.
  4. Read the first AngularTS error, not the cascade.
  5. Confirm injected token order matches function parameters.
  6. Confirm the owning scope/controller contains the value read by the view.
  7. Inspect transport status before changing rendering code.

Raw interpolation means bootstrap or compilation did not complete. “Module not available” usually means boot order or spelling. “Unknown provider” means the token is absent from the loaded module graph.

For typed views, verify changing positions receive a function reader rather than a snapshot. Call keyed item readers inside nested bindings. Replace collection properties in controller methods. ESM applications import tag factories such as div and button directly from @angular-wave/angular.ts; no-build UMD applications use angular.tags. Controller typing is supplied to component<T>(); use DOM property names such as htmlFor.

Remove sibling features while preserving the failing owner, data, and timing. Do not hide races with timeouts. Use the error catalog after identifying the failed boundary.

9 - Production and deployment

Produce, verify, deploy, observe, and roll back an AngularTS artifact without relying on development behavior.

Produce one immutable artifact

Install from the lockfile, typecheck source and tests, validate generated bindings, run browser tests, and build once. Promote the same artifact through environments. Rebuilding after approval changes the release.

Fingerprint immutable assets. Revalidate HTML and route manifests so they reference current fingerprints. Version service-worker caches and remove obsolete entries only after the new worker can serve a complete application.

Verify production behavior

Smoke-test generated output with development fallbacks disabled. Cover direct route entry, refresh on nested URLs, CSP, API credentials, loading and failure states, offline behavior, and service-worker upgrade from the previous release.

Design rollback before deployment

Retain the previous artifact. Determine whether storage migrations, server contracts, or service-worker caches make rollback incompatible. Prefer additive data changes.

Attach a release identifier to errors and operations. After deployment, verify asset integrity, critical routes, request latency, error rate, and one synthetic workflow. Stop promotion or roll back from explicit thresholds.

Follow the upgrade procedure for framework changes.

10 - Browser support

Define an AngularTS browser contract from required Web APIs, compiled output, fallbacks, and production tests.

List browser families and versions, mobile requirements, assistive-technology coverage, and optional capabilities. Framework support does not automatically provide WebTransport, service workers, storage, workers, or WebAssembly everywhere.

Isolate capability selection:

function createRealtime() {
  if ('WebTransport' in window) return createWebTransport();
  if ('WebSocket' in window) return createWebSocket();
  return createPolling();
}

Detect the API used, not a browser brand. Keep fallback selection in one service so components consume domain events rather than transport differences.

Test production output: cold load, direct route entry, back/forward, storage denial, offline transition, reduced motion, keyboard navigation, and service-worker upgrade. Disable capabilities to test fallbacks even when CI supports them.

When no fallback exists, render an actionable unsupported state before constructing the service. A runtime exception is not a support policy.

11 - Accessibility

Preserve semantics, focus, validation, announcements, and motion preferences across AngularTS structural and route updates.

ng-if removes content and destroys focused descendants. Before closing a dialog, menu, or editor, choose the element that regains focus. ng-show retains the subtree; verify hidden descendants cannot remain in keyboard order.

Use native buttons, links, labels, headings, and lists before ARIA. Attach AngularTS events to those controls instead of rebuilding their keyboard behavior on generic elements.

Associate errors with controls using aria-describedby. Show invalid state after interaction or submission, retain the invalid value, focus the first invalid control after failed submission, and provide a summary for long forms. Server failures are separate from field constraints.

Use a polite live region for completion or background updates and an alert for failures requiring immediate attention. Do not place an entire keyed list in a live region.

Test direct route entry, route transitions, keyed reordering, loading replacement, reduced motion, zoom, keyboard-only operation, and a screen-reader workflow. Automated rules cannot determine whether focus movement makes sense.

12 - Internationalization

Design AngularTS message ownership, locale formatting, asynchronous catalogs, and bidirectional layouts.

Use stable message identifiers and named placeholders. Do not concatenate translated fragments or use English text as an identifier. Choose plural forms from locale rules rather than count === 1.

AngularTS formatting filters delegate to Intl. Pass the application locale and explicit currency or time-zone policy; browser defaults may differ from user settings.

<span>{{ total | currency:user.locale }}</span>
<time>{{ dueAt | date:user.locale }}</time>

A locale service should deduplicate catalog requests, cache by locale and catalog version, and expose loading and fallback state. Do not render raw message identifiers while loading.

Set document or feature lang and dir. Prefer logical CSS properties. Test long labels, narrow screens, mixed-direction names, non-Latin input, plural edge cases, currency minor units, and dates around daylight-saving transitions.

Locale switching should invalidate message and formatter readers without rebuilding unrelated domain state.

13 - Application security

Apply AngularTS trust boundaries to templates, typed properties, URLs, requests, storage, workers, and service workers.

External sources include URL parameters, forms, storage, server responses, realtime messages, worker messages, and cross-window events. Sensitive sinks include HTML, executable URLs, navigation, credentials, logs, and persisted state.

Prefer interpolation, text children, and typed DOM properties. Use ng-bind-html only for markup that passed application sanitization. A trust override must be local and reviewable; never trust a value merely because it came from your server.

Hidden routes, disabled buttons, and client guards improve UX but do not authorize operations. The server must validate identity, permission, input shape, size, and state transition.

Configure credentials and XSRF behavior for known origins. Reject unexpected redirect and content types at the repository boundary. Store the minimum browser data, validate versioned records on read, and never persist bearer credentials for convenience.

Deploy HTTPS and a specific Content Security Policy. Review worker and service-worker scope, allowed connection origins, update behavior, and rollback. Security failures should remain visible rather than trigger broad policy relaxation.

14 - Observability

Correlate AngularTS navigation, requests, workflows, rendering outcomes, and unexpected failures without recording sensitive state.

Assign a stable operation name and correlation ID at the user-action or route boundary. Record start, terminal outcome, duration, release, and route. Do not log every reactive read or scope write; volume hides causality and changes timing.

performance.mark(`task-save:${id}:start`);
try {
  await repository.save(task);
  record({ operation: 'task.save', outcome: 'success', id });
} catch (error) {
  record({ operation: 'task.save', outcome: classify(error), id });
  throw error;
}

Expected domain and transport failures should produce classified outcomes. Unexpected exceptions should reach $exceptionHandler with their cause. Redact credentials, request bodies, user-entered text, and model snapshots.

Carry one correlation ID through route transition, request, retry, and workflow command. Record cancellation separately from failure. For realtime connections, record state transitions and reconnect exhaustion, not every message.

After deployment, verify release tagging, source-map policy, critical routes, error rate, request latency, and one synthetic path. Every alert needs an owner and actionable threshold.