A practical look JS MV* Frameworks: Angular 1.5, Ember, VueJS vs. Knockout (with or without components)
Created by EricOP
Press ESC to see the overall roadmap
Press SPACE to progress through each slide
Knockout
Now to Compare to:
ember
Angular
VueJS
With Components
(more on components later)
Nothing Fancy: a form calls an API, a checkbox with a conditional if, and a forEach loop on the results
Hold down Alt and click to zoom in and out on the slide
// JS Controller, Component, or ViewModel Bits
var myModel = new function () {
var self = this;
self.hideEventsWithMeals = ko.observable(false);
self.firstName = ko.observable("Eric");
self.lastName = ko.observable("Johnson");
self.myEvents = ko.observableArray([]);
self.addEvent = function (event) {
self.myEvents.push(
{
id: event.Id,
urlId: '/Admin/Details/' + event.Id,
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription,
pastDate: new Date(event.Date) < new Date()
})
console.log('event=', event);
}
self.alert = function () {
alert("Hello, I'm " + self.firstName() + " " + self.lastName() + ", the stuff!");
};
self.findMyEvents = function () {
var that = self;
//clear old events
that.myEvents([]);
$.ajax({
url: '@Url.Action("MyEventsAPI", "Home")',
dataType: 'json',
method: 'GET',
async: false,
data: { firstName: self.firstName(), lastName: self.lastName() },
success: function (data) {
var responseData = jQuery.parseJSON(data);
for (i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.addEvent(responseData[i]);
}
}
});
}
};
// Knockout bind needed at end
ko.applyBindings(myModel);
Hold down Alt and click to zoom in and out on the slide
{{input type="checkbox" name="hideEventsWithMeals" checked=hideEventsWithMeals}}
var myEventsApp = Ember.Application.create({
rootElement: '#myEvents',
});
myEventsApp.ApplicationController = Ember.Controller.extend({
firstName: "Eric",
lastName: "Johnson",
myEvents: [],
hideEventsWithMeals: false,
myEventsWithoutMeals: Ember.computed.filterBy('myEvents', 'hasMeal', false),
actions: {
alert: function () {
alert("Hello, I'm " + this.get('firstName') + " " + this.get('lastName') + ", the stuff!");
},
addEvent: function (event) {
this.myEvents.push(
{
id: event.Id,
//urlId: '/Admin/Details/' + event.Id, not needed
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription,
pastDate: new Date(event.Date) < new Date(),
hideThisMealEvent: event.hasMeal && this.get('hideEventsWithMeals')
})
console.log('event=', event);
},
findMyEvents: function () {
var that = this;
//clear old events
that.set('myEvents', []);
$.ajax({
url: '@Url.Action("MyEventsAPI", "Home")',
dataType: 'json',
method: 'GET',
async: false,
data: { firstName: that.get('firstName'), lastName: that.get('lastName') },
success: function (data) {
var responseData = jQuery.parseJSON(data);
for (i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.send('addEvent', responseData[i]);
}
}
});
}
}
})
Hold down Alt and click to zoom in and out on the slide
{{event.name}}
{{event.description}}
{{event.date}}
{{event.location}}
{{event.hasMeal}}
{{event.mealDescription}}
{{event.startTime}}
{{event.endTime}}
// JS Controller, Component, or ViewModel Bits
var myEventsApp = angular.module('myEvents', []);
myEventsApp.controller('MyEventsController', function ($scope, $http) {
var self = this;
$scope.firstName = "Eric";
$scope.lastName = "Johnson";
$scope.myEvents = [];
self.alert = function () {
alert("Hello, I'm " + $scope.firstName + " " + $scope.lastName + ", the stuff!");
};
self.addEvent = function (event) {
$scope.myEvents.push(
{
id: event.Id,
//urlId: '/Admin/Details/' + event.Id, not needed
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription,
pastDate: new Date(event.Date) < new Date()
})
console.log('event=', event);
}
self.findMyEvents = function () {
var that = self;
//clear old events
$scope.myEvents = [];
$http({
url: '@Url.Action("MyEventsAPI", "Home")',
responseType: 'json',
method: 'GET',
async: false,
params: { firstName: $scope.firstName, lastName: $scope.lastName }
}).then(function (response) {
var responseData = angular.fromJson(response.data);
for (var i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.addEvent(responseData[i]);
}
});
}
});
Hold down Alt and click to zoom in and out on the slide
{{event.name}}
{{event.description}}
{{event.date}}
{{event.location}}
{{event.hasMeal}}
{{event.mealDescription}}
{{event.startTime}}
{{event.endTime}}
// JS Controller, Component, or ViewModel Bits
new Vue({
el: '#myEvents',
data: {
hideEventsWithMeals: false,
firstName: "Eric",
lastName: "Johnson",
myEvents: []
},
methods: {
alert: function () {
alert("Hello, I'm " + this.firstName + " " + this.lastName + ", the stuff!");
},
addEvent: function (event) {
this.myEvents.push(
{
id: event.Id,
//urlId: '/Admin/Details/' + event.Id, not needed
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription,
pastDate: new Date(event.Date) < new Date()
})
console.log('event=', event);
},
findMyEvents: function () {
var that = this;
//clear old events
that.myEvents = [];
$.ajax({
url: '@Url.Action("MyEventsAPI", "Home")',
dataType: 'json',
method: 'GET',
async: false,
data: { firstName: this.firstName, lastName: this.lastName },
success: function (data) {
var responseData = jQuery.parseJSON(data);
for (i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.addEvent(responseData[i]);
}
}
});
}
}
});
"A powerful, clean way of organizing your UI code into self-contained, reusable chunks." -KO Docs
Self Contained View and (optionally) Controller or ViewModel
Optionally receives parameters from its parent, for one way data-binding
Can use Custom Elements as DOM place-holders
Central idea is making an app composed of several smaller elements, which are easier reason about
Hold down Alt and click to zoom in and out on the slide
// JS Controller, Component, or ViewModel Bits
var myModel = new function () {
var self = this;
self.firstName = ko.observable("Eric");
self.lastName = ko.observable("Johnson");
self.myEvents = ko.observableArray([]);
self.addEvent = function (event) {
self.myEvents.push(
{
id: event.Id,
urlId: '/Admin/Details/' + event.Id,
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription
})
console.log('event=', event);
}
self.alert = function () {
alert("Hello, I'm " + self.firstName() + " " + self.lastName() + ", the stuff!");
};
self.findMyEvents = function () {
var that = self;
//clear old events
that.myEvents([]);
$.ajax({
url: '@Url.Action("MyEventsAPI", "Home")',
dataType: 'json',
method: 'GET',
async: false,
data: { firstName: self.firstName(), lastName: self.lastName() },
success: function (data) {
var responseData = jQuery.parseJSON(data);
for (i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.addEvent(responseData[i]);
}
}
});
}
};
var resultsModel = function (params) {
var self = this;
self.hideEventsWithMeals = ko.observable(false);
self.results = params.results;
// The current item will be passed as the first parameter, so we know which event.id we have
self.viewDetails = function (event) {
window.location.replace("/Admin/Details/" + event.id);
}
}
ko.components.register('results-table', {
template: { element: 'results-table-template' },
viewModel: resultsModel
});
var overallViewModel = {
myModel: myModel,
resultsModel: new resultsModel(myModel.myEvents)
};
ko.applyBindings(overallViewModel);
{{my-events}}
Hold down Alt and click to zoom in and out on the slide
{{input type="checkbox" name="hideEventsWithMeals" checked=hideEventsWithMeals}}
var myEventsApp = Ember.Application.create({
rootElement: '#myEvents',
});
myEventsApp.MyEventsComponent = Ember.Component.extend({
firstName: "Eric",
lastName: "Johnson",
myEvents: [],
actions: {
alert: function () {
alert("Hello, I'm " + this.get('firstName') + " " + this.get('lastName') + ", the stuff!");
},
addEvent: function (event) {
this.myEvents.push(
{
id: event.Id,
//urlId: '/Admin/Details/' + event.Id, not needed
name: event.Name,
description: event.Description,
location: event.Location,
date: event.Date,
startTime: event.StartTime,
endTime: event.EndTime,
hasMeal: event.HasMeal,
mealDescription: event.MealDescription,
pastDate: new Date(event.Date) < new Date()
})
console.log('event=', event);
},
findMyEvents: function () {
var that = this;
//clear old events
that.set('myEvents', []);
$.ajax({
url: '@Url.Action("MyEventsAPI", "Home")',
dataType: 'json',
method: 'GET',
async: false,
data: { firstName: that.get('firstName'), lastName: that.get('lastName') },
success: function (data) {
var responseData = jQuery.parseJSON(data);
for (i = 0; i < responseData.length; i++) {
//console.log('for loop', i, responseData[i]);
that.send('addEvent', responseData[i]);
}
}
});
}
}
});
myEventsApp.ResultsTableComponent = Ember.Component.extend({
ResultsTableComponent: [],
hideEventsWithMeals: false,
myEventsWithoutMeals: Ember.computed.filterBy('results', 'hasMeal', false),
actions: {
viewDetails: function (eventId) {
window.location.replace("/Admin/Details/" + eventId);
}
}
});
*Press B or . on your keyboard to go black screen for a mic-drop. *