54474

Does KnockoutJS provide suitable architecture for building large web apps?

Question:

<h2>Quick question:</h2>

<em>Will KnockoutJS provide a solid ground for developing a <strong>large</strong> web app? I am afraid of having one huge viewModel that will become unmaintainable.</em>

<h2>Background info</h2>

I'll be building a web app that will be heavily client-side based. The backend will just be a RESTful endpoint. The entire interface of the web app will be built in pure HTML/CSS/JS - no server side scripting involved.

The web app itself will consist of several smaller apps with one general login (kind of like Google's web apps where you have Gmail, Docs, Calendar, Reader, etc.).

Each of those web apps will have some common functionality (such as a sidebar tree view, a top bar menu view, a notifications system), and some app-unique features. Usually I break my apps down to encapsulate functionality, something like:

var myNamespace = { common: { settings: {}, user: {}, notifications: {} }, app1: {}, app2: {}, app3: {} };

Now, I really enjoy working with KnockoutJS and figured that it will be helpful when building some elements of my project (such as the notification system, or an advanced grid view with auto-refresh as the app will support collaboration). But I just can't figure out where to put my viewModel into this structure.

I can only find trivial examples of how to build apps with KnockoutJS. <em>Can you actually build something more advanced than a Twitter reader with it?</em> Are there any good examples of how to break down a lot of functionality in the viewModel, or perhaps into many viewModels?

Proposed solution

While the more theoretical question (the Quick question) is still kind of unanswered here, I think I've found a solution that works in practice. @Simon 's answer gave me some food for thought, and here's what I've got so far:

// First: a collection of Observables that I want to share ld.collectionOfObservables = { notifications: ko.observableArray([]), }; // Now let's define a viewModel. I put all my stuff inside the // 'ld' namespace to avoid cluttering the global object. ld.viewModel1 = function (args) { // Look inside args and bind all given parameters // Normally you will want args to be an object of Observables. for (var key in args) { if (args.hasOwnProperty(key)) { this[key] = args[key]; } }; // So, by now we already have some observables in // 'this', if there were any supplied in 'args'. // Additionally, we define some model-unique properties/observables this.folders = [ 'Inbox', 'Archive', 'Sent', 'Spam' ]; this.selectedFolder = ko.observable('Inbox'); }; // *** Let's pretend I create similar class and call it ld.viewModel2 *** ld.viewModel2 = function (args) { .... } // OK, now go on and instantiate our viewModels! // This is the fun part: we can provide 0-many observables here, by providing them in an object // This way we can share observables among viewModels by simply suppling the same observables to different viewModels var vm1 = new ld.viewModel1({ notifications: ld.collectionOfObservables.notifications, // we take an Observable that was defined in the collection }); var vm2 = new ld.viewModel2({ notifications: ld.collectionOfObservables.notifications, // shared with vm1 }); // Of course, we could just send the entire ld.collectionOfObservables as an array // but I wanted to show that you can be more flexible and chose what to share. // Not easy to illustrate with *one* shared Observable - notifications - // but I hope you get the point. :) // Finally, initiate the new viewModels in a specified scope ko.applyBindings(vm1, document.getElementById('leftPane')); ko.applyBindings(vm2, document.getElementById('bottomPane'));

Now, if JS had real inheritance it'd be even better cause right now I feel that all my viewModels start with this:

for (var key in args) { if (args.hasOwnProperty(key)) { this[key] = args[key]; } };

But that's just a minor inconvenience. Let me know what you think!

<strong>Edit 1:</strong> Could the solution be as simple as using the with: binding? See "<a href="http://blog.stevensanderson.com/2011/08/31/knockout-1-3-0-beta-available/" rel="nofollow">1. Control flow bindings</a>" for an example.

<strong>Edit 2:</strong> I think my last edit was too quick. with: binding may help with the structure of your code, but AFAIK it doesn't help you share observables between those different parts. So the proposed solution above is still the way to go.

Answer1:

You can use partial views and share observables between them.

var some_observable = ko.observable() var Model1 = function(something) { this.something_1 = something; }; var Model2 = function(something) { this.something_2 = something; }; var view_1 = Model1(some_observable); var view_2 = Model2(some_observable); ko.applyBindings(view_1, document.getElementById('some-id')); ko.applyBindings(view_2, document.getElementById('some-other-id')); <div id='some-id'> <input data-bind='value: something_1' /> </div> <div id='some-other-id'> <input data-bind='value: something_2' /> </div>

I've been using this aproach to maintain a list photos in a gallery application, where one view renders thumbnails and another view takes care of uploads.

Answer2:

I've used partial views (in Nancy rather than MVC), each with their own knockout task, each with their own view model. I think it works beautifully - a complicated page broken apart into many simple independent partials. Most of the partial views have their own module/controller/endpoint so the modules are 'skinny' too.

It's a shame about the jQuery templates being dropped, but that's a different issue.

Sorry, I just re-read your post: no server side stuff, so no way to break up the page? Ouch. I still think lots of view models is the way to go.

Answer3:

In my view we could use KO and share View Models to the scope of functional module( say functional widget having multiple html controls). We could look into using <a href="https://docs.tibco.com/products/tibco-pagebus-2-0" rel="nofollow">TIBCO Page bus</a> (Pub/Sub) for communicating between these functional modules in the page to keep the functional modules in a page decoupled and managable manner.

Answer4:

This is an old post but recently I have built a framework for exactly the same purpose in <a href="https://github.com/batilc1/gcc-knockout" rel="nofollow">this repository that I call gcc-knockout</a>. Everything is a component and there is even a view manager to switch views completely and keep history at the sametime. I have not properly documented it yet but the repo comes with an example that demonstrates some of its features.

Note that I also used Google Closure Compiler. You can safely use it in Advanced Mode given that you properly export the properties you will use in html templates. Components communicate using goog.events and everything is pretty clean right now. I have not used knockout's subscribe utility. Feel free to check it out and contribute! I occasionally update it.

Recommend

  • Speedup pydev debugging on python 2.6+
  • Android: Drag ImageView Off Screen
  • How to set permissions to all users on a new share folder , by c# code?
  • What is the most efficient c program for sorting 3 values using if-else? [closed]
  • An answer to the 'how to get Facebook shares and likes of some URL ?' questions
  • Getting a refresh token from google api
  • Insert an hidden character in an HTML text string? [duplicate]
  • Path Similarity in a Directed Graph
  • Why will the following run in parallel rather than sequentially?
  • What is the intersection of two languages with different alphabets? [closed]
  • How to retrieve grouped messages ordered by date SQL
  • Get individual digits from an Int without using strings?
  • For Loop or executemany - Python and SQLite3
  • Why does Twitter use a hash and exclamation mark in URLs, and how do they rewrite search URLs?
  • “Unable to evaluate the expression” in Visual Studio 2012 Debug Mode
  • Webdriver Xpath Performance
  • Set theory data structure
  • How to normalize a database schema
  • Facebook Open Graph Story Custom Actions Keep Getting Rejected - Advice Please?
  • Magento “Please specify the product's option(s)” message remains after completing add to cart a
  • How to distribute an event to all nodes in a (Wildfly) cluster?
  • Allocating a 2D contiguous array within a function
  • Refactoring advice: maps to POJOs
  • converting text file into xml using php?
  • Assign variable to the value in HTML
  • Meteor helpers not available in Angular template
  • Fetching methods from BroadcastReceiver to update UI
  • How to recover from a Spring Social ExpiredAuthorizationException
  • Excel - Autoshape get it's name from cell (value)
  • Check if a string to interpolate provides expected placeholders
  • ILMerge & Keep Assembly Name
  • Large data - storage and query
  • Jquery - Jquery Wysiwyg return html as a string
  • WOWZA + RTMP + HTML5 Playback?
  • RestKit - RKRequestDelegate does not exist
  • Arrays break string types in Julia
  • Traverse Array and Display in markup
  • SQL merge duplicate rows and join values that are different
  • WPF Applying a trigger on binding failure
  • Java static initializers and reflection