23
loading...
This website collects cookies to deliver better user experience
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Observer Pattern</title>
</head>
<body>
<ul></ul>
<form>
<input required type="text" />
<button type="submit">Add</button>
</form>
</body>
<script>
// We'll add all our code here
</script>
</html>
script
element to hold our JavaScript code.<script>
let todos = []; // Subject
</script>
<script>
let todos = []; // Subject
let observers = [];
</script>
const form = document.querySelector("form");
form.addEventListener('submit', (event) => {
event.preventDefault();
const input = form.elements[0];
const item = {
id: Date.now(),
description: input.value,
};
addTodo(item);
input.value = ''; // Clear text input
});
function addTodo(item) {
todos.push(item);
}
todos
array to our HTML unordered list element.ul
element is interested in our todos
array. It wants to observe our array list so that it can display it on the screen. So it wants to be an Observer. Let's implement a function that will display our list.function displayTodos() {
const ul = document.querySelector('ul');
todos.forEach((todo) => {
const li = document.createElement('li');
li.innerText = todo.description;
ul.appendChild(li);
});
}
observers
. To do that we create a helper function to register
new observers.function registerObserver(observer) {
// The observers array is basically an array of functions
observers.push(observer);
}
registerObserver(displayTodos);
todos
array hasn't notified the observers.notifyObservers
function that will loop through our observers
array and call each observer
function to know an update has happened.function notifyObservers() {
observers.forEach((observer) => observer());
}
notifyObservers
function whenever we change the Subject.function addTodo(item) {
todos.push(item);
notifyObservers(); // Add this line
}
// Inside the displayTodos function
function displayTodos() {
const ul = document.querySelector('ul');
ul.innerHTML = ''; // Add this line
button
to every li
element.function displayTodos() {
const ul = document.querySelector('ul');
ul.innerHTML = '';
todos.forEach((todo) => {
const li = document.createElement('li');
li.innerText = todo.description;
// Add these lines
const button = document.createElement('button');
button.innerText = 'Remove';
li.appendChild(button);
ul.appendChild(li);
});
}
removeTodo
function that will handle removing to-dos by their ID.function removeTodo(id) {
todos = todos.filter((todo) => todo.id !== id);
notifyObservers();
}
click
event listener to the remove button, that will call the removeTodo
function.// Inside the displayTodos function
const button = document.createElement('button');
button.innerText = 'Remove';
// Attach an event listener here
button.addEventListener('click', () => {
removeTodo(todo.id);
});
li.appendChild(button)
function persistData() {
localStorage.setItem("saved-todos", JSON.stringify(todos));
}
registerObserver(persistData);
function loadTodos(todoList) {
todos = todoList;
notifyObservers();
}
window.addEventListener("load", () => {
const savedTodos = localStorage.getItem("saved-todos");
if (savedTodos) {
loadTodos(JSON.parse(savedTodos));
}
});
todos
array list. We are mixing UI logic and State logic, which is an attribute of a messy code.register
, add
, remove
, and load
functions as methods to an object. This is called Abstraction.todos
array is no longer visible to the UI logic code. So we create the getTodos
method for accessing the todos
. This is called Encapsulation. The art of hiding internal state and exposing it via a method.function createSubject() {
let todos = [];
let observers = [];
function registerObserver(observer) {
observers.push(observer);
}
function notifyObservers() {
observers.forEach((observer) => observer());
}
function addTodo(item) {
todos.push(item);
notifyObservers();
}
function removeTodo(id) {
todos = todos.filter((todo) => todo.id !== id);
notifyObservers();
}
function loadTodos(todoList) {
todos = todoList;
notifyObservers();
}
function getState() {
return todos;
}
return {
registerObserver,
addTodo,
removeTodo,
loadTodos,
getState,
}
}
createSubject
to create a todos subject.const subject = createSubject();
function displayTodos() {
const ul = document.querySelector("ul");
ul.innerHTML = "";
todos.forEach((todo) => {
const li = document.createElement("li");
li.innerText = todo.description;
const button = document.createElement("button");
button.innerText = "Remove";
button.addEventListener("click", () => {
subject.removeTodo(todo.id);
});
li.appendChild(button);
ul.appendChild(li);
});
}
subject.registerObserver(displayTodos)
subject.registerObserver(() => {
localStorage.setItem("saved-todos", JSON.stringify(todos));
});
window.addEventListener("load", () => {
const savedTodos = localStorage.getItem("saved-todos");
if (savedTodos) {
subject.loadTodos(JSON.parse(savedTodos));
}
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault();
const input = form.elements[0];
const item = {
id: Date.now(),
description: input.value,
};
subject.addTodo(item);
input.value = "";
});
});
createSubject
function adheres to the Observer Pattern design. We subscribe to the todos by registering as observers. What about if we no longer want to be notified?registerObserver
method.function registerObserver(observer) {
observers.push(observer);
return function () {
observers = observers.filter((currentObserver) => !== observer);
}
}
const unregisterDisplayTodos = subject.registerObserver(displayTodos)
// later when we want to unregister
unregisterDisplayTodos(); // displayTodos will no longer be notified