Angular 2 and Relay

Keeping client and server in sync can be tough when building a single page app, and involves tons of boilerplate code. In this write up we’ll combine the best of two worlds by hooking up Angular 2 with Relay, making your workday much more fun and productive!

This technology is so promising, that we’re running an internal experiment at Small Improvements to evaluate its benefits. We gave a talk about Angular 2 and Relay at NG NL 2016 and this is a writeup of the content. (The video of the talk will be published within the next weeks.)

Intro

When you’re building web apps you’ll mostly have to deal with client-server communication. Relay is a library that simplifies client-server communication on an abstract level. In this post we’ll use an example and three challenges to demonstrate the power and simplicity of Relay.

A conference planner app will serve us as the example. This app displays a list of conferences. A user can can attend or leave conferences.

The UI looks like this:

Screen Shot 2016-02-16 at 17.33.18

Challenges with client-server communication

The three challenges we will look at in detail are:

  1. How to define data requirements explicitly within our components?
  2. How to ensure a consistent state after changes?
  3. How to ensure that we don’t break the contract between client and server?

 

Challenge #1 How to define data requirements explicitly within our components?

When writing a single page web application with or without a framework, such as Angular or React, you’ll follow best practices and write UI components. Responsibilities of these components are to display and modify data.

The component is usually connected to a layer  that provides data. This could be a standard service pattern, a flux like implementation, such as Redux or Reflux, or an RxJS stream. The implementation doesn’t matter, the point is that the component needs to rely on the fact that data it needs is provided.

In the graphic below the component asks the ServiceLayer for data, the ServiceLayer asks the CommunicationLayer, which requests the data from a Backend. The data is then piped through until it reaches our component.Screen Shot 2016-02-22 at 13.51.56

The problem here is that the data needs of a component are not specified where they are used. It is the component, which knows all the information about what data it needs, yet it has to rely on someone else that the data will be requested accordingly. Or in other words, the usage is completely disconnected from the request of the data

This disconnection results in components that are hard to understand and tricky to reuse. It’s not obvious what data is needed from the server and if you want to reuse the component you’ll have to ensure that it is properly wired to the layer that provides the data.

Challenge #2 How to ensure a consistent state after changes?

A common and very tricky challenge is to keep the app state consistent. Every time a user interacts with the application, its state is changed. These changes affect the server state and our app state. To ensure consistency we’ll have to update the server and make sure that our internal app state is updated accordingly.

These interactions often have multiple side effects, which can make state updates very complex. This results in complicated manual updating or re-fetching of data.

Let’s use the conference planner as a concrete example. We, as the user Lisa, want to attend NG-Europe. To do so, we’re clicking on the “Attend” button and we’d expect to see the following changes in the UI (highlighted in orange):

Screen Shot 2016-02-22 at 15.50.26

  • The Button label changes from Attend to Leave and its color from green to red.
  • The attendance count for NG-Europe is increased by one.
  • NG-Europe is added to the list of conferences, which the user Lisa is attending.

This single action triggers side effects on multiple data nodes, that would need manual updating or re-fetching.

Challenge #3 How to ensure that we don’t break the contract between client and server?

APIs are usually constantly evolving and most of the time it’s a pain to ensure, that you don’t break consuming clients. A fallback solution is to apply versioning of your API or the attemot to stay backwards-compatible at all times, but this makes even simple changes like renaming a field very inconvenient.

The agreement or contract between client and server is very implicit. Every time a component is using a specific property (for example the date for a conference) it couples itself to server – the contract gets richer. The client expects implicitly that the conference contains such a property. If we now rename or even delete this property on the server side, w don’t have a simply way to check if we broke the contract – we might only discover the issue through a runtime exception. This can lead to developers being very hesitant to refactor the server side API at all, which is far from ideal.

Relay/GraphQL

Facebook had similar problems and they solved them by creating two new libraries: Relay and GraphQL. At first they were only used internally, but last year Facebook open-sourced both.

Relay is a client library which depends on a GraphQL backend. This means Relay can’t talk to a REST/JSON-RPC server, only to a server which is able to understand GraphQL requests.

GraphQL is a query language and runtime to query and change deeply nested JavaScript structures (graphs). One of the most outstanding features of GraphQL is that it is statically typed: At build-time you know exactly what requests and what types are supported.

This is the general architecture:

Screen Shot 2016-02-22 at 13.58.18

The client includes the Relay library which sends GraphQL requests to the server. The server uses the GraphQL library to execute the query and retrieve the data. You can wire the GraphQL layer on top of your business layer, as you would with a REST implementation. It is not designed to solve graph-like problems, but to provide a unified query language to request graph-like structures.

The result of the GraphQL requests is then returned to the client as regular JSON. (Implementations of GraphQL are available for a wide range of programming languages/ platforms such as JavaScript or Java.)

Here is what a GraphQL request and the corresponding response looks like:

Screen Shot 2016-02-22 at 14.29.13

The so called “fragment” specifies properties on an entity. In this example we’re querying for the properties id, firstName and lastName of the type User. The response is a JSON representation of the requested properties. GraphQL is designedt to return only data that was specifically asked for, never less and never more.

Relay Architecture

Relay follows the principal of having Presentational and Container Components. The container components shield away the request logic from the UI components. Every component has a RelayContainer counterpart in order to get the data it needs. The component declares its data requirements and the RelayContainer is responsible to provide this data.

Screen Shot 2016-02-22 at 15.28.08

Relay and Angular 2?

Relay, in the current release, is only available together with React. But the fundamental ideas and principles are in fact totally independent of React. At Small Improvements we have a very large Angular application, that’s why we wanted to use Relay with Angular 1 & 2. The solution Relay provides made us very curious and we wanted to test if Relay can keep its promises.

This motivated us to explore the possibilities of integrating Relay with Angular and Angular 2 and we did the following:

  1. Creating a modified version of Relay, that doesn’t depend on React, but instead can be used framework-independent: Generic-Relay
  2. Connecting Angular 2 and Relay by writing an annotation (@connectRelay), to pair Generic-Relay and Angular 2: Angular2-Relay

Mechanism of the @connectRelay annotation

Screen Shot 2016-02-22 at 15.31.39

The @connectRelay annotation adds two @Input Fields (relayProps and route) to an Angular 2 component. The ngOnChanges callback is used to inform the RelayContainer about any changes in regards to these two inputs. The RelayContainer binds the requested data to the component’s controller via the relayData property.

Using Relay with Angular 2

Now that you have a brief idea of the functional principle, let’s look at a simple code example. We want to wire an Angular 2 component, that displays a user, to Relay.

https://gist.github.com/sfroestl/010fce74dd7a0ddce968

After initializing the RelayContainer “UserAccountContainer”, we declare all data requirements in the fragment.

The @connectRelay annotation makes sure that our plain Angular 2 component is connected to the RelayContainer. After the magic of the annotation is initialized, Relay will ensure, that  we can access the requested data in the relayData binding.

Solving the Challenges

Challenge #1: How to define data requirements explicitly within our components?

Relay allows us to declare the data needs of a component within the RelayContainer’s fragment.

For example this is how we’d specify the data needed to display a conference in our conference planner example:

https://gist.github.com/sfroestl/e44eda915120c47831f4

 

Challenge #2: How to ensure a consistent state after changes?

Relay manages a central store, that acts as a single source of truth. By specifying in a declarative way what state changes are to be expected after an user interaction, Relay can handle the all side effects of this change. This includes updating all components (via the the RelayContainer). The definition of all side effects is specified in GraphQL.

Changes to the state (of server and client) are expressed with Mutations. A Mutation defines what changes will result of an action. We also specify all data that is sent to the server.

Here is a snippet of the Mutation to let a user attend a conference:

https://gist.github.com/sfroestl/3da1663ff4d779a99a63

In getFatQuery() we declare all data we expect to change and therefore has to be requested by Relay.

In getConfigs() we tell Relay the types of changes we want to perform. In this example we want to change some fields (“FIELDS_CHANGE”) and update the list of attending conferences (“RANGE_ADD”). (The complete Mutation can be found here)

Mutations are the most complex part of Relay and will probably be improved in the near future.

The takeaway here is that we declare all side effects of an action and let Relay take care of updating the server and its internal store. Relay also will update all components for which any data has changed.

Challenge #3: How to ensure that we don’t break the contract between client and server?

As mentioned before, GraphQL is statically typed. This feature enables us to verify at build time if the contract between client and server is upheld. The GraphQL server provides a schema file which is used to compile the client’s fragments. That way we ensure that neither client nor server violates the schema.

If we were to query for an invalid property, e.g. the rating property on the Conference type, our build would fail:

Screen Shot 2016-02-22 at 16.54.07.png

This way we avoid any unexpected runtime exceptions by checking the contract as early as possible.

Conclusion

Relay and GraphQL help us to simplify client-sever communication by:

  • Providing a declarative way to specify the data needs at component level
  • Managing a central store including a declarative way to specify changes/updates
  • Having a statically typed schema, which serves as contract between client and server

The most fun point: As demonstrated by the  Conference Planner example, it is possible to use Relay with Angular 2!

Finally we’d like to thank NG-NL for organizing a great conference and for having us.

We hope this writeup gives you a clear understanding of how Relay simplifies client-server communication and how powerful it is to use it with Angular 2. If you have any questions or feedback, feel free to contact use via Twitter (@AndiMarek & @SFroestl) or leave a comment here.

Resources

The slides for the talk

Angular2 Relay (including the Conference Planner example)

Generic-Relay

Relay Homepage

GraphQL Homepage

List of Relay/GraphQL resources (including different GraphQL implementation)

NG-NL

Appendix

The Conference Planner example and the @connectRelay Annotation are written in ES2015, because we use the normal Babel Relay Plugin to transform GraphQL fragments into JavaScript at build time. The same plugin also checks if we align with the server schema.

We don’t say that Relay is the answer to everything and we didn’t discuss when to use Relay. Of course Relay is no silver-bullet and as always there are tradeoffs to consider. Our point is to show that it is possible to use Relay with Angular 2 in a very nice way.

This is mainly because Relay itself is nearly generic out of the box, since it’s React dependencies are minimal. In fact the Generic-Relay project will hopefully be deprecated in the future, because Relay itself will get a core, that provides similar capabilities (See this Issue)