Close

Do we still need web frameworks?

The past, present, and future of component-based development on the web

In my previous two posts, I’ve compared Polymer and Angular on performance and developer experience. In this final post of the series, I want to take a step back and look at how we ended up in a situation where Google has two models for building components for the web, what that means for developers today, and what the future might bring. 

A brief look at history

Polymer and Angular are perhaps more similar than you realize. For starters, both are Google projects. Both also take a component-based approach to building apps.

The Polymer project got started in late 2013 as a library and browser polyfill to help developers get started using the Web Components standards that had been introduced two years earlier. Work on Angular 2 began about a year after Polymer, in late 2014. Among the goals for Angular 2 were improved performance and a more scalable, component-based development model. Initially, the Angular team was planning to base the component model on standard components.

Angular 2 plays nicely with web components built using other libraries (Polymer, X-Tag, and others), allowing you to pass data into them as easily as if they were written in Angular. Angular components use web standards (such as shadow DOM and the HTML5 template tag) in browsers that support them.
angular.io, March 2015.

The Angular team later changed their position and built their own component model that is very similar to Web Components, just not tied to the Web Platform. They did this to not be dependent on the DOM. For performance reasons, they wanted to offload some of the browser work to a Web Worker, do pre-rendering on the server and support platforms other than the Web. The downside of this was added complexity — both runtime and development time.

While both Polymer and Angular have a relatively similar take on what the role of a component is, they differ significantly in how prescriptive they are. Polymer is a small library that builds on top of Web standards. Its component model scales from the use of a single Web Component on a static page to building entire applications. It includes helpers for data binding, routing, and localization, but they are all optional. There are some emerging best practices for how to structure applications, but Polymer doesn’t force any of them on the developer.

Angular is the enterprise Java of front-end frameworks

Angular, on the other hand, is much more opinionated. It’s also more than just a framework; it’s a platform of its own. Angular is the enterprise Java of front-end frameworks. It provides enterprise developers the tools and patterns they are used to when building complex apps. It has support for routing, dependency injection, localization, pre-rendering and testing. It gives developers a well-defined way of structuring their apps and dealing with data flow, removing a lot of the uncertainty from building an app.

Why is Google building both Polymer and Angular?

Why is it then that Google has these two separate projects for building component-based Web applications? Wouldn’t it make more sense for Angular to use Web Components as it’s component model? After all, that’s what the Angular 2 team tried to do at first, right?

I think that Google’s end goal is to make the Web as attractive as it can be for app developers. The Web is after all where they make their money. 

One of the obstacles they identified was that the Web needed a component model to support the building of more complex applications. They needed a quick solution, so Angular 2 was created with its own component model. 

Meanwhile, Google worked with the W3C and other browser vendors to pass standards for adding a component model to the Web platform itself. The standardization process is slow, so they created browser polyfills and the Polymer library to help developers get started using the upcoming features.

By having both Angular and Polymer, Google can offer a way of building component-based Web apps in the short term while working on a more elegant solution for the long term. It wouldn’t surprise me if future versions of Angular moved over to use (or at least support) Web Components when browser support is universal.

Community engagement

When it comes to community size and engagement, there’s no question about which one is more popular among developers. Angular has become one of the most popular web frameworks over the last five years, while Polymer and Web Components have remained a very niche technology.

Google Trends for Angular (blue) and Polymer (red) over the last five years.

Polymer is mostly aimed at component developers. The fact is that Web Components alone are a too low-level construct in most cases when building complex apps. Developers want more structure and help, which means that they are looking for frameworks when building larger applications.

Angular’s popularity also means that there is much more community-generated documentation, courses and other learning material available. You are also more likely to find answers to your Angular questions on Stack Overflow and similar sites. For developers looking for a well-established tool for their next project, especially if they need to get buy-in from their management, Angular looks more appealing at the moment.

Paving the way for the next generation of Web frameworks

Looking at the developer adoption of Polymer and Angular makes it fairly evident that developers want to use frameworks when building apps. The way I see it, Angular represents the current generation of Web frameworks while Polymer is paving the way for the next generation.  

Building on Web Components, future Web frameworks can be built leaner and faster as they no longer need to build and run their own component models in the browser. The shared component model also means that frameworks can be made more modular. Some projects only need a router; others may additionally need dependency injection, localization or a testing library. Being able to choose only the parts you need will make development easier and the end user experience faster.

While Web Components are a very powerful concept, I think their biggest impact for most developers is that they are paving the way for a new generation of faster, more light-weight frameworks and tools — a future where every new framework doesn’t need to build its own component set.

Lazy loading with Vaadin 8

One of my favorite new features in Vaadin 8 is the Grid::setDataProvider method which makes it remarkably easy to implement lazy loading in Grids. In earlier versions of Vaadin, you had to implement a rather complex Container interface. Vaadin 8 not only removes this interface, but also provides a modern API that takes advantage of many Java 8 features.

In this blog post, you will learn how to implement lazy loading to show a sortable list of people in a Grid component by simply providing two lambda expressions. You can find two “flavors” of the example application: One for people who use Spring (Spring Boot), and one for people who use Java EE (CDI and WildFly Swarm):

What is lazy loading?

Lazy loading is a technique used to delay the loading of resources to the point where it’s actually needed. Say you have a database table with 1000 rows, and you want to show this data in a UI with space for 20 rows at a time. You can query just the first 20 rows and when the user scrolls down the list, then you query the next 20 rows, and so forth. If you fetch all the data from your data source, your app will consume more memory and will take longer to show something on the screen.

When you want to display tons of data, you would probably use Vaadin’s Grid. By default, Grid performs lazy loading between the client and the server, which already improves performance a lot. This means that you have lazy loading out-of-the-box between the Grid component (in the browser) and the web server. How you query your data source is entirely up to you. If you try to fetch all the data at once, then the Grid will only send what’s needed when it’s needed to the client. However, all the data will stay in the memory on the server-side. Fortunately, the Grid class provides the mechanisms to allow you to query your data source in a lazy way. Let’s see how to do it!

The domain model

Suppose you have a domain class like the following:

public class Person {

    private Long id;
    private String firstName;
    private String lastName;
    private String email;

    ... getter and setters ...
}

And a service class like this:

public class PersonService {

    public List<Person> findAll(int offset, int limit) {
        ...
    }

    public int count() {
        ...
    }
}
 

The actual implementation of the methods depends on your specific persistence technologies. You can find a full implementation of the Person and PersonService classes for both Spring and Java EE applications using the links provided in the introduction.

Lazy loading functionality with Grid

You obviously need an instance of the PersonService class and a new Grid:

PersonService service = new PersonService();
Grid<Person> grid = new Grid<>(Person.class);

Notice how, in Vaadin 8, Grid is parameterized with the domain type (Person). Now, instead of using the infamous grid.setItems(service.findAll()), you can use the setDataProvider method and pass:

  1. A lambda expression to return a “slice” of the data and,

  2. Another lambda expression to return the total count of people in the data source.

You can delegate these operations to the service class:

grid.setDataProvider(
    (sortOrders, offset, limit) ->
            service.findAll(offset, limit).stream(),
    () -> service.count()
);

Ordering functionality

If you read the previous snippet of code, you might have noticed the sortOrders parameter in the first lambda expression. sortOrder is a List of QuerySortOrder objects that you can use to tell your service how to order the data.

The PersonService::findAll method accepts a Map with String keys representing the name of a Java property in the Person class, and a Boolean value telling whether to sort the property in ascending order. So, for this example, you have to translate between a List<QuerySortOrder> and a Map<String, Boolean>). Some Java should do the job:

Map<String, Boolean> sortOrder = new LinkedHashMap<>();

for (QuerySortOrder order : sortOrders) {
    sortOrder.put(order.getSorted(),
            SortDirection.ASCENDING.equals(order.getDirection()));
}

As you can see, the QuerySortOrder::getSorted method returns a string with the name of the Java property, and the SortOrder::getDirection method (QuerySortOrder extends SortOrder) returns a value from the SortDirection enum.

And that’s it! You can pass this map to the service, so the complete call to the setDataProvider method would look like this:

grid.setDataProvider(
    (sortOrders, offset, limit) -> {
        Map<String, Boolean> sortOrder = sortOrders.stream()
                .collect(Collectors.toMap(
                        sort -> sort.getSorted(),
                        sort -> SortDirection.ASCENDING.equals(
                                sort.getDirection())));

        return service.findAll(offset, limit, sortOrder).stream();
    },
    () -> service.count()
);

Learn more about Vaadin 8

Comparing Polymer and Angular from a developer’s perspective

In my previous post, I showed that Polymer has a significant performance advantage over Angular. If you are trying to make your app fast (you should be), you are probably wondering if it would make sense to build it with Polymer.

The potential problem with building an app with Polymer is that Polymer was not initially designed for building applications. It’s a library to help developers work with Web Component APIs (think jQuery for the Web Components APIs). 

Fortunately, the component-based development model offered by Web Components is flexible enough that you can build complex applications using the browser as your framework. But while it’s possible to create complex apps with Polymer, it’s still missing the structure and guidance a framework gives you.

In this post, I will compare the development experiences in building the same application in both Angular and Polymer.

Tools and languages

Both Angular and Polymer come with command-line (CLI) tools that help developers set up and build projects. The CLI tools hide much of the complexity of the build process: taking care of things like transpilation, optimizing and bundling code for production. The Angular CLI additionally (and optionally) performs an Ahead-of-Time (AOT) compilation step to reduce the amount of JavaScript processing that is needed in the browser. 

Polymer

Since Polymer is based on Web standards, components are built with HTML and JavaScript. In Polymer 2 and later, the default language level is ES6, which will get transpiled down to ES5 by the CLI to support older browsers. 

A simple Polymer component.

Polymer is still fairly limited when it comes to IDE support. The Polymer team is working on improving that through the Polymer IDE plugin that can be used with editors like VS Code and Atom. The Polymer IDE plugin helps developers avoid common mistakes, and offers some autocomplete help. Because Polymer uses plain ES6, there are no type checks.

Angular

Angular supports development in TypeScript, JavaScript, and Dart. Most documentation and examples that are available online are using TypeScript, which is the de-facto language for building Angular apps. TypeScript is a superset of JavaScript that adds type checking. Type checking gives developers an added layer of checks on the code and can be helpful to ensure APIs are used correctly, especially when working in bigger teams or when using third party libraries. 

A simple Angular component. 

The use of TypeScript in Angular also makes it easier for IDEs to offer development time help and checks. Visual Studio Code and WebStorm both have good support for TypeScript, the latter even helping the developer with including imports, making Angular development in an IDE fast despite the amount of boilerplate.

Debugging

Debugging Polymer applications is straightforward. You can use the browser developer tools to set breakpoints and inspect the running code just like you would with any other front-end technology. 

Most often, debugging the Angular app boiled down to a series of console.log statements

Angular is much more complex to debug due to the build steps between your code and the code that is running in the browser. The Angular CLI build creates source maps that should allow you to debug the TypeScript code in the browser. In practice, this didn’t work well as the source maps didn’t match the running code and breakpoints didn’t work. There is also a Chrome plugin for debugging Angular apps called Augury that allows you to inspect the hierarchy and status of your components. I did not find the plugin to be very useful in pinpointing issues. Most often, debugging the Angular app boiled down to a series of console.log statements. Error messages in the browser console were cryptic, and stack traces rarely include identifiable code, only Angular internal calls. 

Application structure and routing

Since both Angular and Polymer are component-based, the general application structure is similar. The UI is constructed out of components that get combined into successively more complex components, which are nested until an app emerges. In Angular, you can additionally bundle related components into modules that are used to scope dependencies, and that can be loaded lazily when needed.

Larger applications are organized into views. The views are typically bound to URLs so that users can link to different parts of the application and use browser navigation buttons to move back and forth. This mapping between view components and URLs is handled by a router. 

Angular ships with a somewhat complex but feature-rich router that supports arbitrarily deeply nested routes. It helps the developer keep the application’s JavaScript size manageable by lazily loading code for modules that aren’t needed until later. It further allows routes to be equipped with Guards, code that is run before or after a view gets activated, to conditionally prevent navigation. The router is configured in a TypeScript file through nested objects.

Polymer ships an optional router that can be used to map between URLs and components. Using the router requires several steps and a lot of repetitive code. You need to add an app-location and anapp-route element to your app to listen for URL changes. Using this information, you can decide what components to show, and optionally (manually) hook up lazy loading of those components. 

By default, Polymer components are only lazily loaded into the DOM, but not removed. This leads to the DOM growing over time in large applications which may result in performance issues, especially on devices with limited memory. 

The lack of a good router is the single biggest factor making it difficult to build large applications in Polymer

The Polymer router also supports nested routes. In practice, however, using nested routes gets very complicated as the router doesn’t know which view is active so sub-routes will get triggered on all views regardless if they are visible. The router does not either hook up to the build tools, so you need to separately configure views for lazy loading. 

Based on my experience, I feel that the lack of a good router is the single biggest factor making it difficult to build large applications in Polymer. 

Working with data

Data binding and data flow are important aspects of building apps. One of the functionalities that Polymer provides on top of the Web Components standards is a simple data binding system that supports both one-way and two-way binding of data. One-way data binding is preferred over two-way binding to make it easier to integrate the components with other frameworks and to enable uni-directional data flow patterns like Redux.

Angular also comes with support for both one-way and two-way data binding. In addition to data binding, it also comes with a Dependency Injection (DI) system that makes it easier to hook up services, especially when testing. Angular builds heavily on RxJS and Observables for handling data, especially for asynchronous HTTP communication. The reactive programming model will take some getting used to if you don’t have experience from before, but enables you to build elegant UI code. 

Editing data in forms is somewhat different in Polymer and Angular. Polymer has a form element that extends the HTML form. All validation is handled by the form elements themselves, which is a bit limiting at times. In Angular, the validation is handled by the framework which makes it easier to combine different components but still have consistent validation.

Making it look nice

When it comes to customizing the look and feel of your app, Angular is much easier to work with than Polymer. Angular works together with existing CSS libraries like Bootstrap or Semantic UI and you can easily write global CSS to give your app a cohesive look. 

Polymer is harder to customize in terms of look and feel because Web Components are designed to encapsulate their implementations, which means that you need to declare CSS variables and mixins that you import in your components. This means that you cannot use something like Boostrap easily and that it’s hard to get third party components fit your applications look and feel. 

Angular also makes working with dynamic styles easier than polymer, as the template syntax includes a helper for setting style names based on a condition. In Polymer, you need to rely on basic data binding and custom methods which adds a lot of boilerplate.

Mobile support: Progressive Web Apps, native apps, or both?

Angular and Polymer have different approaches when it comes to mobile support. Angular is more focused on today’s needs, so it includes support for building both Web applications and native applications (although native support requires you to write a separate implementation of the view). They also added support for creating Progressive Web Applications (PWA) recently. 

For Polymer, PWA is the only mobile strategy. This is in line with Polymer’s goal of building on Web standards to expand what you can do on the Web. 

Maintainability and stability 

Angular and Polymer both have a less than stellar track record when it comes to API stability. Polymer had a very rough transition period leading from version 0.5 until 1.0. Likewise, Angular 2 went through an extended period of API changes during development, lasting throughout the release candidates. There have even been some smaller breaking changes within the “stable” 2.x version. 

The good news is that both teams have taken note and are planning for smoother upgrade paths in the future. Polymer 2, currently in release candidate, includes support for legacy components so you can migrate applications piece by piece. The Angular team has also promised that breaking changes in Angular 2+ will be easier to migrate, and have hinted several times at a migration tool to streamline the changes.

While it’s hard to predict the future, I think that Polymer and the W3C standards it builds on will provide a more stable foundation for apps that need to be maintained for longer periods. 

Choosing development comfort over performance

We know that Polymer allows us to create fast web apps. But it turns out that Polymer by itself is too low-level to easily build big applications. There is clearly still a reason developers are selecting a framework like Angular instead of Polymer. Frameworks help developers structure applications and take care of things like routing, dependency injection, localization and other things you need to deal with when building a large application. Unfortunately, this developer convenience comes at a runtime performance cost to the end user. 

A good first step towards making Web Components suitable for building bigger and more performant apps would be an improved router. It would both help to give structure to apps and make it easier to create apps with tens or hundreds of views.  


In the last post of this series, I’ll take a look at why Google is working on two competing component-based tools for building web apps and what it might mean for the future of Web frameworks. 

Stay tuned.