Design Pattern — Iterator in Angular
Behavioral patterns are design patterns that deal with the ways objects communicate with each other.
Behavioral patterns consist of:
1. Chain of Responsibility
2. Command
3. Interpreter
4. Iterator
5. Mediator
6. Memento
7. Observer
8. State
9. Strategy
10. Template Method
In this article, I will explain how the Iterator pattern works. You can see and run an example of the Iterator pattern in a demo project, which can be found at this link here.
As the name suggests, the Iterator design pattern traverses through a given set of data in a specific manner without requiring knowledge of the internal representation of the data. These sets can be arrays, collections, lists, stacks, trees, etc. The core idea of the Iterator design pattern is to define the behavior of traversing a collection in a separate object called an Iterator.
In my opinion, the Iterator design pattern is primarily used for traversing binary trees and graphs. Using loops in such cases would be unclear, but an Iterator can handle these structures through custom methods, often called next(). It separates the logic of iteration from the data structure itself.
In the case of complex structures, iterators make the code more readable and easier to maintain. Therefore, to illustrate the Iterator design pattern, I will propose an implementation of a binary tree. The Tree class will contain a root field of type TreeNode, to which elements of the tree will be added. The add function in the Tree class adds a new node to the tree. Below is the implementation of the TreeNode and Tree class:
class TreeNode {
value: string;
left: TreeNode | null;
right: TreeNode | null;
constructor(value: string, left: TreeNode | null = null, right: TreeNode | null = null) {
this.value = value;
this.left = left;
this.right = right;
}
}
interface Iterator<T> {
current(): T;
next(): T;
valid(): boolean;
}
interface Aggregator {
getIterator(): Iterator<TreeNode>
}
class Tree implements Aggregator {
private root: TreeNode | null;
private size: number;
constructor() {
this.root = null;
this.size = 0;
}
public getRoot(): TreeNode | null {
return this.root;
}
public getSize(): number {
return this.size;
}
public add(value: string): void {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
this.size++;
}
private insertNode(node: TreeNode, newNode: TreeNode): void {
if (newNode.value < node.value) {
if (!node.left) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (!node.right) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
public getIterator(): Iterator<TreeNode> {
if (!this.root) {
throw new Error("Tree is empty");
}
return new TreeIterator(this.root);
}
}In the example, the in-order traversal technique is applied. Of course, the search method may be different. The in-order traversal is implemented using the TreeIterator class, which provides a way to traverse the tree in the correct order, printing values in ascending order.
The TreeIterator class has a private field stack: TreeNode[], which acts as a stack. It is used to store the nodes of the tree during iteration. The traverseLeft method prepares the stack in such a way that nodes are visited in the correct sequence. This ensures that in-order traversal is implemented iteratively, not recursively.
The TreeIterator class also contains methods such as:
current(): Returns the current node.next(): moves the iteration to the next node.valid(): checks if there are still elements to traverse.
Below is the implementation of the TreeIterator class:
class TreeIterator implements Iterator<TreeNode> {
private stack: TreeNode[];
constructor(root: TreeNode) {
this.stack = [];
this.traverseLeft(root);
}
private traverseLeft(node: TreeNode | null): void {
while (node) {
this.stack.push(node);
node = node.left;
}
}
public current(): TreeNode {
if (!this.valid()) {
throw new Error("No current element");
}
return this.stack[this.stack.length - 1];
}
public next(): TreeNode {
if (!this.valid()) {
throw new Error("No next element");
}
const node = this.stack.pop()!;
if (node.right) {
this.traverseLeft(node.right);
}
return node;
}
public valid(): boolean {
return this.stack.length > 0;
}
}The design pattern is started in the IteratorComponent class, its code is below:
@Component({
selector: 'app-iterator',
standalone: true,
imports: [FormsModule],
template: `
<div>
<h1>Tree Iterator</h1>
<input [(ngModel)]="newNodeValue" placeholder="Enter node value" />
<button (click)="addNode()">Add Node</button>
<h2>In-Order Traversal:</h2>
<ul>
@for (node of traversalResult; track node) {
<li>{{ node }}</li>
}
</ul>
</div>
`,
styleUrl: './iterator.component.scss'
})
export class IteratorComponent {
tree = new Tree();
newNodeValue: string = '';
traversalResult: string[] = [];
addNode(): void {
if (this.newNodeValue.trim()) {
this.tree.add(this.newNodeValue);
this.newNodeValue = '';
this.updateTraversal();
}
}
updateTraversal(): void {
const iterator = this.tree.getIterator();
const result: string[] = [];
while (iterator.valid()) {
result.push(iterator.current().value);
iterator.next();
}
this.traversalResult = result;
}
}The structure of the Iterator pattern consists of several elements:
Get Bulicka Ola’s stories in your inbox
Join Medium for free to get updates from this writer.
Iterator interface declares the operations required for traversing a collection: fetching the next element, retrieving the current position, restarting iteration, etc. In our case it is the interface Iterator<T>
Concrete Iterators implement algorithms for traversing a collection. In our case it is the interface TreeIterator .
Collection interface declares one or multiple methods for getting iterators compatible with the collection. Note that the return type of the methods must be declared as the Iteratorinterface so that the concrete collections can return various kinds of iterators. In our case it is the Aggregator interface
Concrete Collections return new instances of a particular concrete iterator class each time the client requests one. In our case it is the Treeclass
Client works with both collections and iterators via their interfaces. In our case it is the IteratorComponentclass
Summary
The main use of the Iterator pattern is to provide a uniform and convenient way to iterate through the elements of a collection (e.g., list, tree, set), regardless of the internal structure of that collection.
Using a for loop is much faster than Iteration. In Angular, forloops can also leverage the track method, which functions similarly to an index in a database. The Iterator design pattern is primarily focused on separating the logic of iteration from the collection.
Materials:
- Java. Wzorce projektowe. Translation: Piotr Badarycz. Original title: Java design patterns. A Tutorial. Author: James William Cooper
- https://refactoring.guru/design-patterns/iterator
My other articles related to Design Patterns in Angular:
Structural patterns:
Design Pattern — Adapter in Angular
Design Pattern — Composite in Angular
Design Pattern — Decorator in Angular
Design Pattern — Facade in Angular
Design Pattern — Proxy in Angular
Creational patterns:
Design Pattern — Builder in Angular
Design Pattern — Factory (Simple Factory) in Angular
Design Pattern — Prototype in Angular
Behavioral patterns:
Design Pattern — Chain of Responsibility in Angular
Design Pattern — Memento in Angular
Design Pattern — Observer in Angular
I’m not good at English. I write articles to practice English.
I wanted to mention that there may be some mistakes in the articles related to the English language. I apologize for that. If I will notice any, I will correct them.











