I’ve been using TypeScript with KnockoutJs and RequireJs for 2+ years now (Knockout for almost 4 years) with a team on a larger and growing code base. Code can be found in the tsWithKo folder .
TypeScript gives us type checking at compile time. This isn’t essential to Step 3 (it could have been a different step), but I think it fits in nicely. Checkout the TypeScript playground to learn more.
Knockout enables data binding and reduces the amount of code by avoiding the jQuery code that was interacting with and manipulating the DOM. It also enables easier unit testing, since the View Models (investigate Model View ViewModel MVVM for more details about view models) have properties and methods. They are decoupled from the DOM.
RequireJs is a module loader following the
Asynchronous Module Definition (AMD)
pattern. This enables import ...
in TypeScript and we avoid having to add the <script src="
in our HTML. We can
bundle and minify
using Gulp
or something else like
Grunt
.
Configuration is required and there are newer technologies than RequireJs, but I stayed with what I was familar with for this example.
The EnergyViewModel.ts file:
import * as ko from 'knockout';
import * as $ from 'jquery';
import LoadingIndicator from './loadingIndicator';
import EnergyDataApi from './energyDataApi';
import EnergyDataDto from './energyDataDto';
import EnergyRowViewModel from './energyRowViewModel';
export default class EnergyViewModel {
public yearOptions: KnockoutObservableArray<string> = ko.observableArray([]);
public selectedOption = ko.observable('all');
public energyData: KnockoutObservableArray<EnergyRowViewModel> = ko.observableArray([]);
/**
* The selected row that was clicked for deatils.
*/
public rowVmToShowDetailsFor: KnockoutComputed<EnergyRowViewModel>;
/**
* The View Model (for data binding to the View) for the main energy view.
*/
constructor(private energyDataApi: EnergyDataApi, private loadingIndicator: LoadingIndicator) {
// react to a change in the year option selection
this.selectedOption.subscribe((selectedOption) => {
this.yearOptionSelected(selectedOption);
});
// a computed property, updates when a value inside changes
// not the best use (would get really CPU intensive with 1000's)
this.rowVmToShowDetailsFor = ko.computed(() => {
var active = this.energyData().filter((d) =>{
return d.isDetailsActive();
})[0];
console.log('computing rowVmToShowDetailsFor ' + JSON.stringify(active));
return active;
}, this);
}
public initialize() {
this.loadingIndicator.showLoading();
var yearOptionsPromise = this.energyDataApi.getYearOptions().then((options: string[]) => {
this.yearOptions(options);
})
return $.when<void | EnergyDataDto[]>(yearOptionsPromise, this.getEnergyData(this.selectedOption())).then(() => {
this.loadingIndicator.hideLoading();
});
}
public yearOptionSelected(selectedOption: string) {
this.loadingIndicator.showLoading();
return this.getEnergyData(selectedOption).then(() => {
this.loadingIndicator.hideLoading();
});
}
public getEnergyData(option: string) {
return this.energyDataApi.getEnergyData(option).then((energyData) => {
var vmList = energyData.map((data) => {
return new EnergyRowViewModel(data);
});
this.energyData(vmList);
});
}
}
The
Knockout documentation is very helpful
to learn about the bindings.
<div class="row"> <select data-bind="options: yearOptions, value: selectedOption"></select> </div>
Now let’s look at implementing this with Aurelia in Step 4.1 and Angular2+ in Step 4.2 .
Please consider using Brave and adding me to your BAT payment ledger. Then you won't have to see ads! (when I get to $100 in Google Ads for a payout, I pledge to turn off ads)
Also check out my Resources Page for referrals that would help me.