JS MVC Smackdown

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

Let the JavaScript MVC "SmackDown" Begin

To Start With we had

- Just ASP.NET w/ MVC -

Knockout

Now to Compare to:

ember

Angular

VueJS

With Components

(more on components later)

This is the site UI the follow code examples implement.

Nothing Fancy: a form calls an API, a checkbox with a conditional if, and a forEach loop on the results

1. Knockout




Hold down Alt and click to zoom in and out on the slide

1. Knockout



 

1. Knockout


// 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);
					

2. Ember




{{input class="form-control text-box single-line" value=firstName placeholder="first"}}
{{input class="form-control text-box single-line" value=lastName placeholder="last"}}

Hold down Alt and click to zoom in and out on the slide

2. Ember



{{input type="checkbox" name="hideEventsWithMeals" checked=hideEventsWithMeals}} 

{{event.name}} {{event.description}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}
{{event.name}} {{event.description}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}

2. Ember


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]);
                    }
                }
            });
        }
    }
})
					

3. Angular 1.5



Hold down Alt and click to zoom in and out on the slide

3. Angular 1.5



 

{{event.name}} {{event.description}} {{event.date}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}

3. Angular 1.5


// 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]);
            }
        });
    }
});
					

4. Vue.js


Hold down Alt and click to zoom in and out on the slide

4. Vue.js



 

{{event.name}} {{event.description}} {{event.date}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}

4. Vue.js


// 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]);
                    }
                }
            });

        }
    }
});
					

What are Components?

What are Components?

  • "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

5. Knockout with Components




Hold down Alt and click to zoom in and out on the slide

5. Knockout with Components








					

5. Knockout with Components


// 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);
					

6. Ember with Components



{{my-events}}
{{input class="form-control text-box single-line" value=firstName placeholder="first"}}
{{input class="form-control text-box single-line" value=lastName placeholder="last"}}

Hold down Alt and click to zoom in and out on the slide

6. Ember with Components









  
{{input type="checkbox" name="hideEventsWithMeals" checked=hideEventsWithMeals}} 

{{event.name}} {{event.description}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}
{{event.name}} {{event.description}} {{event.location}} {{event.hasMeal}} {{event.mealDescription}} {{event.startTime}} {{event.endTime}}

6. Ember with Components


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);
        }
    }
});
					

Summary :

They're all good MV* Frameworks

Thank you for following along!


*Press B or . on your keyboard to go black screen for a mic-drop. *