Single Page Apps
a framework agnostic approach

Why Single Page Apps
without frameworks?

BATMAN.JS

How to structure
a Frontend Application?
Libraries/Frameworks are taught
never best practices
What makes a
Single Page App?






Once upon a time...
Multi Page Web Application
aka the »traditional website«
Server

https://www.awesome-site.io

request page
Server

https://www.awesome-site.io
request page
respond with Markup / CSS / JS / ...

Server

https://www.awesome-site.io/navigate/to/other/url
request another url

Server

https://www.awesome-site.io/navigate/to/other/url
request another url
respond with Markup / CSS / JS / ...

Single Page Application
aka »Web-App«
Server

https://www.awesome-site.io

request page
Server

https://www.awesome-site.io
respond with Markup / CSS / JS / ...
request page

Server

https://www.awesome-site.io/#/navigate/to/other/url
request data


Server

https://www.awesome-site.io/#/navigate/to/other/url
respond with JSON
request data

The role of the server
-
Authentication
-
Loading the initial page (resources)
-
Providing data
The main parts
of a »Single Page App«

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<!-- entry point -->
<div id="app"/>
<script src="/path/to/bundle.js"></script>
</body>
</html>
Routing
Application Logic (Structure)
Rendering
Data Layer
Asynchronicity
Tooling

Routing
entry point
entry point
entry point
entry point

https://www.awesome-site.io/#/navigate/to/other/url

#
window.addEventListener('hashchange', () => {
// route changed, handle it!
});
#
HTML5 History API (without #)
window.onpopstate = function(event) {
// route changed, handle it!
};
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
Application Logic
(Structure)

MVC
HTML
function() {}
function() {}
function() {}
JSON
MVVM
One-Way DataBinding
Model => View
Two-Way DataBinding
Model <=> View

JSON

HTML
function() {}
JSON
function() {}
MVW (Whatever)
explicit one-way data-flow
reason about your code
Tell don't ask

const result = askForData();
this.state = result.X
FireAndForget();

FLUX

- intention to do something
- simple description (string)
- optional: payload

- receives actions
- dispatches actions to stores

- single source of truth
- updates state when action appears
- fires event when state changes

- triggers actions
- acts on change event from store

REDUX
»Redux is a predictable state container for JavaScript apps.«
ACTION
STORE
Reducers
ACTION
STORE
Reducers
State is read-only
The only way to mutate state is to emit an action, an object describing what happened
ACTION
STORE
Reducers
ACTION
STORE
Reducers
Changes are made with pure functions
To specify how the state tree is transformed by actions, you write pure reducers
f(initialState, action) => newState
UCER FL
RED
UX
RED
UX
const todos = (state = [], action) => {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
{
id: state.length + 1,
text: action.text,
completed: false
}
];
default:
return state;
}
};
const todoApp = combineReducers({todos});
const store = createStore(todoApp);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({
type: 'ADD_TODO',
text: 'Mett kaufen'
});
store.dispatch({
type: 'ADD_TODO',
text: 'Mate trinken'
});
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
dispatch({}); // initial state
return { getState, dispatch, subscribe};
};
const combineReducers = (reducers) => {
return (state = {}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
}, {});
};
};
a single state tree
makes things simple...
- Testability
- Debugging
- Storage
REACTIVE

»Reactive programming is programming with asynchronous data streams.«
With asynchronous ..... ?
»Think of it as an immutable array.«
[ 14, 9, 5, 2, 10, 13, 4 ]
[ 14, 9, 5, 2, 10, 13, 4 ]
.filter(x => x % 2 === 0)
[ 14, 2, 10, 4 ]
[ 1, 2, 3 ]
.map(x => x * 2)
[ 2, 4, 6]

a stream of click events
aka »Observable«


.filter(event => event.x < 250)
»Rx is the underscore.js for events.«
RX Example
(detect the number of button clicks)
// Make the raw clicks stream
var button = document.querySelector('.this');
var clickStream = Rx.Observable.fromEvent(button, 'click');
// HERE
// The 4 lines of code that make the multi-click logic
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x >= 2; });
// Same as above, but detects single clicks
var singleClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x === 1; });
// Listen to both streams and render the text label accordingly
singleClickStream.subscribe(function (event) {
document.querySelector('h2').textContent = 'click';
});
multiClickStream.subscribe(function (numclicks) {
document.querySelector('h2').textContent = ''+numclicks+'x click';
});
Rx.Observable.merge(singleClickStream, multiClickStream)
.throttle(1000)
.subscribe(function (suggestion) {
document.querySelector('h2').textContent = '';
});
Rendering

<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<!-- entry point -->
<div id="app"/>
<script src="/path/to/bundle.js"></script>
</body>
</html>
f(data) => view
data changing over time...
»Change and its detection in JavaScript frameworks«
Projecting Data
f(json) => view
Manual Re-Rendering
»I have no idea what I should re-render. You figure it out.«
Data-Binding
»I know exactly what changed and what should be re-rendered because I control your models and views.«
Two-Way Data-Binding
aka Dirty Checking
»I have no idea what changed, so I'll just check everything that may need updating.«
Virtual DOM
»I have no idea what changed so I'll just re-render everything and see what's different now.«
Data Layer
Server
Data-Layer
HTTP
JavaScript
var request = new XMLHttpRequest();
request.open('GET', '/url/to/get/json', true);
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
console.log(data);
}
};
request.send();
jQuery
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
console.log(data);
});
Fetch API
fetch('https://api/url/to/get/json').then(function(response) {
return response.json();
}).then(function(jsonResponse) {
console.log(jsonResponse);
});
There are other data sources...
-
Local Storage
-
WebSockets
Asynchronicity
It's the nature of the web
Callbacks
asyncFunction(function (response) {
// do something
});
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
asyncFunction(function (response) {
// do something
});
});
});
});
});
});
});
});
});
});
Pyramid of doom
Promises
const promise = new Promise((resolve, reject) => {
if (/* success */) {
resolve("success");
}
else {
reject(Error("error"));
}
});
promise.then((result) => {
console.log(result); // "success"
}, (err) {
console.log(err); // Error: "error"
});
Observables (Rx.js)
var clickStream = Rx.Observable.fromEvent(button, 'click');
// HERE
// The 4 lines of code that make the multi-click logic
var multiClickStream = clickStream
.buffer(function() { return clickStream.throttle(250); })
.map(function(list) { return list.length; })
.filter(function(x) { return x >= 2; });
Let's code your own SPA
from scratch
Routing
Application Logic (Structure)
Rendering
Data Layer
Asynchronicity
Tooling


Data Layer
MVVM
Two-Way Data-Binding
#
Promises
Fetch API
Conclusion

BATMAN.JS
There are so much possibilities!
Focus on your product first!
Then use the simplest technology!

Resilience - Jeremy Keith @adactio (Beyond Tellerrand 2016)
»Simplicity is prerequisite for reliablity«
Edsger W. Dijkstra
Learn JavaScript






The end
Single Page Apps
By René Viering
Single Page Apps
without frameworks
- 2,010