AngularJS angular.forEach(): Iterating Arrays and Objects Without Surprises

I still run into AngularJS codebases in 2026 for one simple reason: they work, they ship revenue, and rewriting them is risky. The flip side is that everyday tasks—like iterating through API payloads, normalizing objects for a view, or building a lookup map—can turn into a small mess when teams mix styles (plain for loops, native Array.prototype.forEach, Lodash, jQuery-era patterns, and AngularJS helpers).

When I’m reviewing legacy AngularJS, angular.forEach() is one of those functions that looks boring but quietly shapes correctness and maintainability. It’s not magic. It’s a small abstraction over “iterate over stuff” with a few AngularJS-specific choices: it supports arrays and objects, it’s safe for null/undefined, it can bind this via a context parameter, and it behaves consistently across older browser quirks that AngularJS historically had to care about.

If you’re maintaining an AngularJS app today, I want you to leave with three things: a precise mental model of what angular.forEach() does, patterns I trust in production, and the failure modes that waste the most debugging time.

What angular.forEach() Actually Does

angular.forEach() iterates over each item in an array-like value or each own property in an object, calling your iterator for every entry.

Signature:

angular.forEach(object, iterator, [context])

The iterator is called like this:

iterator(value, key, obj)
  • value: the current element (for arrays) or property value (for objects)
  • key: the numeric index (arrays) or property name (objects)
  • obj: the original object you passed in

The optional context becomes the this value inside the iterator:

iterator.call(context, value, key, obj)

A few details that matter in real code:

1) It’s safe when the input is null/undefined.

If object is falsy (null, undefined, 0, ‘‘), AngularJS won’t iterate it. Practically, the common case is null/undefined from “API payload not loaded yet.” This makes angular.forEach() a little more forgiving than calling someArray.forEach(...) when someArray might be missing.

2) It iterates objects by own properties.

For plain objects, AngularJS checks hasOwnProperty so you don’t accidentally iterate properties from the prototype chain.

3) It tries to delegate to a native .forEach when present.

If the object has its own .forEach method (and it’s not angular.forEach itself), AngularJS will often call that. For normal arrays, that means you may end up using the native implementation.

4) It returns the original input.

This surprises people because they expect a collected array like map. angular.forEach() is for side effects (pushing into an array, building an index, mutating an object). The return value is the original object, which can be handy in fluent helper code but is not a “result.”

5) There is no built-in “break.”

Unlike a for loop, you can’t stop early by returning from the iterator. If you need early exit, you should reach for a different pattern (I’ll show what I do later).

Arrays vs Objects: The Semantics You Can Rely On

When you’re maintaining code under pressure, “iteration semantics” is a fancy phrase for: will this code behave the same tomorrow as it does today?

Here’s what I treat as stable expectations:

Iterating arrays

For arrays, your key is an index (0, 1, 2, …), and your value is the element.

var skills = [‘Binary search‘, ‘Linear search‘, ‘Interpolation search‘];

angular.forEach(skills, function (skillName, index) {

// index is 0..length-1

// skillName is the string

});

If your array contains undefined slots or you’re dealing with sparse arrays, behavior can differ across iteration styles. In practice, most AngularJS apps build arrays densely from JSON, so you rarely want sparse arrays anyway.

Iterating objects

For objects, key is a property name and value is the property value:

var featureFlags = {

enableNewCheckout: true,

enableFraudRules: false

};

angular.forEach(featureFlags, function (enabled, flagName) {

// flagName: ‘enableNewCheckout‘

// enabled: true

});

One subtle point: property iteration order.

If you are relying on the order of properties when iterating a plain object, you’re setting yourself up for a nasty “works on my machine” moment—especially in older AngularJS code that might still run in embedded webviews or odd enterprise browsers. Even though modern JavaScript engines have more defined property ordering rules than they used to, I still recommend: if order matters, convert to an array of entries and sort explicitly.

The context parameter: the cleanest way to avoid outer-scope mutation

AngularJS code often ends up with “capture a variable, mutate it inside the loop.” That works, but it’s easy to get wrong when refactoring.

Using context makes intent obvious:

var accumulator = { total: 0 };

var numbers = [3, 7, 10];

angular.forEach(numbers, function (n) {

this.total += n;

}, accumulator);

// accumulator.total === 20

I like this in legacy code because it reduces the chance of accidentally closing over a variable that later changes meaning.

“Array-like” inputs

AngularJS has long dealt with DOM collections, arguments objects, and other array-like values. angular.forEach() is comfortable with those.

If you’re iterating something that might be a NodeList (or a jQuery/jqLite collection), angular.forEach() can be a safer default than assuming .forEach exists.

Runnable Example: Normalizing API Data for the View

The most common AngularJS pattern I see is: fetch data, normalize it, and store it on $scope for rendering.

Here’s a complete, runnable example that takes a list of “search methods” from a local constant (standing in for an API response), extracts names, and renders them.

<!DOCTYPE html>

<html>

<head>

<meta charset=‘utf-8‘ />

<title>angular.forEach demo</title>

<script src=‘https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js‘></script>

<style>

body { font-family: system-ui, -apple-system, Segoe UI, Arial, sans-serif; padding: 24px; }

.card { max-width: 560px; border: 1px solid #ddd; border-radius: 10px; padding: 16px; }

h1 { margin: 0 0 8px; font-size: 20px; }

ul { margin: 8px 0 0; }

</style>

</head>

<body ng-app=‘app‘ ng-cloak>

<div class=‘card‘ ng-controller=‘SearchMethodsCtrl as vm‘>

<h1>Search methods</h1>

<div>Count: {{ vm.methodNames.length }}</div>

<ul>

<li ng-repeat=‘name in vm.methodNames track by $index‘>{{ name }}</li>

</ul>

</div>

<script>

(function () {

‘use strict‘;

angular.module(‘app‘, [])

.controller(‘SearchMethodsCtrl‘, function () {

var vm = this;

vm.methodNames = [];

// Pretend this came from an API.

var response = {

methods: [

{ name: ‘Binary Search‘, time: ‘O(log n)‘ },

{ name: ‘Linear Search‘, time: ‘O(n)‘ },

{ name: ‘Interpolation Search‘, time: ‘Average O(log log n)‘ }

]

};

// Normalize for the view.

angular.forEach(response.methods, function (method) {

vm.methodNames.push(method.name);

});

});

})();

</script>

</body>

</html>

A couple of choices here are intentional:

  • I use controller as (SearchMethodsCtrl as vm) because it reduces $scope coupling and makes refactors less painful.
  • I keep iteration logic tiny. If it starts growing, I extract it into a pure function and test it.

A more realistic normalization: building a lookup map

When you render a list and also need fast lookups by ID (for selection states, detail panes, etc.), I often build both an array and a map.

vm.methods = response.methods;

vm.methodsByName = Object.create(null);

angular.forEach(vm.methods, function (method) {

// Object.create(null) gives you a clean dictionary without prototype keys.

vm.methodsByName[method.name] = method;

});

If you’ve ever chased a bug where toString shows up as a key, you’ll appreciate the Object.create(null) trick.

Common Mistakes That Waste Debugging Time

These are the issues I see most often when teams copy/paste patterns without understanding the mechanics.

Mistake 1: Expecting angular.forEach() to return a computed result

This is wrong:

var names = angular.forEach(methods, function (m) { return m.name; });

// names is NOT an array of strings

What I do instead:

  • If I want a derived array and I know I have a real array: use map.
  • If I want to support object iteration too: build the result explicitly.
var names = [];

angular.forEach(methods, function (m) {

names.push(m.name);

});

Or, for arrays:

var names = (methods || []).map(function (m) { return m.name; });

Mistake 2: Trying to break early

This doesn’t work:

angular.forEach(methods, function (m) {

if (m.name === ‘Binary Search‘) {

return; // only returns from the iterator, not the outer loop

}

});

What I recommend:

  • If you need early exit on arrays, use a for loop. It’s direct and clear.
  • If you need “find one item,” use a helper that expresses that intent.

Example with a for loop (boring, correct, fast):

function findMethodByName(methods, targetName) {

if (!methods) return null;

for (var i = 0; i < methods.length; i++) {

if (methods[i].name === targetName) return methods[i];

}

return null;

}

I’ve seen people throw an exception to break out of angular.forEach(). Yes, it works. No, I don’t recommend it in application code. It’s hostile to debuggers and tends to hide real errors.

Mistake 3: Mutating what you’re iterating in a subtle way

Pushing into a different array while iterating is fine. Modifying the same array (splicing, sorting) inside the iterator is where things go sideways.

If you need to filter items out, do it in two steps:

var active = [];

angular.forEach(methods, function (m) {

if (m.isActive) active.push(m);

});

vm.methods = active;

Mistake 4: Updating $scope from async code without triggering a digest

AngularJS only updates the view during a digest cycle. If your iteration happens after a promise resolves or after a non-Angular callback, you may need $applyAsync.

This is a typical safe pattern:

$http.get(‘/api/methods‘).then(function (res) {

$scope.$applyAsync(function () {

$scope.methodNames = [];

angular.forEach(res.data.methods, function (m) {

$scope.methodNames.push(m.name);

});

});

});

If you’re already in an Angular-managed promise ($http, $q), you usually don’t need $applyAsync. But if you’re inside a raw fetch, a WebSocket callback, or a third-party SDK callback, you often do.

Mistake 5: Confusing context with passing extra arguments

The context parameter only binds this. It does not pass extra parameters to the iterator.

If you need extra data, either close over it (carefully) or bind a context object intentionally:

var ctx = { prefix: ‘Method: ‘, output: [] };

angular.forEach(methods, function (m) {

this.output.push(this.prefix + m.name);

}, ctx);

vm.methodLabels = ctx.output;

Performance and Digest-Cycle Realities

When teams ask me “is angular.forEach() slow?”, I reframe it: in AngularJS apps, the loop is rarely the main cost. The main cost is usually what you do per item (watchers, DOM work, filters in templates), not the iteration helper itself.

That said, iteration still matters when you hit tens of thousands of items or you rerun normalization on every digest.

What’s typically fast enough

  • Iterating a few hundred items to build a view model: usually fine.
  • Iterating a few thousand items once after an API call: usually fine.
  • Rebuilding large arrays every digest: not fine.

If you’re iterating 10,000+ elements and doing work per element, you can easily add noticeable UI delay. In my experience, once you cross that threshold, you should start measuring. In older AngularJS apps, I often see “small” loops balloon because they run more often than anyone expected.

The biggest foot-guns are in templates

If you do this:

<li ng-repeat=‘m in methods‘>{{ expensiveFormat(m) }}</li>

You’ll pay for expensiveFormat repeatedly during digests. Your angular.forEach() in the controller is not the enemy.

Instead, precompute what you need once:

vm.methodRows = [];

angular.forEach(response.methods, function (m) {

vm.methodRows.push({

name: m.name,

time: m.time,

label: m.name + ‘ (‘ + m.time + ‘)‘

});

});

Practical ways to keep AngularJS lists snappy

  • Use track by in ng-repeat.

– If you have stable IDs: track by m.id.

– If not: track by $index is better than nothing but can cause odd UI reuse on reorders.

  • Prefer one-time bindings {{ ::value }} for data that never changes after load.
  • Avoid chaining filters in templates for large lists.
  • If you must re-normalize frequently, cache results or normalize only when inputs change (for example, on API response, not on every digest).

angular.forEach() vs for loops

If I’m squeezing the last bit of performance out of a hot path, I use a for loop because it’s the most direct tool and has the least abstraction. But I only reach for that when the iteration is truly hot.

If the code is not hot, I pick readability. Debug time costs more than a few milliseconds.

When I Use angular.forEach() vs Native Methods (and When I Don’t)

Here’s the rule I follow in 2026 when I’m writing or refactoring AngularJS:

  • If I know I have an array and I’m in a modern runtime (which is basically always now): I prefer native map, filter, reduce, and forEach.
  • If the input might be an object, array-like, or null/undefined: angular.forEach() is often the cleanest.
  • If I need early exit: plain for loop (arrays) or for...in with hasOwnProperty (objects), or better, convert to entries and use a loop you can break.

A quick table to make decisions fast:

Task

Traditional AngularJS choice

Modern choice I recommend in 2026 —

— Iterate an array for side effects

angular.forEach(arr, fn)

arr.forEach(fn) (after guarding arr) Build a new array

angular.forEach + push

arr.map(fn) Build a filtered array

angular.forEach + if + push

arr.filter(predicate) Build a lookup map

angular.forEach + assignment

reduce or forEach (either is fine) Iterate object key/value pairs

angular.forEach(obj, fn)

Object.keys(obj).forEach(...) or Object.entries(obj) Needs to handle null safely

angular.forEach(maybeNull, fn)

(maybeNull []).forEach(fn) or guard clauses Need early exit

awkward with angular.forEach

for loop / some / find

A concrete example: building a list of names.

AngularJS-friendly, tolerant of null:

var names = [];

angular.forEach(maybeMethods, function (m) {

if (m && m.name) names.push(m.name);

});

Native and clean when you know you have an array:

var names = (methods || [])

.filter(function (m) { return m && m.name; })

.map(function (m) { return m.name; });

My “don’t use angular.forEach()” cases

  • You need early exit (finding one item, checking a condition, short-circuiting).
  • You’re already in a pure function where you want to return a value and avoid side effects.
  • You’re migrating code toward shared utilities that might run outside AngularJS (Node scripts, shared packages, tests). In that case, relying on angular as a global can be friction.

Patterns I Trust for Real-World AngularJS Code

If you want angular.forEach() to make code clearer instead of noisier, these patterns help.

Pattern 1: Extract normalization into a pure function

Controllers should be thin. Normalization should be testable.

function buildMethodViewModel(methods) {

var vm = {

names: [],

byName: Object.create(null)

};

angular.forEach(methods, function (m) {

if (!m || !m.name) return;

vm.names.push(m.name);

vm.byName[m.name] = m;

});

return vm;

}

Now your controller is just wiring:

var model = buildMethodViewModel(response.methods);

vm.methodNames = model.names;

vm.methodsByName = model.byName;

Pattern 2: Use context for accumulation when it clarifies intent

I don’t force context everywhere, but it’s nice when you’re building multiple outputs.

var ctx = { names: [], times: [] };

angular.forEach(methods, function (m) {

if (!m) return;

this.names.push(m.name);

this.times.push(m.time);

}, ctx);

vm.methodNames = ctx.names;

vm.methodTimes = ctx.times;

This avoids a pile of outer variables and makes it obvious what is being built.

Pattern 3: Guard for data shape, not just existence

When payloads change, AngularJS apps often fail silently until the UI renders nothing.

I like explicit checks inside the iterator:

angular.forEach(response.methods, function (m, idx) {

if (!m || typeof m.name !== ‘string‘) {

// If you have logging, this is where I‘d record a warning.

return;

}

vm.methodNames.push(m.name);

});

If you’re running strict linting, this is also where you can keep TypeErrors away.

Testing and Refactoring in 2026: Keep the Loop, Upgrade the Surroundings

AngularJS itself is legacy, but your engineering practices don’t have to be.

When I modernize an AngularJS codebase without rewriting it, I focus on:

  • extracting pure functions
  • adding tests around data shaping
  • enforcing formatting/linting
  • reducing implicit digest churn

A small unit test around a forEach-based normalizer

Even if your app still uses Karma/Jasmine, you can test pure functions without browser-heavy setup. Here’s a Jasmine-style example that’s easy to adapt.

// buildMethodViewModel.spec.js

describe(‘buildMethodViewModel‘, function () {

it(‘collects names and builds a lookup map‘, function () {

var input = [

{ name: ‘Binary Search‘, time: ‘O(log n)‘ },

{ name: ‘Linear Search‘, time: ‘O(n)‘ }

];

var model = buildMethodViewModel(input);

expect(model.names).toEqual([‘Binary Search‘, ‘Linear Search‘]);

expect(model.byName[‘Binary Search‘].time).toBe(‘O(log n)‘);

});

it(‘ignores invalid entries safely‘, function () {

var input = [null, { name: ‘‘ }, { name: ‘Interpolation Search‘ }];

var model = buildMethodViewModel(input);

expect(model.names).toEqual([‘Interpolation Search‘]);

});

});

The key idea: keep iteration dumb and predictable. Your tests should validate shape handling and edge cases, not AngularJS internals.

AI-assisted workflows that actually help here

I’m careful with AI code generation in legacy frameworks because the wrong suggestion can introduce subtle digest bugs. Where I do find AI helpful in 2026:

  • generating test cases from a payload schema
  • spotting “hidden” mutation inside iterators during refactors
  • proposing safer guard clauses when data is partially optional

I still review these changes manually, especially anything that touches $scope and async code.

Practical Next Steps You Can Apply Tomorrow

If you maintain AngularJS, angular.forEach() is one of those small tools that either keeps code tidy or becomes a dumping ground for side effects. I treat it as a readable iteration helper, not as a functional programming primitive.

Here’s what I recommend you do next:

1) Pick one hot path where you normalize API data (the one that feeds a large list). Move the normalization into a pure function that takes raw input and returns a view model. Keep angular.forEach() inside that function if you need object/array-like support.

2) Add two or three unit tests for edge cases you’ve actually seen: missing fields, null entries, and unexpected types. These tests pay for themselves the first time an API response changes.

3) Scan your templates for expensive work inside ng-repeat. If you see chained filters or function calls, precompute those values in the controller once. Your users will feel that difference more than any micro-choice between iteration helpers.

4) Standardize your iteration style. In my experience, teams waste time when half the code uses angular.forEach(), a quarter uses native methods, and the rest uses random loops. Choose a default (native array methods for arrays; angular.forEach() when inputs are uncertain) and enforce it in code review.

AngularJS may be old, but predictable code is timeless. When your iteration is consistent, your bugs become boring—and boring bugs are the ones you fix fast.

Scroll to Top