 My name's Gray, Gray Norton. I'm a technical lead and manager on the Polymer team. And while I've been doing web stuff for a long time, I'm actually pretty new to the team. I just joined in May right before Google I.O. And I'm super excited to be here at my first Polymer Summit. So today, we're going to be talking about Dataflow. This is a topic that we get asked a lot about, especially now that more developers are building not only apps, not only elements, but also apps with Polymer. Because Polymer basically provides a powerful set of low-level tools for managing data. But it's not very prescriptive about how you use them. It's really up to you to figure out how to structure your app. And the fact is that the way you move data around can have a big impact on the performance of your app, on how productive you are in writing your app, on how easy your code is to test and debug, and on how well things scale as your app and your team grow. So we're going to cover a lot of ground, some old and some new, some that Rob touched on just a little bit ago. But before we dive in, I thought it would be fun to kick off with a too long didn't watch slide. And I realized it might actually be a little bit risky, because I'm all that stands between you and lunch. So if you promise not to leave, I'll show you what's on it. So this slide is a little bit flip and intentionally oversimplified. I said a moment ago that Polymer isn't prescriptive, and that hasn't changed in the last 45 seconds. But I wanted to start off by giving some props, if you'll excuse the pun, to unidirectional data flow. Rob obviously is a big fan too. And frankly, this is a hugely influential pattern that really has come to dominate thinking in the front end world over the last couple of years, spurred largely by the React ecosystem. And there's a lot of good there. We think it's a great pattern for building apps with Polymer as well. And we're seeing a lot of Polymer app developers adopted of their own volition. And it's something that you can expect to see more explicit support from us for going forward. Okay, so with that spoiler out of the way, let's take a quick look at how we'll spend the next 20 minutes. First, I'm gonna spend some time recapping last year's thinking in Polymer talk with a focus on the aspects that are most relevant to data flow. And then we'll be into the meat of our agenda, reflecting on some key differences between building elements and building apps, exploring some trade-offs between unidirectional and bi-directional flow, and then touching again on a few of the changes that Rob mentioned around Polymer 2.0 in this area. So at last year's Polymer Summit, our friend and colleague, Kevin Schaaf, gave a great talk called Thinking in Polymer. And if you haven't seen it, I highly recommend that you go to YouTube and check it out. But this year, Kevin has more important business. He and his wife are expecting a baby girl any day now. So he stayed back in San Francisco and I sincerely hope, since it's like 4.45 in the morning that he's not watching, but in case he is, hey, Kevin, we miss you. But in any case, his talk provides a useful foundation for our discussion. So I'm gonna hit some of those highlights over the next few minutes. So for his talk, Kevin built this simple chat demo that you see up there. And there's no backend to it. He probably could have used Firebase and done really well, to be honest. And it does just enough to be interesting. So you can view threads, post messages, edit thread titles, and that's about it. But it was a good lens for examining polymer fundamentals and it's also a good sandbox for us to explore some data flow ideas. So we'll use it again this year. And with that, let's jump right in. So what does it mean to think in polymer? It all starts with components, of course. But components aren't unique to polymer. Virtually every modern approach to front-end development is based on components. What does make Polymer different from many other frameworks and libraries is that instead of bringing its own component model, it uses the one that the browser provides, the DOM. So the DOM really has all the pieces of a basic component model, if you think of it. It gives you composition, letting you assemble views and apps from basic building blocks. It has some basic facilities for moving data around in the form of attributes and properties and events. It has a native declarative format you may have heard of called HTML. And critically extensibility. So with web components, for the first time, developers have the ability to extend the semantics of DOM. And that is really the missing piece to the puzzle. So Kevin's app showcased a number of custom elements produced by the Polymer team, like the ones you see here. But more importantly, it demonstrated how you can build your own reusable elements like this input header. And how you can also compose your app from the ground up or from the top down, depending on how you wanna think about it, entirely using custom elements. So we've just made a pretty good pitch for the DOM and web components, but what's Polymer's role in this? Well, again, as Rob showed really clearly with his talk a few minutes ago, Polymer's purpose in life is to sprinkle a thin dusting of sugar over the web component goodness of the platform. Basically, our aim is to make it easier for you to build custom elements whether you're developing reusable components or entire apps. And to that end, Polymer provides a declarative format for easily composing custom elements. Here we're using it to build the input header for our chat view app. And the main point to note here is that this is just a native HTML template with some text annotations sprinkled in. And those text annotations manage the flow of properties between the host element and the elements within its shadow DOM. We'll take a closer look at those annotations in a couple of minutes. In the same vein, Polymer also provides some convenient helpers for defining your APIs and behaviors for your elements, like this simple format which you know and love for declaring properties. But the features that Polymer provides are really based around a certain way of thinking about the component model. And the way we think about it is in a pattern called the mediator pattern. So in this pattern, a custom element acts as a mediator and encapsulating the elements that it's built from, orchestrating interactions between them and exposing an API for other elements to use. Now, conceptually, you can think of a custom element in the mediator pattern as having a model that it uses to play its mediator role. And the model really, as inputs, has facets of its own API and it can pull a lot of levers that are exposed by the APIs of the elements that it encapsulates. And the model also incorporates whatever logic is necessary to make it all work together. So I'm introducing these concepts because we're going to use them now as a lens to think at a higher level about how we design data flow in our apps. And of course, much of a typical element's job as a mediator is about data flow. So without further ado, let's dig in. Now, as I mentioned, and if you've been developing with Polymer, you certainly know about Polymer's broad array of low-level features for managing data. And we'll touch on many of them before we're through, but our goal is not a comprehensive walkthrough of all the features. So for anything we don't cover, I can highly recommend both Arthur's thorough documentation on the site, as well as Rob's awesome series of data polycasts. All right, so I want to go back to the template for our input header and think about it with the mediator pattern in mind. So with that in mind, we can look at this template and it turns out that the entire model in this case, we just talked about the concept of a model for a mediator, the entire model is specified in this case entirely via the annotations in its template. There's no imperative code, there's no observers. This fully describes how this element works. Specifically, it uses a number of bindings to orchestrate the flow of data, as you can see here, and of course the square brackets here are one-way bindings, curly braces are two-way. Now, if we want to think critically about data flow, it's important to understand how Polymer's data features work and how they impact things like performance and code complexity. With that in mind, let's go ahead and visualize how data actually flows through the input header as specified by its bindings in its model. So input header contains a bunch of elements, but it turns out that only these three are directly involved in data flow. So we can simplify our diagram a bit. And you can see there, we have arrows representing all of the bindings in the model, including the one two-way binding between the title property and the value property of paper input. Now, suppose that the view containing our input header wants to specify what kind of icon to display. It sets the input header's icon property starting the flow of data, and as expected, the bindings propagate the provided value down to the related properties on the paper icon button. And in the next slide, it's really intended to look at what those bindings might cost us to see what's happening under the hood. Rob talked through this a few minutes ago, so I'm just gonna very quickly summarize. The data flows at runtime essentially because Polymer at load time has done a bunch of work for us, so based on annotations we provided in the template, Polymer automatically generated a setter and this is pseudocode, it's not exactly what's happening, but it gives you an idea of what happens when that property is set. So it's simple, but what about the two-way binding? We can take a similar look at that. In this case, when someone types, the property value, the value property, rather, of paper input changes, and that triggers the upward flow of data up to the title property of input header. And again, it's a very similar mechanism here. Using the information about our mediators model that we provided in the template annotations, we give Polymer enough information to do a lot of work under the hood for us. In this case, it sets up an event listener to listen for the value changed event, and that works for us because paper input, meanwhile, via a very similar mechanism, had an automatic event dispatch added. So as was the case with our one-way bindings, this two-way binding is lightweight and performant because it's based on native platform features, non-bubbling events and ordinary event listeners. So in a nutshell, this is really basically how data flow works in Polymer, and we refer to the underlying mechanism as property effects because basically it means that setting any given property deterministically causes a particular set of effects to occur. And when one property's effect causes another property to be set, the chain reaction that follows efficiently moves data from one place to another. And all the examples we just looked at were based on bindings. The exact same system really underlies our other data features, like computed properties and observers. So I just wanna take a quick pause here because we're about to zoom out and now look at more app-level data flow. But before we do, I just wanna make a point about two-way bindings because after all, the whole world, including several people who've just gotten up on stage are talking in glowing terms about unidirectional data flow and two-way bindings are inherently bi-directional, right? So does that mean they're evil? Let's go ahead and take one more look at our input header diagram. And to cut to the chase, I can't think of any good reason not to use two-way bindings in a case like this. And there's several reasons. So first of all, the design of the element explicitly requires that the title of the input header and the value of paper input be in sync at all times and the model doesn't do anything else with that value. So having them bound is exactly the behavior we want. The other thing to note is that the values in question are all scalar values. So we don't need to worry about unintentionally mutating something out from under someone who may have passed as a value. And at the end of the day, if we weren't using a two-way binding, we'd essentially need to code the same logic ourselves by hand. So as we move upward to look at app-level data flow, we may find that there are cases where we choose not to use two-way bindings for other reasons, but in this case, it's a perfectly good idea. All right then, onward and upward. Let's take a look at data flow from the app perspective using Kevin's chat view app as our example. So the original app that Kevin built for last year employs bi-directional data flow. But in preparation for this talk, I basically built an alternative version, actually a few alternative versions using unidirectional flow. And in a couple of minutes, we'll compare and contrast the two approaches. But in order to evaluate them, first we need to understand the app's model as a whole. So whereas reusable elements tend to have relatively simple self-contained models, an app's model more often consists of nested structured data, slices of which are often shared across views. And that's true even in the case of this simple app. So here's a reasonable structured representation of the chat view app's data. It has a list of threads, and each thread in turn has an ID, a title, a list of messages, each message has a user and the text of the message. And we also have an object representing the user. We know the user's name, and then we have an object that we use to keep track of the last message they've looked at in every thread so that we can show the unread thread count and the little icons next to the unread threads in the list. And then lastly, we have a bit of application state. The currently selected thread is here as well. So in addition to the data, we also need to think about what actions the user can take. In this case, as we discussed, very simple. You can select a thread, post a message, or set a thread title. But there's sort of a piece in between, or a related piece, and that's basically the set of mutations that we might make to our application's model as a result of events that are either driven by the user or by the application itself. And this is actually really the meat when we're trying to decide how to structure our app. This is the meat of the things that we need to consider because where we choose to make these mutations is all about what kind of data flow we have. So armed with this top-down model of our app, we can now evaluate our options for flowing data. And until now, we've been framing this choice in terms of directionality, but as I suggested just a second ago, I actually find it easier to think about in terms of distribution of responsibility. So one option is to distribute responsibility for our model fairly evenly between the views of our app, with each one taking ownership of certain aspects. And this tends to result in bi-directional data flow. The other option is to concentrate responsibility in some central place, with individual view models then becoming extremely simple. And this tends to produce unidirectional flow. So here's a simple schematic diagram, representing the distributed version of ChatView, the one that was built last year. And it shows how we distributed the responsibilities between the various components. So most of the choices here were obvious. The mutations of the model were basically assigned to the piece of view that was responsible for displaying the related data. There's really only one piece of this, the question of where to update the last red thread that could have gone in either place. But the interesting thing to note here is that we have our ChatThreadView updating the last red message and we have the ChatThreadList sort of calculating the derived data that's based on that. And so we have sort of an implicit coupling, if you will, between these two views through the same piece of structured data that they're mutating. And that leads to some complexity. So here's the corresponding diagram for the centralized version. And you can see our model there is much thicker in ChatView. And the other thing to note here is right now I'm showing this centralized view as living inside of the ChatView view. I also actually built a separate version of this using the Redux library in which the store sits outside the view altogether and there's a Polymer Redux library that binds Redux to Polymer. So don't be worried if you don't like the look of a big chunk of logic sitting inside of view element. But in any case, you can see it's much simpler because effectively all of our mutations are happening at the same place. So here's a slightly more complex view. And of course, in building the app, Kevin used a lot of bindings and so it was very easy, very declarative. It's actually a very elegant code to do the distributed version of the app that he built last year. But the interesting thing is that underlying it, you still get a fairly complex model. And so that complexity is there even though it's somewhat hidden from the niceties of Polymer's binding syntax. And here's the corresponding diagram for the unidirectional version. Just simple one-way bindings. Okay, so now we've looked at it sort of schematically. I just wanna touch on a few, we're not gonna look in detail at the code, but we're looking at the bi-directional version, the distributed version. And one, a few things to note. So whenever we're making mutations to our structured data, we end up using the Polymer-provided non-standard ways of doing that. So here instead of calling array push, we're calling this.push. And that's some notifications of the changes we make in one view propagate to another. Similarly, we're using this.set here. And then here is a complex observer that is dependent on a number of some deep paths to basically observe changes. And again, this is some complexity here because what it means is that in a view where you're declaring one of these observers, you need to understand the structure of some fairly deeply nested data. So basically, again, you have sort of a coupling that maybe feels a little uncomfortable. I won't talk about array selector, but it's another sort of abstraction that comes into play as a result of the fact that we are distributing our mutations across views. So let's look at some highlights of the unidirectional approach. So this is one of the views in the app. And the main thing I want you to notice here is that the bindings are all purely one way. So we don't have any two-way bindings. Another thing to note, since we're not mutating any data inside the model of the views, we need some other way, some other mechanism to signal that an action has taken place and a mutation needs to occur. And so here we're just using Palmer 1.0 event fire method. I also experimented with a more react style of actually passing functions down as properties and lo and behold, that works too. But you need something like this to implement unidirectional flow. And then here's the actual app template. And again, purely one-way bindings. There are no two-way bindings anywhere in this app at all. And you can see it's listening for those events that are being fired by the child views. So the other interesting thing to note is because the view elements became so thin in terms of their model, the version that I ended up working with the redux version of the app was almost identical to the one in the pure Polymer unidirectional version I did, which was another nice advantage. So the one piece we haven't shown is how we actually respond to the events in our main app. In other words, what does the logic look like in that centralized model? And it doesn't look, again, Kevin's version almost purely declarative. Everything's in templates. There's a few sort of one-line functions. Here we actually have some longer bits of logic. And effectively what we're doing is we're listening for the specific events like setting the thread title. You can see the last line of this we're calling it this.underscore update function. And it turns out that all of the event handlers call that at the end. And then we just have one centralized update function where we take our now updated data structure and we use a sort of an immutable approach. You can see that there's a call here to object assign. So if we, since we know we've changed the data, we ensure that it bypasses or that it doesn't get tripped up by Polymers dirty checking by creating a new object. And we do the same thing with the messages array by calling slice. And of course in Polymer 2.0 with by default skipping the dirty check some of these things wouldn't be necessary although they still might be performance optimizations. In any case, a little more code in the centralized unidirectional way but also a lot looser coupling between the views and the containing app. And I think ultimately because we have a lot of code here that's really easy to look at in the debugger for example. I don't know if any of you have run into cases where something's gone wrong with your bindings and you're not sure why they can be pretty difficult to debug and code like this is very easy. So again, it's not a case where it's clear slam dunk. There's a lot of really nice things about the distributed bidirectional version but there are definitely some good advantages that I think would become greater as your app grows and your team grows to the unidirectional approach. So again, I just wanted to point out the Polymer Redux Library built by Christopher Turner. If you're interested at all in experimenting with Polymer plus Redux, I recommend it. It's very small, does its thing well. And then again, just a reminder of some of the relevant changes in Polymer 2.0. I mentioned the big one that robbed it as well which is that by default it's no longer doing a dirty check on objects and arrays. The other one that he also mentioned but not quite in this context is I think an interesting thing to speculate on going forward because if you do end up writing an application or a code base that depends entirely on sort of the centralized unidirectional model then there's actually a fairly significant percentage of the code in Polymer's data layer that technically you don't need in that scenario. And so if you combine that fact with the sort of a la carte model of Polymer 2.0 you could imagine defining your own element base class that was a lot lighter weight. And again, these were the things that we were optimizing for and the main thing that I, the main takeaway here is you need to understand what your team looks like, what your app looks like and think about some of the trade-offs that we looked at here today when making the choice for how you want to structure data in your app. At that URL I will upload my experiments. Right now it is a placeholder read me but within the next couple of days I'll put it up. So if you wanna go and watch that repo you'll get notifications and it might be interesting to play with the concepts I certainly had fun. So with that enjoy your lunch. Enjoy the rest of day one and we'll see you around.