Single-page applications: old & new solutions for user interface (UI)
About single-page applications
Single-page applications (SPA) are nowadays the standard for the implementation of user interfaces for web applications. In addition to providing a more user-friendly experience, close to a desktop application, single-page applications allow developers to better manage data transfer between application and server, reducing load as well as the waiting time. While the process of designing and developing a complex single-page application may be laborious, the various problems raised by this type of architecture have been largely overcome using modern development frameworks such as Angular or React. Therefore, the main challenges have now shifted to the UX (user experience) side, where the requirements are constantly evolving toward providing as much information as possible at any time while also keeping the user focused on performing his tasks in the most efficient manner.
There are no magic solutions for designing a great single-page application. As long as we are tied to a browser, any user interface will still employ elements of a website, which means that it will be regarded by many users as a set of “pages” accessible through a menu. Those users will always try to quickly find their way through the application using familiar interaction patterns, which at times may not be exactly the ones envisioned by the designer. Moreover, any original single-page application interface will need to find a fine balance between implementing a wide range of custom solutions and keeping the development effort under control. This may often lead to oversimplifications and inconsistencies arising from reusing the same interface components for multiple purposes or in different visual contexts, with little attention to the details of the tasks the users will have to perform in real-life scenarios.
Some user interfaces do a decent job regarding usability, in most cases with a great deal of help from off-the-shelf component libraries and tools (like Material Design), which come with guidelines and solutions to the most common user needs. Still, when designing a new user interface, especially one for a large enterprise application, the constraints and limitations of a typical web interface are not easy to overcome, and there is always room for improvement.
A simple user scenario…
Let us start with a rather common scenario: the users want to update the status of a certain project from the ones their team is currently working on. They will open the list of projects, filter it by some criteria (like client name) to quickly find the one they are looking for, and click to open its details. Next, the users will look through a list of tasks related to the project, find the ones assigned to them, change the status of some high-priority ones, and then create a new task for a co-worker. Finally, they will go to the list of files related to the same project and upload some more documents.
… and some interface solutions
What we described above is a sequence of steps that goes in a linear fashion up to a point (project details) and then splits into two sub-sequences, each with its own extra steps. If we look at existing single-page applications, we will find multiple solutions for implementing such a scenario.
A page-based approach would involve creating components for each step and displaying them one at a time, based on user choices. So, we would first have a list of projects, maybe using a table view to show as many details as possible for each record, with usual sorting and filtering. Then, when the users select the desired project, we will replace the list with a set of detailed information for that project. Some links will be provided for accessing the list of related tasks and documents. Clicking on one link will replace the project details with the subsequent component, where we will use perhaps the same table view approach with sorting and filtering for either tasks or documents. A further selection of an element from the new list will show its details, an option that may not be needed for the documents if all information can be fitted in the list itself. The last step would involve displaying some forms for creating or editing tasks or for uploading documents, often in a modal dialog box. While this approach may provide easy navigation through the single-page application, the overall experience of the users is subpar because they need to go back and forth through a lot of pages to accomplish their goals.
A second solution is based on a simple design rule: always show a list of records and the details of the selected element from that list at the same time in a split-screen layout. The screen real estate will be put to better use, with the (rather minor) inconvenience of being able to show a smaller number of columns on the table view (it can be replaced with a more responsive generic list, though). The subsequent lists would use the same pattern, with the list of tasks displayed in pair with the details of the selected task, while the list of the documents can be paired directly with the upload form. To be as consistent as possible, the create/edit form for a task can temporarily replace the task details component on the right side of the screen, so a modal dialog would not be needed. The solution provides a more efficient way to interact with the single-page application, and it is widely used in web applications.
A third possible solution would attempt to display all project details and related data on a single “page” while the list of projects may still have its own full screen. Thus, in a multi-split layout, the users will see all project details, as well as the list of associated tasks and the details of a selected task, the list of associated documents, and -at times- the task create/edit form or the document upload form. All options are on the table (screen) for the users, who can choose what to see and what to do regarding the selected project without ever leaving the “page”. On the other hand, they are likely to lose focus on such a crammed screen, in addition to putting a strain on the server by loading a lot of data they may end up not using at all. So, despite some apparent advantages, it is not actually the most efficient implementation.
As we can see, each of the above solutions can provide a usable interface for the example scenario, but with various degrees of efficiency in accomplishing the user goals or in handling the communication with the server. However, when judging the advantages or the drawbacks of each approach, we need to consider some factors which are very relevant in a real case scenario.
First, the data structure and the relations between entities can be much more complex than described above, and they may also change over time. Let us extend the scenario with some more requirements. For example, we may find that some of the project documents are directly linked to their tasks. The users should be able to upload documents when creating or editing a task, and those documents should also appear in the list of documents for the parent project. Or the users may want to be able to create tasks and attach (point to) a document already present in the project’s documents list. What if we throw in the mix another data type, like some client orders tied to the same project, orders that also have relations to its documents and/or tasks? What if the project is related to a certain list of users (a team) which needs to be constantly updated? What if those users should have different roles on the project and/or client orders? What if some documents should be not just updated but created based on some provided templates? The list of changes can go on and on, and we may suddenly find that, after a while, a well-implemented interface solution is not applicable to the new requirements. Yes, usually, an interface organized around data types (solution 2) can easily handle any complexity, but how? By transferring the burden of understanding the relations and finding the relevant data on the user itself. This will never work very well.
Then there is the typical human behavior that needs to be considered. Sometimes the users can lose focus on what they are doing. They can be temporarily distracted by offscreen events like a phone call or will go to a meeting. Also, the way they use the single-page application is heavily influenced by their capacity to be organized. They can start filling out a form and, in the process, find the need to look for some additional information in another part of the application. Or they remember that another step should have been done before. Rather than letting them deal with all these situations by themselves, a good interface should always try to assist the users as much as possible in overcoming obstacles, preventing mistakes, or helping to fix them, all while keeping the focus on the main tasks intended to be performed.
Let us present another user interface solution, one that, while still having a lot to prove, seems to be quite effective in ensuring a consistent user experience in large-scale business applications. This solution, named “flow interface”, was developed mainly around the goal of providing effective navigation through the various sections and subsections of a complex interface, with special attention given to hierarchical data structures where tree views, the obvious choice, are accompanied by alternative navigation methods. Subsequent goals included limiting the number of server lookups by carefully choreographing the user behavior around the most important use cases, as well as efficient usage of large-screen real estate.
(Note: we will not discuss common interface elements, like a header or a sidebar/menu, since those components are rather mandatory in most single-page applications and are usually implemented in a way that takes minimum screen space.)
The flow interface concept uses some of the best features of each previously described solution while also solving some of their main issues. Since the second solution uses, arguably, the screen of real estate in a good way, without throwing too much data in a single layout, we will start from there. We will split the screen into two columns and display the list of projects on the left side and the details of a selected project on the right side. The list can have a permanent or hidden filter, as well as some sorting options, and will display a minimum of relevant information about each project in a custom block (no table view). For the details component, we may optionally choose to group de information into sections and display each piece of data in a visually distinctive label/value pair. The idea is to keep the layout consistent for the components of the same type, but otherwise, we can use any suitable layout inside a component.
We can easily continue to expand the flow with a list of documents related to the current task, which would be the same component used for project documents, but already filtered to show only the ones linked to the task. A document upload form can follow, and so on. But what will happen if the users go back to the first component in the flow, the project list, and choose another project? We show the new project details as the second component, but we also remove all the following components from the flow since they may contain data not related to the selected project. It is a logical move, and the users will understand it right away.
So, we have till now three main methods to manage components in a flow: adding a new component (always to the right side, as the next step in the flow), removing a component (together with all subsequent components from that point on), and replacing a component with another one (while again removing the next components if any). Those methods will allow the user to manage various sequences of activities by choosing, at each step, what he wants to see next. Also, displaying two flow columns at once (but not the same fixed pairs like in solution 3) means that the users will always have access to both the data they want to see or change (the right-side component) and its context (the left-side component).
What we get in the end is a dynamic user interface, able to adapt to different scenarios while using a single navigation concept. Of course, having many components at the same time (on or off-screen) raises some issues regarding the performance or keeping the information up to date, so a general communication mechanism is needed to ensure that the changes performed on a certain component will be reflected on another one. On a more technical level, this means implementing a solution for sending data and commands between components based on their id, type, or current position in the flow. In addition to this, the interface can let the user perform some actions manually, like refreshing a component or closing it.
The flow concept may seem complicated when described but already proved to be very effective in real-world applications and quite easy to learn, to the point that it could be further expanded, thus becoming a “multi-flow”. As the name suggests, the user interface can provide multiple flows at the same time, separated into tabs, which allows the user to bring them into view one at a time.
Of course, managing several tabs increases the level of complexity from the implementation point of view, but the advantages of having more active flows expand the user options significantly. They will be able to open parallel threads to work on several tasks simultaneously, a very important capability that is lacking in most web applications (it is usually accomplished by opening any number of extra browser tabs). The idea itself is not exclusive to browsers since many desktop applications (and some web-based ones) are using it and most of the usability issues associated with displaying a variable number of tabs have already been solved. The multi-flow concept can just leverage this experience and implement the best solutions for good integration of the main menu, multi-flow tabs, and flow-related navigation elements.
On a technical level, we may need to implement several functionalities to ensure a good user experience. Apart from the multi-flow management, we may need, for example, clever routing so that a specific URL opens the application with an already active flow. This will ensure that links from external apps, like an email client, can lead to a specific interface configuration. We can also save a certain sequence of components and recreate it in another session. Or we may let the user mark some components (a certain project details view, for example) as favorite and show a widget on the home page or dashboard from where he can open it with one click in its original context/flow. A lot of features can be developed on top of this capability of storing contexts (flow sequences) rather than only links to specific pages.
Notes on responsive layouts
A complex interface is not easy to be adapted to a small screen such a s the one on a mobile device. However, the flow interface described above can be handled quite well on a mobile device if we implement a few key functionalities. The most important of them is to switch the active flow to a single column, which means a single component will be visible at a time. If the inner layout of each component is itself responsive, then all we must do is to adopt some touch gestures, like using swipe to navigate right and left between opened components, to get an experience quite close to a native mobile app. In addition to this, the flow navigation bar can be converted into a dropdown menu and the multi-flow tabs bar can be limited to showing just icons of the related data type (same as the ones in the sidebar, perhaps). So, the responsivity requirements are not a dealbreaker for this interface.
Building efficient single-page applications
What was presented above are just some of the capabilities of this concept, and one can figure out many other ways of leveraging its flexibility in various user scenarios. There are, of course, some drawbacks related to the implementation of such an interface. On one side, there is an inherent complexity that has to be managed, mostly regarding the communication between various interface components opened at the same time on- or off-screen. This means that it may not worth implementing it for a single-page application or for one which uses limited-depth navigation paths. Also, there is a certain learning curve for the average user, who needs to be assisted, at least for the first day of his journey, with well-placed instructions and messages. And finally, depending on the actual implementation, the interface can be a medium to a heavy load for the browser (may need an important amount of memory to run). It is still a good option, maybe the best one for some data structures and user scenarios, for building an effective single-page application, one that the users can love to work with every day.