Skip to content

Knockout.js Control Flow — Conditional Rendering and List Iteration

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Knockout.js Control Flow. We cover key concepts, practical examples, and best practices to help you master this topic.

Knockout.js control flow bindings manage DOM creation and destruction based on your ViewModel state, using foreach for lists, if/ifnot for conditions, and with for context switching.

What You'll Learn

  • Using foreach to render lists with automatic updates
  • Conditional rendering with if, ifnot, and visible
  • Context switching with the with binding
  • Understanding the let binding for local variables
  • Performance characteristics of each approach

Why It Matters

Manual DOM manipulation for lists and conditionals is tedious and error-prone. Control flow bindings let you declare what should be rendered and let Knockout handle creating, updating, and removing DOM elements when data changes.

Real-World Use

An email client where the inbox list uses foreach, a loading spinner uses if, the selected email detail uses with, and empty-state messages use ifnot. Each binding handles a specific rendering concern declaratively.

Control Flow Decision Tree

flowchart TD
    A[Need to render?] --> B{Type}
    B -->|List| C[foreach binding]
    B -->|Conditional| D{When?}
    B -->|Single Object| E[with binding]
    D -->|Show/Hide| F[visible binding]
    D -->|Create/Remove DOM| G[if / ifnot binding]
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Foreach: Rendering Lists

The foreach binding duplicates the element's content for each item in an array:

<ul data-bind="foreach: products">
  <li>
    <strong data-bind="text: name"></strong>
    <span data-bind="text: price"></span>
    <button data-bind="click: $parent.removeProduct">Remove</button>
  </li>
</ul>
function Product(name, price) {
  this.name = ko.observable(name);
  this.price = ko.observable(price);
}

function ViewModel() {
  var self = this;
  self.products = ko.observableArray([
    new Product('Laptop', 999),
    new Product('Mouse', 25),
    new Product('Keyboard', 75)
  ]);

  self.removeProduct = function(product) {
    self.products.remove(product);
  };
}

Expected output: Three list items render. Clicking Remove calls removeProduct with the correct product, removing that item from the array and the DOM.

Foreach with $index and Context

Inside foreach, special context variables are available:

<ol data-bind="foreach: items">
  <li>
    <span data-bind="text: $index()"></span>. <!-- Zero-based index -->
    <span data-bind="text: $data"></span>      <!-- Current item value -->
    <span data-bind="text: $parent.title"></span> <!-- Parent context -->
  </li>
</ol>

For nested foreach loops, context variables stack:

<div data-bind="foreach: categories">
  <h3 data-bind="text: name"></h3>
  <ul data-bind="foreach: items">
    <li>
      <!-- $parents[1] = root, $parents[0] = parent (category) -->
      <span data-bind="text: $parents[1].title"></span> -
      <span data-bind="text: $data"></span>
    </li>
  </ul>
</div>

If Binding: Create or Destroy DOM

The if binding removes child elements from the DOM when the condition is falsy and recreates them when truthy:

<!-- If: Completely removes/creates the element -->
<div data-bind="if: isLoggedIn">
  <p>Welcome back, <span data-bind="text: userName"></span>!</p>
  <button data-bind="click: logout">Logout</button>
</div>

<div data-bind="ifnot: isLoggedIn">
  <p>Please <a href="#/login">login</a> to continue.</p>
</div>

<!-- Visible: Only hides/shows (element stays in DOM) -->
<div data-bind="visible: showWarning" class="warning">
  Your session will expire soon.
</div>

Expected output: When isLoggedIn is false, the welcome message and logout button do not exist in the DOM at all (not just hidden). When it becomes true, Knockout renders them.

If vs Visible: When to Use Which

Scenario Use Reason
Tabs/accordions visible Keep content in DOM for instant switching
Login/logout UI if Sensitive content should not exist in DOM at all
Loading spinner if No need to keep hidden spinner element
Form validation errors visible Show/hide messages without losing input state
Large hidden sections if Save memory by not rendering hidden content

With Binding: Context Switching

The with binding changes the binding context to a specific object:

<div data-bind="with: selectedProduct">
  <h2 data-bind="text: name"></h2>
  <p data-bind="text: description"></p>
  <p>Price: <span data-bind="text: price"></span></p>
  <button data-bind="click: $parent.addToCart">Add to Cart</button>
</div>
function ViewModel() {
  var self = this;
  self.products = ko.observableArray([...]);
  self.selectedProduct = ko.observable(null);

  self.selectProduct = function(product) {
    self.selectedProduct(product);
  };

  self.addToCart = function() {
    // 'this' is selectedProduct, not the ViewModel
    var product = self.selectedProduct();
    // Add to cart logic...
  };
}

Expected output: When selectedProduct is null, the with block renders nothing. When selectProduct is called, the block appears with the product's properties bound directly.

Containerless Control Flow

Sometimes you need control flow without a wrapping element. Use comment-based syntax:

<ul>
  <li>Static item</li>
  <!-- ko foreach: dynamicItems -->
    <li data-bind="text: $data"></li>
  <!-- /ko -->
</ul>

<!-- ko if: showExtra -->
  <p>Extra content that appears conditionally.</p>
<!-- /ko -->

<!-- ko with: getCurrentUser() -->
  <span data-bind="text: fullName"></span>
<!-- /ko -->

Expected output: The <!-- ko --> and <!-- /ko --> comments act as virtual elements. The bindings work exactly the same way but without adding wrapper divs to the DOM.

Let Binding (Local Variables)

The let binding creates named local variables within a scope:

<!-- ko let: { total: cartTotal(), discount: calculateDiscount() } -->
  <p>Subtotal: $<span data-bind="text: total.toFixed(2)"></span></p>
  <p>Discount: -$<span data-bind="text: discount.toFixed(2)"></span></p>
  <p>Total: $<span data-bind="text: (total - discount).toFixed(2)"></span></p>
<!-- /ko -->

Common Mistakes

  1. Using visible when if is appropriate - Keeping logged-out content in the DOM with visible can expose secure content via DevTools. Use if for sensitive sections.

  2. Creating too many nested foreach loops - Deeply nested foreach blocks with large arrays hurt performance. Flatten data or use containerless syntax to reduce nesting.

  3. Forgetting context changes with with - Inside with, the binding context changes. Access parent properties via $parent or $root.

  4. Not using containerless syntax - Adding wrapper divs just to use foreach or if clutters the DOM. Use <!-- ko --> comments instead.

  5. Mutating arrays during foreach rendering - Removing items while iterating can cause unexpected behavior. Defer mutations or use techniques that work with observableArray.

Practice Questions

  1. What is the difference between if and visible bindings?
  2. How do you access the outer ViewModel from inside a foreach loop?
  3. What are containerless control flow bindings and when would you use them?
  4. What happens to the DOM when the with binding's value changes from null to an object?
  5. How do you define local variables in a Knockout template?

Challenge: Build a tabbed interface with three tabs (Details, Specs, Reviews). Use the if binding to show only the active tab's content and the foreach binding to render review items in the Reviews tab.

FAQ

Does foreach re-render all items when the array changes?

No. Knockout tracks added and removed items and only updates the affected DOM nodes. Existing items are not re-rendered unless their individual observable properties change.

Can I use if and foreach together on the same element?

No. Use a containerless if wrapping a foreach, or nest them: <!-- ko if: condition --> <!-- ko foreach: items --> ... <!-- /ko --> <!-- /ko -->

Does the `with` binding work with null/undefined values?

Yes. When the value is null or undefined, the inner content is not rendered. This is useful for detail views that only show when an item is selected.

What is the performance of `if` vs `visible`?

visible is faster for rapid show/hide toggling because the DOM stays intact. if is better for memory when elements stay hidden for long periods or contain sensitive content.

Can I animate the addition/removal of foreach items?

Yes. Use the afterAdd and beforeRemove callbacks on the foreach binding to trigger jQuery animations or CSS transitions when items enter or leave the DOM.

Mini Project

Build a multi-step checkout form with four steps (Cart, Shipping, Payment, Confirmation). Use if bindings to show only the active step, foreach to render cart items, and with to bind the current step's data context.

What's Next

Bindings connect ViewModel to View. Now learn how to handle user interactions with event and form bindings for clicks, submissions, and input changes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro