7

From JavaScript mess to Cleaner Code - Step 3

 3 years ago
source link: https://www.aligneddev.net/blog/2016/javascript-mess-to-cleanercode-step-3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

From JavaScript mess to Cleaner Code - Step 3 TypeScript with Knockout and RequireJs

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

HTML - data-binding example

The Knockout documentation is very helpful to learn about the bindings. <div class="row"> <select data-bind="options: yearOptions, value: selectedOption"></select> </div>

The Good

  • TypeScript gives compile time warnings, imports, strongly typed objects helping us discover, avoid typing errors and more
    • Easier for C# developers to get into with classes
    • Upcoming EcmaScript (JavaScript) features before they are all supported
  • KnockoutJs gives us UI binding and decouples us from the DOM, making it easier to test (and more fun to code in my opinion)
  • You don’t need TypeScript or Require for KnockoutJs. I recommend to start with this and then learn the next thing.
  • Chrome knockout extension helps debug bindings
  • RequireJs enables Import, avoid having to add the script src to the HTML in order, and smaller modules of code
    • NodeJs and NPM to get and manage dependencies
    • newer alternatives are SystemJs and Webpack

The Bad

  • You need to learn about TypeScript, KnockoutJs, RequireJs (configuration is the most difficult), something to compile the Typescript (grunt, gulp, Visual Studio, tsc.exe options). That’s a lot if you’re just getting started
  • RequireJs AMD approach seems to be falling out of favor for new technologies like webpack, SystemJs, etc?
  • need to have NodeJs and NPM to get dependencies (learning required)
  • more libraries and files = more bytes to download and less efficient (especially without bundling, minification and g-zip)

Now let’s look at implementing this with Aurelia in Step 4.1 and Angular2+ in Step 4.2.



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK