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

Return to the regular view of this page.

Directive Guides

Directives are easier to learn by role than by name. Start here when you want to understand how AngularTS extends HTML, then use the Directives reference for individual attributes and API details.

Learning Path

  1. Overview: how directives are discovered, compiled, linked, and prioritized.
  2. Data Binding: directives that synchronize scope data with text, HTML, classes, styles, and form controls.
  3. Structural Directives: directives that add, remove, repeat, switch, include, or protect DOM sections.
  4. Forms: validation, model options, form state, and messages.
  5. HTTP Directives: declarative requests and Server-Sent Events from HTML.
  6. Animation Directives: CSS hooks and animation-specific attributes.
  7. Advanced Directives: workers, WebAssembly, channels, DOM references, accessibility, and event helpers.

Reference Lookup

When you already know the directive name, use the per-directive reference. The reference keeps pages small and specific, while this section keeps the conceptual grouping that helps new users build a mental model.

1 - Advanced directives: workers, WASM, channels, viewport

AngularTS advanced directives for Web Workers, WebAssembly, lazy loading, event channels, DOM references, and accessibility — all declarative in HTML.

AngularTS ships several advanced directives that expose modern browser APIs — Web Workers, WebAssembly, Intersection Observer, and custom event channels — directly in HTML without requiring JavaScript. These directives are registered as part of the core ng module and are available in every AngularTS application.

Controller and scope directives

ng-controller

Attaches a controller function to a section of the DOM, creating a new child scope populated by the controller.

  <p>{{ ctrl.title }}</p>
</div>
  .controller('TodoController', function($scope) {
    $scope.title = 'My Todos';
  });

ng-scope

Marks an explicit scope boundary. Useful for isolating a section of the page without a controller.

  <p>Hello {{ user.name }}</p>
</div>

ng-init

Initializes scope variables inline in the template. Best suited for simple one-off values or prototyping.

  Hello {{ name }}, count is {{ count }}
</div>

Tip: Prefer controllers or services over ng-init for any real application logic. ng-init is ideal for zero-JS demos and quick prototypes.

DOM reference directives

ng-el

Use ng-el when you only need the native DOM element. It is the simple DOM reference API and is usually the clearest choice for canvas, focus management, layout measurement, and pointer-driven UI.

<section ng-controller="BoardController as $ctrl">
  <canvas ng-el="$ctrl.boardEl"></canvas>
</section>
function BoardController() {
  this.boardEl = null;
}

ng-el="$ctrl.boardEl" stores the element on the controller. Use this form with controller-as syntax.

For simple scope shorthand, pass a bare name:

<canvas ng-el="boardEl"></canvas>

If the value is omitted, AngularTS uses the element id.

<canvas id="board" ng-el></canvas>

ng-ref

Use ng-ref when you need a component or directive controller reference, or when you need to assign the reference to a full expression such as $ctrl.child.

<search-box ng-ref="$ctrl.search"></search-box>
<button ng-click="$ctrl.search.clear()">Clear</button>

By default, ng-ref reads the component or element-directive controller when one exists, and falls back to the DOM element.

ng-ref-read is not a standalone directive. It only changes what ng-ref assigns.

<canvas ng-ref="$ctrl.boardEl" ng-ref-read="$element"></canvas>
<my-widget ng-ref="$ctrl.resizeHandle" ng-ref-read="resizeHandle"></my-widget>

Use ng-ref-read="$element" only when you specifically need the DOM element through an assignable expression. For the common DOM-only case, prefer ng-el.

function BoardController() {
  this.boardEl = null;
}

Event and communication directives

ng-channel

Subscribes to a named event channel (backed by $eventBus). When an event is published on the channel, the expression is evaluated.

  Latest: {{ lastNotification }}
</div>

ng-listener

Attaches a DOM event listener to the element using the native addEventListener API — without the overhead of Angular’s event directive wiring.

  Scroll-tracked content
</div>

ng-inject

Injects a named service directly into the scope, making it accessible in the template without a controller.

  <!-- $http is now available in this scope -->
</div>

Lazy loading and performance

ng-viewport

Uses the browser’s Intersection Observer API to defer content rendering until the element enters the viewport. Ideal for below-the-fold content.

  <!-- loadComments() is called once when this enters the viewport -->
  <div ng-repeat="comment in comments">{{ comment.text }}</div>
</div>

ng-viewport

  • Type: expression

Expression to evaluate when the element enters the viewport. Called once and not repeated.

ng-cloak

Prevents the browser from briefly displaying uncompiled template syntax ({{ }}) before AngularTS bootstraps. Add to the root element or any element with interpolation.


<div ng-cloak>
  <p>Hello {{ user.name }}</p>
</div>

Note: Always include the CSS rule [ng-cloak] { display: none; } in your stylesheet when using ng-cloak.

Web Workers and WebAssembly

ng-worker

Runs a JavaScript file in a Web Worker and binds the result back to scope. The worker communicates via postMessage.

     data-params="{ items: largeList }"
     data-on-result="sortedItems = $result">
  <div ng-repeat="item in sortedItems">{{ item.name }}</div>
</div>

ng-worker

  • Type: string
  • Required: yes

Path to the Web Worker JavaScript file.

data-params

  • Type: expression

Data to pass to the worker as the message payload.

data-on-result

  • Type: expression

Expression evaluated when the worker posts a result. $result contains the worker’s response.

interval

  • Type: number

Re-send the params to the worker on a millisecond interval.

throttle

  • Type: number

Throttle worker invocations to at most once per N milliseconds.

The worker file uses standard onmessage / postMessage:

  var sorted = e.data.items.slice().sort((a, b) => a.name.localeCompare(b.name));
  postMessage(sorted);
};

ng-wasm

Loads a WebAssembly module and makes its exports available on scope.

  <p>Result: {{ math.add(3, 4) }}</p>
</div>

ng-wasm

  • Type: string
  • Required: yes

Path to the .wasm file to load.

as

  • Type: string

Scope property name to assign the WASM exports object to. Defaults to wasm.

Transclusion

ng-transclude

Used inside custom directive templates to mark where transcluded content should be inserted.

  return {
    transclude: true,
    template: '<div class="panel"><ng-transclude></ng-transclude></div>'
  };
});
  <p>This content is transcluded into the panel.</p>
</my-panel>

Non-bindable sections

ng-non-bindable

Prevents AngularTS from compiling or interpolating a subtree. Use this for displaying raw Angular syntax as documentation or code examples.

  {{ this will not be interpolated }}
  ng-repeat="item in items"
</pre>

Aria directives

AngularTS automatically manages ARIA attributes for accessibility. These directives are applied alongside their functional counterparts:

DirectiveARIA attribute managed
ng-disabledaria-disabled
ng-checkedaria-checked
ng-readonlyaria-readonly
ng-requiredaria-required
ng-show / ng-hidearia-hidden
ng-modelaria-value*, role

The $aria service and $ariaProvider let you configure which ARIA attributes are automatically managed:

  $ariaProvider.config({
    ariaDisabled: true,
    ariaChecked: true,
    ariaReadonly: true,
    ariaRequired: true,
    ariaHidden: true,
    ariaValue: true,
    tabindex: true
  });
});

Event directives

AngularTS generates event directives for all common DOM events. They all follow the ng-<eventname>="expression" pattern:

DirectiveDOM event
ng-clickclick
ng-dblclickdblclick
ng-focusfocus
ng-blurblur
ng-keydownkeydown
ng-keyupkeyup
ng-keypresskeypress
ng-mouseentermouseenter
ng-mouseleavemouseleave
ng-mousemovemousemove
ng-mouseovermouseover
ng-mousedownmousedown
ng-mouseupmouseup
ng-submitsubmit (on forms)
ng-cutcut
ng-copycopy
ng-pastepaste
ng-loadload
ng-oncustom event name

The expression is evaluated in the current scope. $event refers to the native DOM event object:

<input ng-keydown="$event.key === 'Enter' && submit()">

Event policy can be declared once on the same element and applies to every ng-on-* or generated event directive on that element:

<div
  ng-on-pointerdown="$ctrl.startDrag($event)"
  data-event-prevent
  data-event-capture>
</div>

Supported policy attributes:

AttributeBehavior
data-event-preventCalls $event.preventDefault() first
data-event-stopCalls $event.stopPropagation() first
data-event-captureRegisters the listener with capture: true
data-event-onceRegisters the listener with once: true
data-event-passiveRegisters the listener with passive: true

data-event-passive cannot be combined with data-event-prevent, because passive listeners cannot cancel default browser behavior.

ng-pointer-capture can be combined with ng-on-pointer* handlers for game and board interactions that need reliable pointer streams:

<div
  ng-pointer-capture
  ng-on-pointerdown="$ctrl.startDrag($event)"
  ng-on-pointermove="$ctrl.drag($event)"
  ng-on-pointerup="$ctrl.drop($event)"
  data-event-prevent>
</div>

2 - Animation Directives: ng-animate-swap and CSS Hooks

Use ng-animate-swap, ng-animate-children, and CSS class hooks to animate structural directives like ng-if, ng-repeat, and ng-show with the $animate service.

AngularTS integrates animations into the same lifecycle that drives structural directives. Rather than managing setTimeout calls or CSS transitions manually, you add the animate attribute to an element and AngularTS’s $animate service handles entry, exit, and class-change transitions — applying and removing CSS hook classes at the right moment in the digest cycle.

How $animate integrates with directives

Structural directives — ng-if, ng-repeat, ng-show, ng-hide, ng-switch, ng-include, and ng-animate-swap — all check whether an element carries animation data before manipulating the DOM. When the animate attribute is present on an element (detected via hasAnimate(element)), they delegate DOM operations to $animate instead of performing them directly:

OperationWithout $animateWith $animate
Insert elementelement.after(clone)$animate.enter(clone, parent, after)
Remove elementelement.remove()$animate.leave(element)
Toggle classelement.classList.add(cls)$animate.addClass(element, cls)
Swap classesel.add(a); el.remove(b)$animate.setClass(element, add, remove)

This means you can add CSS transitions to any structural operation simply by adding the animate attribute and writing the corresponding CSS rules.


CSS animation hook classes

When $animate performs an operation it applies a sequence of CSS classes in two frames to give the browser time to set up the transition:

Element enter

ng-enter  →  ng-enter + ng-enter-active  →  (classes removed)

Element leave

ng-leave  →  ng-leave + ng-leave-active  →  element removed

Element move (ng-repeat reorder)

ng-move  →  ng-move + ng-move-active  →  (classes removed)

Class add/remove

ng-CLASS-add  →  ng-CLASS-add + ng-CLASS-add-active  →  CLASS applied
ng-CLASS-remove  →  ng-CLASS-remove + ng-CLASS-remove-active  →  CLASS removed
.card.ng-enter {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.card.ng-enter-active {
  opacity: 1;
  transform: translateY(0);
}

/* Fade out when the element is removed */
.card.ng-leave {
  opacity: 1;
  transition: opacity 0.2s ease;
}
.card.ng-leave-active {
  opacity: 0;
}

ng-animate-swap

ng-animate-swap swaps between transcluded content blocks using enter/leave animations as a watched expression changes. It runs at priority 550 — after ng-if (600) but before most attribute directives.

<div ng-animate-swap="currentAlert">
  <div ng-if="currentAlert === 'success'" class="banner banner-success">
    {{ message }}
  </div>
  <div ng-if="currentAlert === 'error'" class="banner banner-error">
    {{ message }}
  </div>
</div>

Each time the watched expression changes, the old element is removed via $animate.leave and the new transcluded clone is inserted via $animate.enter. The previous scope is destroyed before the new clone is linked.

A more common pattern is using ng-animate-swap to animate between stateful view components:

<div class="tab-content" ng-animate-swap="activeTab">
  <div class="tab-panel" ng-include="activeTab + '.html'"></div>
</div>
  opacity: 0;
  transform: translateX(20px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.tab-panel.ng-enter-active {
  opacity: 1;
  transform: translateX(0);
}
.tab-panel.ng-leave {
  opacity: 1;
  transition: opacity 0.2s ease;
}
.tab-panel.ng-leave-active {
  opacity: 0;
}

ng-animate-swap

  • Type: expression

The watched expression. Any time its value changes, the current transcluded block is removed with a leave animation and a new clone is entered. Use the for attribute alias interchangeably.


ng-animate-children

ng-animate-children propagates an animation-children flag to the element’s cache so that the $animate queue knows whether child elements should also be animated during parent transitions.

<div ng-if="showPanel" ng-animate-children="true">
  <div class="panel-header">Header</div>
  <div class="panel-body" ng-repeat="item in items">{{ item }}</div>
</div>

When ng-animate-children is set to "on" or "true" (or is present as an empty attribute), child animations run in parallel with the parent. Without it, the animation queue suppresses child animations while the parent is entering or leaving.

<ul ng-repeat="group in groups" ng-animate-children>
  <li ng-repeat="item in group.items" class="list-item">{{ item }}</li>
</ul>

ng-animate-children

  • Type: string | none

Accepts "on", "true", or an empty attribute to enable child animations. Any other value (or an interpolated expression evaluating to a falsy string) disables child animations.


Animating ng-repeat lists

ng-repeat triggers ng-enter and ng-leave animations for items added to or removed from the collection. Add the animate attribute on the repeated element to opt in.

  <li class="task-item"
      ng-repeat="task in tasks track by task.id"
      animate>
    <span ng-bind="task.title"></span>
    <button ng-click="removeTask(task)">Remove</button>
  </li>
</ul>
  animation: slideIn 0.25s ease forwards;
}

.task-item.ng-leave {
  animation: slideOut 0.2s ease forwards;
}

.task-item.ng-move {
  transition: all 0.3s ease;
}

@keyframes slideIn {
  from { opacity: 0; transform: translateX(-16px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes slideOut {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(16px); }
}
  .controller('TaskCtrl', function($scope) {
    $scope.tasks = [
      { id: 1, title: 'Design wireframes' },
      { id: 2, title: 'Write tests' },
      { id: 3, title: 'Deploy to staging' }
    ];

    $scope.removeTask = function(task) {
      const idx = $scope.tasks.indexOf(task);
      if (idx !== -1) $scope.tasks.splice(idx, 1);
    };
  });

Animating ng-show / ng-hide

ng-show and ng-hide both apply a temporary ng-hide-animate class alongside ng-hide when the $animate service is active. This class serves as the transition anchor:

  {{ message }}
</div>
  transition: opacity 0.3s ease, max-height 0.3s ease;
  overflow: hidden;
}

/* State when hidden */
.notification.ng-hide {
  opacity: 0;
  max-height: 0;
}

/* Applied during the animation frame to enable the transition */
.notification.ng-hide-animate {
  display: block !important;
}

JavaScript animations via $animate

The $animateJs service allows you to register JavaScript-based animation hooks alongside CSS animations:

  .animation('.flip-card', function() {
    return {
      enter: function(element, done) {
        // Use Web Animations API or any library
        element.animate(
          [{ transform: 'rotateY(90deg)' }, { transform: 'rotateY(0deg)' }],
          { duration: 300, easing: 'ease-out' }
        ).onfinish = done;

        return function(cancelled) {
          if (cancelled) element.style.transform = '';
        };
      },
      leave: function(element, done) {
        element.animate(
          [{ transform: 'rotateY(0deg)' }, { transform: 'rotateY(-90deg)' }],
          { duration: 250, easing: 'ease-in' }
        ).onfinish = done;
      }
    };
  });
  {{ card.content }}
</div>

Tip: JavaScript animations and CSS animations can coexist on the same element. The $animate service runs CSS animations and JavaScript animation hooks in parallel, calling done only after both complete.


Animation and the HTTP directives

The HTTP directives (ng-get, ng-post, etc.) also support the animate attribute. When present, swapped content uses $animate.enter and $animate.leave instead of direct DOM manipulation:

     trigger="load"
     swap="innerHTML"
     animate
     class="post-content">
</div>
  opacity: 0;
  transition: opacity 0.4s ease;
}
.post-content.ng-enter-active {
  opacity: 1;
}

3 - Data Binding Directives in AngularTS Explained

Explore ng-bind, ng-bind-html, ng-model, ng-class, ng-style, and ng-init with real examples covering one-way and two-way data binding.

Data binding directives synchronise values between the scope and the DOM without requiring you to write event listeners or DOM queries by hand. AngularTS provides one-way and two-way binding variants to cover every use case, from safe text output to full form synchronisation.

ng-bind

ng-bind is the attribute equivalent of double-curly interpolation ({{ }}). It watches the given scope expression and writes the result as the element’s textContent. Because it sets textContent, it is immune to XSS — no HTML is ever parsed.

<p>Hello, {{ user.name }}!</p>

<!-- Using ng-bind — element is empty until AngularTS compiles -->
<p>Hello, <span ng-bind="user.name"></span>!</p>

The implementation watches the expression on every digest. The lazy attribute defers the first watch until a digest is explicitly triggered:

<span ng-bind="heavyExpression" lazy></span>

Tip: Prefer ng-bind over {{ }} in the <body> of server-rendered pages to avoid the flash of uncompiled template (FOUC). The element starts empty and is filled when Angular bootstraps.

Parameters

ng-bind

  • Type: expression
  • Required: yes

Any AngularTS expression. The result is converted to a string via stringify() and written to element.textContent. null and undefined render as an empty string.


ng-bind-template

ng-bind-template lets you embed multiple interpolated expressions inside a single attribute value — useful when the surrounding text cannot hold a child element.

<title ng-bind-template="{{pageTitle}} — {{siteName}}"></title>

<!-- Equivalent using child elements (not possible inside <title>) -->

The directive uses attr.$observe to watch the already-interpolated string; Angular re-evaluates the interpolation on each digest and writes the result to textContent.

ng-bind-template

  • Type: string with {{ }} expressions
  • Required: yes

A string literal containing one or more {{ expression }} placeholders. The entire interpolated string is written to textContent.


ng-bind-html

ng-bind-html inserts the expression result directly into element.innerHTML. Use this directive when you need to render server-provided or pre-sanitised markup. It calls $parse during compilation to check for interpolation errors, then watches the expression and sets innerHTML on every change.

<div ng-bind-html="article.body"></div>
  .controller('ArticleCtrl', function($scope) {
    $scope.article = {
      body: '<p>Hello <strong>world</strong></p>'
    };
  });

Warning: ng-bind-html sets innerHTML directly. Always sanitise content on the server before placing it in scope. Avoid interpolating raw user input with this directive.

ng-bind-html

  • Type: expression
  • Required: yes

An expression evaluating to a string of HTML. The string is written to element.innerHTML. null and undefined clear the element.


ng-model

ng-model establishes a two-way binding between a form input and a scope property. Changes to the input update the scope; changes to the scope property update the input. It is the backbone of AngularTS form handling.

Text input

<input type="text" ng-model="user.name" />
<p>Hello, {{ user.name }}!</p>

Checkbox

<input type="checkbox" ng-model="settings.notifications" />
<p ng-bind="settings.notifications ? 'On' : 'Off'"></p>

Select

<select ng-model="selected.country">
  <option value="us">United States</option>
  <option value="gb">United Kingdom</option>
</select>
<p>You selected: {{ selected.country }}</p>

Textarea

<textarea ng-model="message.body" rows="4"></textarea>
<p>Characters: {{ message.body.length }}</p>

ng-model creates an NgModelController instance that manages:

  • $viewValue — the formatted value shown in the control (always a string for native inputs)
  • $modelValue — the parsed value stored in the scope
  • $parsers — pipeline from view to model (view → model transformation)
  • $formatters — pipeline from model to view (model → view transformation)
  • $validators — synchronous validator functions keyed by error name
  • $asyncValidators — async validator functions that return Promises
  • $error — object whose keys are failing validator names

State properties

PropertyTypeDescription
$pristinebooleantrue if the user has not interacted with this control
$dirtybooleantrue after the user has changed the value
$touchedbooleantrue after the control has lost focus
$untouchedbooleantrue before the control has ever been blurred
$validbooleantrue if all validators pass
$invalidbooleantrue if any validator fails

CSS classes

AngularTS automatically toggles CSS classes on the element:

.ng-valid { border-color: green; }

/* Applied when the model is invalid */
.ng-invalid { border-color: red; }

/* Applied when the user has not yet changed the value */
.ng-pristine { }

/* Applied once the user has changed the value */
.ng-dirty { }

/* Applied once the input has been blurred */
.ng-touched { }

ng-model

  • Type: expression
  • Required: yes

An assignable AngularTS expression. The expression must be assignable (pointing to a scope property), otherwise AngularTS throws an ngModel:nonassign error.


ng-class

ng-class conditionally adds and removes CSS classes based on a scope expression. It supports three input formats: an object, an array, or a string.

Object syntax

Keys are class names; values are expressions. A class is applied when its value is truthy.

<div ng-class="{ active: isActive, 'text-danger': hasError }">
  Status panel
</div>
$scope.isActive = true;
$scope.hasError = false;
// Result: class="active"

For larger views, prefer projecting domain state into a view-model class map and binding that map directly. This keeps templates readable and lets JavaScript type checking validate the object shape.

<button ng-class="tile.classes">
  Fire
</button>
/**
 * @param {{ state: string, sunk: boolean }} tile
 * @returns {ng.ClassMap}
 */
function tileClasses(tile) {
  return {
    placed: tile.state === 'unit',
    hit: tile.state === 'hit',
    miss: tile.state === 'miss',
    sunk: tile.sunk,
  };
}

$scope.tile = {
  state: 'unit',
  sunk: false,
  classes: tileClasses({ state: 'unit', sunk: false }),
};

Array syntax

Each element is evaluated as a class name or object. Useful when combining static and conditional classes.

<div ng-class="[baseClass, { highlighted: isSelected }]">
  Item
</div>
$scope.baseClass = 'card';
$scope.isSelected = true;
// Result: class="card highlighted"

String syntax

The expression evaluates to a space-separated class string.

<div ng-class="currentTheme">
  Themed content
</div>
$scope.currentTheme = 'theme-dark compact';
// Result: class="theme-dark compact"

The implementation uses reference counting internally (digestClassCounts) so that when multiple ng-class directives compete on the same element, classes are not prematurely removed while still in use.

ng-class-even / ng-class-odd

These variants apply classes only to even- or odd-indexed rows inside an ng-repeat.

  <li ng-repeat="item in items"
      ng-class-even="'row-even'"
      ng-class-odd="'row-odd'">
    {{ item.name }}
  </li>
</ul>

ng-class

  • Type: ng.ClassValue (string | ng.ClassMap | array)
  • Required: yes
  • String — a space-delimited list of class names.
  • Object — keys are class names, values are truthy/falsy conditions.
  • Array — each element is recursively processed using the above rules.

ng-style

ng-style watches a scope object and applies its key-value pairs as inline CSS properties using element.style.setProperty. When the expression changes, the old properties are first removed before the new ones are applied.

<div ng-style="boxStyles">
  Dynamic box
</div>
  'background-color': '#4f46e5',
  'color': '#ffffff',
  'padding': '16px',
  'border-radius': '8px'
};
<p ng-style="{ 'font-size': fontSize + 'px', 'font-weight': isBold ? 'bold' : 'normal' }">
  Styled text
</p>

Note: CSS property names must use hyphen-case (background-color, not backgroundColor) since the implementation calls element.style.setProperty rather than assigning to element.style[key].

ng-style

  • Type: object
  • Required: yes

An expression evaluating to an object where keys are CSS property names in hyphen-case and values are CSS value strings. Setting the expression to null or undefined removes all previously applied inline styles.


ng-init

ng-init evaluates an expression during the pre-link phase — before child directives are linked. It is most useful for initialising scope variables inline without requiring a separate controller.

<div ng-init="count = 0; title = 'Hello'">
  <button ng-click="count = count + 1">Clicked {{ count }} times</button>
</div>
<ul>
  <li ng-repeat="item in items" ng-init="pos = $index + 1">
    {{ pos }}. {{ item.name }}
  </li>
</ul>

The implementation checks whether a controller is present on the element (via getController) and, if so, evaluates the expression on the controller’s scope. Otherwise it falls back to the current scope.

Warning: Use ng-init sparingly. For any non-trivial initialisation, use a controller instead. Keeping logic in templates makes it harder to test and maintain.

ng-init

  • Type: expression
  • Required: yes

An AngularTS expression or semicolon-separated list of expressions evaluated once in the pre-link phase. The expression has access to the current scope.

4 - Form Directives and Validation in AngularTS

Complete guide to AngularTS form directives: ng-model, built-in validators, ng-messages, ng-model-options, and form state properties like $valid and $dirty.

AngularTS treats HTML forms as first-class citizens of the framework. Every <form> element (and ng-form directive) creates a FormController instance that tracks the aggregate validity and dirty state of all its inputs. Each input bound with ng-model gets its own NgModelController that manages the view-to-model pipeline, validation, and CSS class hooks.

The form directive

A plain <form> element is enhanced by AngularTS into a FormController. Give the form a name attribute and the controller is published on the current scope under that name, giving you programmatic access to validation state.

  <input type="email" name="email"
         ng-model="user.email"
         required />

  <button type="submit"
          ng-disabled="registrationForm.$invalid">
    Register
  </button>
</form>
  .controller('RegistrationCtrl', function($scope) {
    $scope.register = function() {
      if ($scope.registrationForm.$valid) {
        // submit logic
      }
    };
  });

FormController state properties

PropertyTypeDescription
$validbooleantrue when all child controls are valid
$invalidbooleantrue when any child control is invalid
$pristinebooleantrue before any control has been changed
$dirtybooleantrue after any control has been changed
$submittedbooleantrue after the form’s submit event fires
$errorobjectKeys are validator names; values are arrays of failing controls
$pendingobjectKeys are validator names with pending async validators

CSS classes on forms

AngularTS automatically toggles these classes on both the <form> element and each ng-model input:

ClassApplied when
ng-pristineControl has not been changed
ng-dirtyControl has been changed
ng-validAll validators pass
ng-invalidAny validator fails
ng-submittedForm has been submitted
ng-touchedInput has been blurred at least once
ng-untouchedInput has never been blurred
ng-pendingAn async validator is in progress
input.ng-invalid.ng-dirty {
  border: 2px solid #ef4444;
}

input.ng-valid.ng-dirty {
  border: 2px solid #22c55e;
}

/* Show error messages only after the form is submitted or field is touched */
.field-error {
  display: none;
}
input.ng-touched.ng-invalid ~ .field-error {
  display: block;
}

ng-form

ng-form creates a nested FormController that tracks its own controls independently. Because browsers do not allow nested <form> elements, ng-form is the standard way to group controls into sub-sections with their own validity state.

  <!-- Shipping address group -->
  <ng-form name="shippingForm">
    <input name="street" ng-model="shipping.street" required />
    <input name="city" ng-model="shipping.city" required />
    <p ng-if="shippingForm.$invalid">Please complete shipping address.</p>
  </ng-form>

  <!-- Billing address group -->
  <ng-form name="billingForm">
    <input name="street" ng-model="billing.street" required />
    <p ng-if="billingForm.$invalid">Please complete billing address.</p>
  </ng-form>

  <button ng-disabled="outerForm.$invalid">Submit</button>
</form>

The outer form’s $valid is false if any nested ng-form is invalid, allowing you to check a single top-level form while still presenting granular errors per section.


Input directives

All standard HTML input types are supported. For <input>, <textarea>, and <select> elements, AngularTS registers type-specific parsers and validators automatically when ng-model is present.

Text / Email / URL

<input type="text"
       name="username"
       ng-model="user.username"
       required
       minlength="3"
       maxlength="20"
       ng-pattern="/^[a-z0-9_]+$/" />

<input type="email"
       name="email"
       ng-model="user.email"
       required />

<input type="url"
       name="website"
       ng-model="user.website" />

Number

<input type="number"
       name="age"
       ng-model="user.age"
       min="18"
       max="120"
       required />

Parses the input into a JavaScript number. Sets $error.number if the value is not numeric, $error.min or $error.max if out of range.

Checkbox

<!-- Simple boolean checkbox -->
<input type="checkbox"
       name="agree"
       ng-model="user.agreedToTerms"
       required />

<!-- Custom true/false values -->
<input type="checkbox"
       ng-model="settings.mode"
       ng-true-value="'dark'"
       ng-false-value="'light'" />

Radio

<label>
  <input type="radio" ng-model="plan" value="free" /> Free
</label>
<label>
  <input type="radio" ng-model="plan" value="pro" /> Pro
</label>
<label>
  <input type="radio" ng-model="plan" value="enterprise" /> Enterprise
</label>
<p>Selected: {{ plan }}</p>

Select

<!-- Simple string options -->
<select name="country" ng-model="user.country" required>
  <option value="">-- Choose a country --</option>
  <option value="us">United States</option>
  <option value="gb">United Kingdom</option>
</select>

<!-- Object options with ng-options -->
<select ng-model="selectedRole"
        ng-options="role.id as role.label for role in roles">
  <option value="">-- Select a role --</option>
</select>

Built-in validators

required / ng-required

required makes the field mandatory. ng-required accepts an expression so you can make a field conditionally required.


<!-- Conditionally required -->
<input type="text"
       ng-model="user.company"
       ng-required="user.accountType === 'business'" />

The validator calls NgModelController.$isEmpty on the view value. For text inputs, empty means "". For checkboxes, empty means false.

minlength / ng-minlength

       ng-model="user.password"
       ng-minlength="8" />
<p ng-if="form.password.$error.minlength">
  Password must be at least 8 characters.
</p>

maxlength / ng-maxlength

       ng-model="tweet.text"
       ng-maxlength="280" />
<p>{{ 280 - tweet.text.length }} characters remaining</p>

pattern / ng-pattern

pattern accepts a regex literal (as an attribute value subject to interpolation). ng-pattern accepts a scope expression that evaluates to a RegExp object or a string.

<input type="text"
       ng-model="user.phone"
       pattern="^\d{10}$" />

<!-- Dynamic pattern from scope -->
<input type="text"
       ng-model="user.code"
       ng-pattern="validationRules.codePattern" />
  codePattern: /^[A-Z]{2}\d{4}$/
};

Note: Avoid the g (global) flag on patterns used with ng-pattern. RegExp objects with the g flag maintain internal state between calls to .test(), which causes alternating pass/fail results.


ng-messages

ng-messages simplifies displaying validation error messages. It watches a form control’s $error object and renders the first matching ng-message child (or all matching, with the multiple attribute).

  <div class="field">
    <label>Email</label>
    <input type="email"
           name="email"
           ng-model="user.email"
           required />

    <div ng-messages="signupForm.email.$error" role="alert">
      <p ng-message="required">Email address is required.</p>
      <p ng-message="email">Please enter a valid email address.</p>
    </div>
  </div>
</form>

By default, only the first matching error is shown. Add the multiple attribute to show all failing validators at once:

  <p ng-message="required">Password is required.</p>
  <p ng-message="minlength">At least 8 characters required.</p>
  <p ng-message="pattern">Must contain a number and special character.</p>
</div>

ng-message-default

ng-message-default acts as a fallback shown when none of the specific ng-message keys match but there is at least one truthy error:

  <p ng-message="pattern">Invalid format.</p>
  <p ng-message-default>This field has an error.</p>
</div>

Reusable message templates

Use ng-messages-include to load a shared message template file:

  <div ng-messages-include="'/partials/common-messages.html'"></div>
  <p ng-message="email">Not a valid email address.</p>
</div>

ng-model-options

ng-model-options controls when the model is updated and how updates are debounced. It can be placed on a form (to apply to all controls) or on individual inputs.

Debounce

Delay model updates by a given number of milliseconds after the user stops typing:

<input type="text"
       ng-model="search.query"
       ng-model-options="{ debounce: 300 }" />

This fires a search 300ms after the last keystroke rather than on every character.

Update on blur

Only update the model when the input loses focus:

<input type="text"
       ng-model="user.username"
       ng-model-options="{ updateOn: 'blur' }" />

Combined events

Update immediately on blur, but also debounced on default (which covers all other events):

<input type="text"
       ng-model="user.slug"
       ng-model-options="{
         updateOn: 'blur default',
         debounce: { 'default': 500, 'blur': 0 }
       }" />

Getter/setter

When getterSetter: true, the ng-model expression is expected to be a function that both gets and sets the value:

<input type="text"
       ng-model="user.getName"
       ng-model-options="{ getterSetter: true }" />
let _name = 'Alice';
$scope.user = {
  getName: function(newValue) {
    if (arguments.length) {
      _name = newValue.trim();
    }
    return _name;
  }
};

Available options

updateOn

  • Type: string

Space-separated DOM event names that trigger model updates. Use "default" as a placeholder for the control’s standard update event (e.g., "input" for text inputs).

debounce

  • Type: number | object

Milliseconds to wait before committing the view value. Can be an object mapping event names to delays: { 'blur': 0, 'default': 500 }.

allowInvalid

  • Type: boolean

When true, the model is updated even when the value is invalid (normally the model is set to undefined when validation fails).

getterSetter

  • Type: boolean

When true, the ng-model expression is treated as a function that acts as both getter and setter.

timezone

  • Type: string

Timezone offset for date/time input parsing, e.g. "UTC" or "+0530".


Complete form example

The following example demonstrates a registration form with multiple input types, validators, debouncing, and ng-messages error display.


  <!-- Username -->
  <div class="field">
    <label for="username">Username</label>
    <input id="username"
           type="text"
           name="username"
           ng-model="form.username"
           ng-model-options="{ debounce: 300 }"
           required
           ng-minlength="3"
           ng-maxlength="20"
           ng-pattern="/^[a-z0-9_]+$/" />

    <div ng-messages="regForm.username.$error"
         ng-if="regForm.username.$touched || regForm.$submitted">
      <p ng-message="required">Username is required.</p>
      <p ng-message="minlength">At least 3 characters.</p>
      <p ng-message="maxlength">No more than 20 characters.</p>
      <p ng-message="pattern">Only lowercase letters, numbers, and underscores.</p>
    </div>
  </div>

  <!-- Email -->
  <div class="field">
    <label for="email">Email</label>
    <input id="email"
           type="email"
           name="email"
           ng-model="form.email"
           required />

    <div ng-messages="regForm.email.$error"
         ng-if="regForm.email.$touched || regForm.$submitted">
      <p ng-message="required">Email is required.</p>
      <p ng-message="email">Enter a valid email address.</p>
    </div>
  </div>

  <!-- Password -->
  <div class="field">
    <label for="password">Password</label>
    <input id="password"
           type="password"
           name="password"
           ng-model="form.password"
           required
           ng-minlength="8" />

    <div ng-messages="regForm.password.$error"
         ng-if="regForm.password.$dirty">
      <p ng-message="required">Password is required.</p>
      <p ng-message="minlength">At least 8 characters required.</p>
    </div>
  </div>

  <!-- Terms acceptance -->
  <div class="field">
    <label>
      <input type="checkbox"
             name="terms"
             ng-model="form.agreedToTerms"
             required />
      I agree to the terms and conditions
    </label>
    <p ng-if="regForm.terms.$error.required && regForm.$submitted">
      You must accept the terms.
    </p>
  </div>

  <button type="submit"
          ng-disabled="regForm.$invalid && regForm.$submitted">
    Create account
  </button>
</form>
  .controller('RegistrationCtrl', function($scope) {
    $scope.form = {};

    $scope.submitRegistration = function() {
      $scope.regForm.$setSubmitted();

      if ($scope.regForm.$valid) {
        // Call your API
        console.log('Registering:', $scope.form);
      }
    };
  });

5 - HTTP Directives: ng-get, ng-post, ng-put, ng-sse

Make HTTP requests and stream SSE from HTML using ng-get, ng-post, ng-put, ng-delete, and ng-sse — no JavaScript required for common data-fetching patterns.

AngularTS ships a family of declarative HTTP directives inspired by HTMX. Rather than wiring up $http calls in a controller, you attach ng-get, ng-post, ng-put, ng-delete, or ng-sse directly to HTML elements. The directives handle the request lifecycle, DOM insertion, loading states, error handling, and scope merging — all configured through HTML attributes.

How they differ from $http

When you use $http directly you write controller code: define the request, subscribe to the promise, update scope properties, handle errors, and trigger a digest. The HTTP directives do all of this declaratively:

<button ng-click="loadUser()">Load user</button>
<p>{{ user.name }}</p>
  $http.get('/api/user/1').then(function(res) {
    $scope.user = res.data;
  });
};
<button ng-get="/api/user/1">Load user</button>
<p>{{ name }}</p>

When the server returns JSON, the directive merges it into scope automatically. When it returns HTML, the result is compiled and injected into the DOM using the configured swap strategy.


ng-get

ng-get fires a GET request when the configured event occurs (default: click for buttons, change for inputs, submit for forms).

<button ng-get="/api/weather?city=London">
  Get weather
</button>
{{ temperature }}°C — {{ description }}
<button ng-get="/partials/user-card.html"
        swap="outerHTML"
        target="#user-area">
  Show profile
</button>
<div id="user-area"></div>

Automatic trigger

Use trigger="load" to fire the request immediately when the element is linked (no user interaction required):

<div ng-get="/api/dashboard/stats" trigger="load">
  <span ng-bind="stats.users"></span> users
</div>

Polling with interval

<div ng-get="/api/live-count"
     trigger="load"
     interval="5000">
  {{ count }} online
</div>

ng-post

ng-post fires a POST request. On <form> elements, the default trigger is submit and the form data is collected via FormData. On buttons and other elements, the trigger defaults to click.

<form ng-post="/api/contact" name="contactForm">
  <input name="email" type="email" ng-model="contact.email" required />
  <textarea name="message" ng-model="contact.message" required></textarea>
  <button type="submit" ng-disabled="contactForm.$invalid">Send</button>
</form>
<button ng-post="/api/cart/add"
        success="cartMessage = $res.message"
        error="cartError = $res.error">
  Add to cart
</button>

When the form element has an enctype attribute, the request body is URL-encoded using Content-Type: application/x-www-form-urlencoded. Without enctype, form data is sent as a JSON object.


ng-put

ng-put fires a PUT request. Use it to update resources:

      name="profileForm"
      success="profileSaved = true"
      state-success="dashboard">
  <input name="name" ng-model="user.name" required />
  <input name="email" type="email" ng-model="user.email" required />
  <button type="submit">Save changes</button>
</form>

ng-delete

ng-delete fires a DELETE request. It shares all the same modifier attributes as ng-get:

<li>
  <span>{{ item.name }}</span>
  <button ng-delete="/api/items/{{ item.id }}"
          swap="delete"
          target="#item-{{ item.id }}">
    Delete
  </button>
</li>

ng-sse

ng-sse opens a persistent Server-Sent Events connection using the $sse service. Incoming messages are handled the same way as HTTP responses: JSON payloads are merged into scope, HTML strings are injected using the configured swap strategy.

<div ng-sse="/api/events/notifications"
     swap="beforeend">
</div>
<div ng-sse="/api/events/market"
     trigger="load">
  <p>{{ price | currency }}</p>
  <p>{{ change }}%</p>
</div>

The connection is torn down automatically when the scope is destroyed (e.g., when navigating away). Reconnect behaviour and error logging are provided by the $sse service.

app.get('/api/events/market', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify({ price: 42.50, change: 0.3 })}\n\n`);
  }, 2000);

  req.on('close', () => clearInterval(interval));
});

Shared modifier attributes

All HTTP directives accept the same set of modifier attributes to control their behaviour:

swap

  • Type: string
  • Default: innerHTML

Controls how the response HTML is inserted. Possible values:

  • innerHTML — replaces the element’s inner content (default)
  • outerHTML — replaces the entire element
  • textContent — inserts plain text without HTML parsing
  • beforebegin — inserts immediately before the element
  • afterbegin — inserts inside the element, before its first child
  • beforeend — inserts inside the element, after its last child
  • afterend — inserts immediately after the element
  • delete — removes the target element entirely
  • none — performs no DOM insertion (useful when only scope merging is needed)

target

  • Type: CSS selector

A querySelector selector for the element that receives the response. When omitted, the directive element itself is the target.

<button ng-get="/api/user" target="#profile-card">Load</button>
<div id="profile-card"></div>

trigger

  • Type: DOM event name

The DOM event that fires the request. Defaults to click for buttons and generic elements, change for inputs/selects/textareas, and submit for forms. Use "load" to trigger immediately on link.

latch

  • Type: interpolated expression

Re-fires the request every time the interpolated value changes.

<div ng-get="/api/search" latch="{{ query }}" swap="innerHTML">
  {{ results.length }} results
</div>

interval

  • Type: number

Fires the request immediately and then repeats every N milliseconds. The interval is cleared when the scope is destroyed.

<div ng-get="/api/status" interval="10000">{{ status }}</div>

delay

  • Type: number

Wait N milliseconds before sending the request after the trigger event fires.

throttle

  • Type: number

Ignore subsequent trigger events for N milliseconds after the request fires.

loading

  • Type: none

When present, sets data-loading="true" on the element while the request is in flight and data-loading="false" when it completes. Useful as a CSS hook.

<button ng-get="/api/data" loading>Load</button>
button[data-loading="true"] {
  opacity: 0.6;
  cursor: wait;
}

loading-class

  • Type: string

A CSS class toggled on the element while the request is in flight.

<button ng-get="/api/data" loading-class="is-loading">Load</button>

success

  • Type: expression

Expression evaluated when the response has a 2xx status code. The response data is available as $res.

<button ng-get="/api/items" success="items = $res">Fetch</button>

error

  • Type: expression

Expression evaluated when the response has a 4xx or 5xx status code. The response data is available as $res.

<button ng-post="/api/login"
        success="redirect('/dashboard')"
        error="loginError = $res.message">
  Log in
</button>

state-success

  • Type: string

Router state name to navigate to on success (calls $state.go).

state-error

  • Type: string

Router state name to navigate to on error (calls $state.go).

animate

  • Type: none

When present, enables $animate transitions for the swap operation. Requires the $animate CSS hooks to be defined.

enctype

  • Type: string

Sets the Content-Type request header and URL-encodes form data. Use "application/x-www-form-urlencoded" to replicate a native HTML form submission.


Practical examples

Loading a data table on mount

  <thead>
    <tr>
      <th>Name</th>
      <th>Status</th>
      <th>Joined</th>
    </tr>
  </thead>
  <tbody ng-get="/api/users"
         trigger="load"
         swap="innerHTML">
    <!-- Populated with server-rendered rows -->
  </tbody>
</table>

Infinite scroll

  <li ng-repeat="post in posts">{{ post.title }}</li>
</ul>

<button ng-get="/api/posts?page={{ nextPage }}"
        ng-viewport
        on-enter="loadMore()"
        swap="beforeend"
        target="#post-list">
  Load more
</button>

Real-time notifications via SSE

  <span ng-bind="notifications.unread"></span>
</div>

<!-- SSE merges JSON payloads into scope automatically -->
<div ng-sse="/api/events/notifications" trigger="load">
</div>

Form submission with redirect on success

      ng-post="/api/auth/login"
      state-success="app.dashboard"
      error="authError = $res.message">
  <input type="email" name="email" ng-model="credentials.email" required />
  <input type="password" name="password" ng-model="credentials.password" required />
  <p ng-if="authError" ng-bind="authError"></p>
  <button type="submit" ng-disabled="loginForm.$invalid">Log in</button>
</form>

6 - Directive Overview

Learn how AngularTS directives attach behavior to HTML, how built-in directive groups are organized, and how to create custom directives.

Directives are how AngularTS attaches behavior and structure to HTML. A directive is a marker on a DOM element or attribute that tells $compile to attach behavior, transform DOM, wire events, create scopes, or connect controllers.

Every built-in directive in AngularTS is applied as an HTML attribute using the ng- prefix. The compiler normalizes prefixes, so ng-bind, data-ng-bind, and x-ng-bind all match the same directive.

Exact custom directive contracts live in TypeDoc:

How Directives Are Matched

The restrict option controls where a directive can appear.

RestrictMatch formExample
AAttribute<span ng-bind="name"></span>
EElement<my-widget></my-widget>
AE / EAAttribute or element<div ng-form> or <ng-form>

Attribute directives are the common case in AngularTS. They keep the host element in place and augment it.

Built-In Directive Groups

AngularTS groups its built-in directives by the user-facing job they perform. Keep these groups in mind when choosing where to look in the docs.

GroupPurposeExamples
Data bindingSynchronize scope data with the viewng-bind, ng-model, ng-class, ng-style
StructuralAdd, remove, repeat, or switch DOMng-if, ng-repeat, ng-show, ng-hide, ng-switch
FormsTrack validity, dirty state, messages, and model optionsform, ng-model, ng-messages, validators
HTTPTrigger declarative network work from the DOMng-get, ng-post, ng-put, ng-delete, ng-sse
AnimationsCoordinate directive-driven animation hooksng-animate-swap, ng-animate-children
AdvancedBridge browser APIs and integration boundariesng-worker, ng-wasm, ng-viewport, ng-pointer-capture

Create A Custom Directive

Register directives on a module with .directive(name, factory). The factory returns a directive definition object.

angular.module("demo", []).directive("highlight", () => {
  return {
    restrict: "A",
    scope: {
      color: "@highlight",
    },
    link(scope, element) {
      const paint = (color) => {
        element.style.backgroundColor = color || "yellow";
      };

      paint(scope.color);

      scope.$watch("color", paint);
    },
  };
});
<p highlight="gold">Pinned note</p>

Use compile() when the directive must transform template DOM before linking. Use link() when it only needs to attach behavior to each compiled instance.

Execution Order

When multiple directives appear on the same element, AngularTS sorts them by priority from highest to lowest. Directives with equal priority run in registration order.

<li ng-repeat="item in items" ng-class="{ active: item.selected }">
  {{ item.name }}
</li>

Structural directives such as ng-repeat and ng-if use high priorities and terminal behavior because they replace or remove DOM before lower-priority directives are linked.

Scope Choice

Use the default inherited scope for simple behavior. Create a child scope when the directive needs local state. Use isolate scope bindings when the directive is designed as a reusable widget with explicit inputs and callbacks.

scope: {
  title: "@",
  value: "=",
  onSave: "&",
}

Prefer components for larger reusable UI pieces. Use directives for behaviors, DOM integrations, structural transforms, and lightweight element coordination.

Next Steps

7 - Structural Directives: ng-if, ng-repeat, ng-switch

Master AngularTS structural directives including ng-if, ng-show, ng-hide, ng-repeat with track by, ng-switch, ng-include, and ng-non-bindable.

Structural directives reshape the DOM by adding, removing, or repeating elements. Unlike attribute directives that modify an existing element, structural directives use transclusion to stamp out or remove entire sections of the template. They run at high priority (600–1000) and set terminal: true so that lower-priority directives do not compile until the structure is resolved.

ng-if

ng-if conditionally includes or removes a DOM element based on a scope expression. When the expression becomes falsy, the element and its entire subtree — including child scopes and event listeners — are destroyed. When it becomes truthy again, a fresh element and child scope are created.

<div ng-if="user.role === 'admin'">
  <h2>Admin Panel</h2>
  <p>Welcome, {{ user.name }}. You have full access.</p>
</div>
<div ng-if="isLoggedIn">
  <p>Welcome back, {{ user.name }}!</p>
  <button ng-click="logout()">Log out</button>
</div>
<div ng-if="!isLoggedIn">
  <p>Please <a href="/login">sign in</a> to continue.</p>
</div>

The implementation uses transclude: "element" at priority 600, which means the compiler replaces the element with a comment placeholder and hands the element template to the transclusion function. When the expression turns truthy, the transclusion function clones the template and inserts it after the comment. When it turns falsy, the child scope is destroyed and the clone is removed.

If the $animate service is available on the element, ng-if delegates to $animate.enter and $animate.leave to trigger CSS transition hooks.

Note: ng-if vs ng-show: ng-if completely removes the element from the DOM (destroying the child scope), while ng-show keeps the element in the DOM and toggles an ng-hide CSS class. Use ng-if when the hidden content is expensive to keep around or when it should not run watchers while invisible.

Parameters

ng-if

  • Type: expression
  • Required: yes

An expression evaluated on each digest. When truthy, the element is inserted into the DOM in a new child scope. When falsy, the element and its child scope are destroyed.


ng-show / ng-hide

ng-show and ng-hide toggle element visibility without removing the element from the DOM. They work by adding or removing the ng-hide CSS class, which sets display: none.

ng-show

Removes ng-hide when the expression is truthy, showing the element.

<div ng-show="form.submitted">
  <p>Thank you! Your form was submitted successfully.</p>
</div>

ng-hide

Adds ng-hide when the expression is truthy, hiding the element.

<div ng-hide="isLoading">
  <p>Content is ready.</p>
</div>
<div ng-show="isLoading">
  <p>Loading...</p>
</div>

Both directives integrate with $animate. When the animate service detects the element has animation hooks, they call $animate.addClass or $animate.removeClass with the temporary class ng-hide-animate for the duration of the animation.

.my-panel.ng-hide {
  opacity: 0;
}
.my-panel.ng-hide-animate {
  transition: opacity 0.3s ease;
}

ng-show

  • Type: expression
  • Required: yes

When truthy, ng-hide class is removed from the element, making it visible.

ng-hide

  • Type: expression
  • Required: yes

When truthy, ng-hide class is added to the element, hiding it.


ng-repeat

ng-repeat iterates over a collection (array or object) and stamps a copy of the template for each item. Each copy gets its own child scope populated with the iteration variables below.

Special scope properties

VariableTypeDescription
$indexnumberZero-based index of the current iteration
$firstbooleantrue for the first item
$lastbooleantrue for the last item
$middlebooleantrue when neither first nor last
$evenbooleantrue when $index is even
$oddbooleantrue when $index is odd

Repeating over an array

  <li ng-repeat="product in products">
    <strong>{{ product.name }}</strong> — ${{ product.price }}
    <span ng-if="$first">(newest)</span>
    <span ng-if="$last">(oldest)</span>
  </li>
</ul>

Repeating over an object

Use the (key, value) tuple syntax to iterate over an object’s own enumerable properties. Keys starting with $ are excluded automatically.

  <dt ng-repeat="(key, value) in user">{{ key }}</dt>
  <dd>{{ value }}</dd>
</dl>

Track by

By default, ng-repeat tracks items by identity (hashKey). When the collection changes, AngularTS tries to reuse existing DOM nodes by matching on the track key. The track by clause lets you provide a stable identity for items, which dramatically improves performance when working with large lists or when items are replaced by new objects from the server.

<div ng-repeat="user in users">{{ user.name }}</div>

<!-- With track by id — DOM nodes for unchanged users are reused -->
<div ng-repeat="user in users track by user.id">{{ user.name }}</div>

<!-- Track by $index — useful for arrays of primitives -->
<div ng-repeat="tag in tags track by $index">{{ tag }}</div>

Alias with as

The as clause aliases the filtered collection to a new scope variable. This is useful when you apply filters to the collection and need the filtered count.

<p>{{ filtered.length }} of {{ products.length }} items</p>
<ul>
  <li ng-repeat="product in products | filter:searchText as filtered">
    {{ product.name }}
  </li>
</ul>

Animation support

Add the animate attribute to enable $animate.enter / $animate.leave on items:

  {{ item.name }}
</li>
  opacity: 0;
  transition: opacity 0.3s;
}
.list-item.ng-enter-active {
  opacity: 1;
}
.list-item.ng-leave {
  opacity: 1;
  transition: opacity 0.3s;
}
.list-item.ng-leave-active {
  opacity: 0;
}

Warning: Duplicate track keys in the same repeater throw an ngRepeat:dupes error. Use track by $index or a unique property like track by item.id to guarantee uniqueness.

ng-repeat

  • Type: string
  • Required: yes

Expression in one of the forms:

  • item in collection
  • (key, value) in object
  • item in collection track by expression
  • item in collection | filter as alias

ng-switch

ng-switch renders exactly one child block matching the current value of a watched expression. It is the structural equivalent of a JavaScript switch statement.

  <div ng-switch-when="active">
    <p>Account is active. Welcome, {{ user.name }}!</p>
  </div>
  <div ng-switch-when="suspended">
    <p>Your account has been suspended. Please contact support.</p>
  </div>
  <div ng-switch-when="pending">
    <p>Your account is awaiting verification.</p>
  </div>
  <div ng-switch-default>
    <p>Unknown account status.</p>
  </div>
</div>

ng-switch-when registers its transclusion function under the key !value (to avoid collisions with $-prefixed keys) using an internal NgSwitchController. When the watched expression changes, the currently shown block has its scope destroyed (with an optional $animate.leave) and the matching block is transcluded into place.

Multiple values on one case

The ng-switch-when-separator attribute allows a single ng-switch-when to match multiple values:

  <div ng-switch-when="Saturday Sunday" ng-switch-when-separator=" ">
    Weekend!
  </div>
  <div ng-switch-default>Weekday</div>
</div>

ng-switch

  • Type: expression
  • Required: yes

The expression whose value is compared against ng-switch-when values.

ng-switch-when

  • Type: string
  • Required: yes

The literal string value to match against the ng-switch expression.

ng-switch-default

  • Type: none

Rendered when no ng-switch-when matches the current value.


ng-include

ng-include fetches an external HTML template, compiles it in the current scope (or a new child scope), and inserts it into the DOM. It watches the URL expression and replaces the content whenever the URL changes.

<div ng-include="'/partials/user-profile.html'"></div>

<!-- Include a dynamic template based on user role -->
<div ng-include="'/partials/nav-' + user.role + '.html'"></div>

The directive emits three events on the scope:

  • $includeContentRequested — when the template request begins
  • $includeContentLoaded — when the template is compiled and inserted
  • $includeContentError — when the template request fails
<div ng-include="templateUrl"
     ng-init="templateUrl = '/partials/home.html'"
     onload="onTemplateLoaded()">
</div>
  console.log('Template inserted successfully');
};

ng-include

  • Type: expression
  • Required: yes

Expression evaluating to the URL of the template to load. The URL is fetched via $templateRequest (which uses the template cache if available).

onload

  • Type: expression

Expression evaluated after the template is compiled and inserted.

autoscroll

  • Type: expression

When truthy, calls $anchorScroll after the template is inserted.


ng-non-bindable

ng-non-bindable tells the AngularTS compiler to skip the element and all its descendants entirely. No interpolation, directives, or watches are set up inside the marked section.

<pre ng-non-bindable>
  &lt;p&gt;Use {{ expression }} to interpolate values.&lt;/p&gt;
  &lt;div ng-bind="myValue"&gt;&lt;/div&gt;
</pre>
<div ng-non-bindable>
  {% raw %}{{ jinja_template_variable }}{% endraw %}
</div>

Note: ng-non-bindable is essential when displaying code documentation, Jinja/Twig snippets, or any content that uses {{ }} but should not be treated as AngularTS expressions.