Telerik blogs
3ds_header

Modern web development using JavaScript has evolved over the past decade to embrace patterns and good practices that are implemented through various libraries and frameworks. Although it is easy to get caught up in the excitement of frameworks like AngularJS or the KendoUI implementation of MVVM (that's Model-View-ViewModel, which I'll explain more about in an upcoming article), it is important to remember the fundamental patterns and repeatable practices that make development easier. In fact, it is difficult to make a qualified decision about your development stack without understanding "how" and "why" a particular tool, library, or framework may benefit the application and, more importantly, your team.

I've been either programming or leading teams who program line of business websites since the mid-90s when the first commercial applications on the "World Wide Web" began to emerge. In my opinion, there are three fundamental concepts that have truly revolutionized the way web apps are developed - what I am calling the "3 D's of Web Development".

  • Declarative (versus Imperative)

  • Data-binding

  • Dependency injection

I learned about the differences between declarative and imperative code when I was building Silverlight applications using XAML and C#. I was delighted to see that these concepts carried over into HTML and JavaScript development. Data-binding is a huge benefit that leverages these concepts. Finally, dependency injection is crucial for managing large code bases (or moderate sized code bases managed by large teams) - and a number of solutions have appeared to address this in JavaScript.

In this short series, I will summarize these concepts and share lessons learned from case studies that illustrate why they are so important. Note they are not the only concepts, but if you master them I guarantee you'll look at your projects, tools, libraries, and frameworks in a different light.

The first concept we'll explore is declarative versus imperative.

Case Study: Sentiment Analysis

Before we get into the details of what I mean by declarative and imperative approaches, let me share an example from my own experience that I believe illustrates why a solid understanding of these approaches can be important. While this example was built using Silverlight and C#, the same overall approach can be applied to JavaScript application development.

In late 2009, Microsoft reached out to my company to assist them with a rewrite of an internal Silverlight application they were working on. It was one of those "skunkworks" projects that was incredibly effective but slapped together ad hoc and needed to be refactored to be ready for prime time.

Being an aesthetically challenged developer, I knew we would leverage the services of a design firm and work in tandem to create the next generation solution. The application was pioneering at the time because it aggregated data from various social networking platforms, then ran sentiment analysis to determine if posts were "positive" or "negative", and finally enabled the management of campaigns to address negative sentiment.

A great example of how this application could be used is an automobile manufacturer that issues a large recall. The engine would collect data related to the recall and generate a typically negative baseline sentiment. The company would then launch a campaign to educate consumers, provide apologies and compensation, and then measure the overall impact on sentiment. There are many platforms and engines for conducting that type of analysis and campaigns today.

The critical aspect of this project was that it was on a tight timeline so design and development would need to progress in parallel. A far more traditional approach would be to go through a design phase, providing wireframes and various design comps, until the team agreed on a specific implementation of the experience and then start developing. We didn't have the time to follow that approach.

Fortunately, we didn't have to.

One simple example that I remember in the application was a section that allowed users to access to various campaigns. In the original interface, users selected a campaign from a dropdown list and then the details appeared in a selection area on the screen. The design eventually evolved to show summary information about every campaign in a left pane, with the same detail in a right pane lighting up when a campaign "tile" was clicked on. Thankfully, this required minimal refactoring because XAML used a declarative approach so that the team was able to change some simple markup and without touching a line of code.

Declarative and Imperative Defined

It is very easy to define imperative and declarative programming in terms of development using either XAML and C# or HTML and JavaScript. The code (either C# or JavaScript) is imperative. The markup (either XAML or HTML) is declarative.

To illustrate even further, with imperative code you describe how to do what you want done. Conversely, declarative code enables you to describe what you want done without worrying about how.

For example, in Kendo UI with jQuery you might write the following code to define a textbox that takes numeric values and doesn't allow alphanumeric input or input that is outside of a defined range.

$("#textbox").kendoNumericTextBox({
    value: 10,
    min: -10,
    max: 100,
    step: 0.75,
    format: "n",
    decimals: 3
});

In that code, jQuery was instructed to find a DOM element with an id of textbox, then call the extension method kendoNumericTextBox and pass in the various parameters that configure the initial value, ranges, etc. This is how you imperatively transform a simple DOM element to a Kendo UI widget.

If you use Kendo UI with Angular, you might do this instead:

<input kendo-numerictextbox k-min="-10" k-max="100" k-step="0.75"
  k-format="n" k-decimals="3"/>

That example takes a declarative approach. Instead of worrying about how to transform the element, you simply declare what you want to happen. It should be a numeric textbox; it should have a minimum value of -10; etc.

This is a very important distinction. In the sentiment analysis application, if the team had imperatively located a dropdown and then populated it with the list, the changes to use a set of tiles would have required changing both the UI (XAML) and the code (C#). Instead, the team simply used C# to build and expose a list of campaigns and to track a selected campaign. The XAML on the dropdown declared the array of campaigns it would use. When it was refactored to a list, the XAML was updated to a ListBox widget that declared the same campaign array.

In JavaScript with jQuery, the imperative approach looks like this:

var options = $("#options");
$.each(result, function() {
    options.append($("<option />").val(this.id).text(this.name));
});

Refactoring to a div instead would require changing the code to loop through and construct a list of div tags as well as bind to a click event.

Using an imperative approach with Angular, the original markup:

<select ng-options="item.name for item in items track by item.id">
</select>

Can be refactored to:

<div ng-repeat="item in items" ng-click="select(item)">{{item.name}}
</div>

This allows more flexibility in the design without having to revisit the code, so that the programmer can move onto other tasks while the design is updated.

The Designer-Developer Workflow

The designer-developer workflow refers to how the user experience and design team interfaces with the development team (and for those of you operating in an Agile context, I do recognize technically they are all the development team, so feel free to think of it as designer role and programmer role).

You may have participated in projects that leveraged a sequential workflow. Development was essentially placed on hold until the design was completed, then the developers were engaged to implement the design. A clean definition and separation of declarative and imperative code enables a more parallel workflow. This is because the implementation of the UI can change even if the data doesn't.

In the end, most of what takes place is the transformation of data to information. The data itself as bits and bytes isn't very useful to the user, but, rendered as a graphic or tile or input box, it becomes meaningful.

I like to further break down the process into three distinct components.

  • The data is ultimately what drives most business applications, but

  • The user interface is what makes it meaningful, and

  • Certain behaviors are applied to enhance the experience"

Keeping these three separated ensures maximum flexibility and maintainability of the application. I've found it's almost always possible to agree upon what data is needed for a particular interface even if the interface hasn't been designed yet.

Case Study: TV Listings

A perfect example is the cable company our team worked with to build a new experience for their channel listings. We began the project before they had even finalized the APIs to retrieve the information! Fortunately, there are many properties about listings that are fairly universal:

  • A channel typically has a number, code, and description associated with it

  • A listing has a type (is it a movie or a series?)

  • A listing has a category (horror, documentary, comedy, etc.)

  • A listing has a title

  • A listing may have actors associated with it

There are many more properties but you get the point.

We made it the job of the imperative code to define data structures to hold the information, retrieve the information (initially through a set of mocked or dummy interfaces that were later refactored to connect with the live APIs once they were developed), and expose the information. This development took place without any dependencies on the APIs being complete or any level of design.

The team even roughed out a simple grid for the listings to have something to interact with while the design team iterated through wireframes or comps. Eventually the grid was designed and the declarative markup updated to reflect the design. No coding changes were required and everything was fully tested up to that point.

In addition, a set of behaviors was defined. When a user clicked on a listing in the grid, the initial behavior was applied to pop up a dialog with the listing information. Later, the design team decided it would be more visually appealing to have the grid swing on a 3D axis away from the viewer and slide the dialog in from the edge. This helped the user retain their context in the grid but enabled more real estate for the listing.

The dialog and grid were both defined in declarative markup. The pop-up was written as a behavior that could be applied declaratively. Using HTML as an example, it may have looked like this:

<div class="listingTile" popupTarget="dialog"></div>

The slide-in effect manipulated both the parent grid and the dialog, so the behavior was written to be implemented like this:

<div class="listingTile" slideParent="grid" slideTarget="dialog"></div>

Imperative code was written to implement the behavior. Once written, it was applied declaratively to whatever elements used the behavior.

Behaviors

You may have heard the term aspect-oriented programming (AOP). It aims to separate certain common concerns or behaviors from code. Behaviors that are not core to the imperative logic you are working on, such as logging or authentication, can be added without modifying code. How is this done?

You may have been writing aspect-oriented code using behaviors without even knowing it. Consider this code snippet:

public Widget GetWidget()
{
    if (HttpContext.Current.User.IsInRole("Widgeteer"))
    {
        return Widget();
    }
}

In that example, imperative code was used to authorize an authenticated user to retrieve information. Authorization is a cross-cutting concern, meaning it is something that may be used throughout your application and isn't specific to individual functions.

A more common approach to solve authorization is to use a behavior to check for the role without modifying the code itself. You may have written something like this instead:

[Authorize(Roles="Widgeteer")]
public Widget GetWidget()
{
    return Widget();
}

In the second example, the method itself focuses on the specific task of returning a widget. The cross-cutting concern of authorization is handled by applying an authorization behavior to the method. This was done using an attribute.

It may not have seemed that impressive at first, but now you can tell your boss that you are an aspect-oriented programmer and used declarative code to address a cross-cutting concern of your imperative business logic.

TypeScript and ES7 Decorators

The ECMAScript7 (aka JavaScript 2016) specification contains a proposal for decorators that enable annotations or metadata to modify classes and properties. This is now implemented in TypeScript and used heavily in Angular 2. There are examples of decorators in the Kendo UI documentation for Angular 2 integration.

Consider the following code snippet:

@Component({ selector: 'my-app' })
@View({ templateUrl: 'mytemplate.html' })
class MyComponent {
}

The class itself will implement logic to expose data and behaviors. The Component declaration notifies Angular 2 that the class will participate in data-binding and may affect the DOM. The View declaration provides information about what markup to inject into the DOM for the control. Once the markup is rendered, Angular 2 will use data-binding to connect the imperative class with its declarative markup.

Summary

The concepts of declarative markup and imperative code are important to understand because they impact not only how you architect solutions, but also how team members collaborate and work in parallel to deliver and maintain solutions. In this article, I discussed the differences between the two approaches and shared some case studies to demonstrate the benefits.

In the next article in this series, I will dive deeper into another concept called data-binding that bridges the gap between declarative markup and imperative code. I'll explain why I believe the introduction of formalized frameworks that support data-binding to the JavaScript and HTML stack has been one of the most significant improvements to web development since the introduction of jQuery in 2006. I'll also share case studies and show how to build apps with a data-binding mindset that is much different than a traditional imperative code-based approach.

Related Resources

Header image courtesy of Christiaan Colen


jeremy likness
About the Author

Jeremy Likness

Jeremy is a senior cloud developer advocate for Azure at Microsoft and a former 8-year Microsoft MVP. Jeremy is an experienced entrepreneur and technology executive who has successfully helped ship commercial enterprise software for 20 years. You can read more from him at his blog or find him on GitHub.

Comments

Comments are disabled in preview mode.