Video details

A Philosophy for Designing Components with Composition | ng-conf: Hardwired

Angular
06.22.2020
English

Jeremy Elbourn

ng-conf is a three-day Angular conference focused on delivering the highest quality training in the Angular JavaScript framework. 1500+ developers from across the globe converge on Salt Lake City, UT every year to attend talks and workshops by the Angular team and community experts.
Follow us on twitter https://twitter.com/ngconf Official Website: https://www.ng-conf.org/

Transcript

All right. So really quick before I start. If you are hearing this on the livestream, go ahead and say pizza in the chat. So I know what the lag is between me saying something and people are hearing it. And so I'm going to go ahead and get started. So welcome to a philosophy for designing components with composition. I want to give a very quick note before I start. Normally I give talks at Engy, called here to talk about updates in angular material and angular CDK. And this talk is a little bit separate from that. We are working on plenty of stuff for a given material and angular CDK. It's just I wanted to talk a little bit more about my underlying philosophy right now. All right. And now I'm seeing all those pieces in the chat. OK. So now I know it's about 15 seconds. So if you don't know me, my name is Jeremy Oborne. I'm a member of the English team and I'm the T.L. for the Angular Components Team, which works on Angular, CDK and angular material. And I wanted to give this talk in order to share some of the philosophy that I've developed over the years when it comes to building UI components. And a lot of this talk is going to be focused on the idea of a common reuseable or library components. But I've observed that every application ends up meaning the build at least some reusable components. And so I think this is going to be useful for everyone. And all of my examples here use Angular, my experiences with Angular. But I think the high level concepts here could apply to really any system. So I want to start off by talking about starting your designs with the interaction pattern. And what do I mean by interaction pattern? Well, let's look at an example. How many of you, upon seeing this little bit of UI knows how it work without really needing any instruction? Hopefully it's most of you. You are at a tech conference and should be a very technologically advanced people. But even the least tech savvy person, you know, probably knows how to use this at this point. We know we can click on this trigger. We'll get some options. We select one and then selected this dropdown interaction pattern has been ingrained in our collective consciousness over the last 50 years of computing. So I buy interaction pattern. I mean, it's some common, well understood interaction that a UI commonly contains are commonly recurs in your eyes across a wide variety of interfaces. And it's not just this dropdown either. There are plenty of these interaction patterns that, you know, what's going on. This this slider volume slider, very intuitive. Pretty much everybody knows how to use that. We have something again, like a tabs. You see this and your computer adjusted brains know how to use this. And we are every day fundamentally composing our applications from these mostly well understood interaction patterns. But is there anything out there that formalizes what these patterns are and how the to think about these patterns as we go into building components and building applications? And it just so happens that there is a really convenient standardized collection of these interaction patterns. And that is the W a I aria specification web accessibility initiative. Accessible rich Internet applications or commonly just Horia for short. This is a specification that complements the HDMI five spec and it captures the idea of augmenting H normal elements with additional semantic information that's used by assistive technology such as screenwriters. There are these rules are generally arm. The RSPCA is generally thought of really in terms of accessibility. But I think that they serve a as a generally useful set of building blocks for all interaction design. And so the way the Orioles park is set up is that you can add this role attribute to an element to capture the interaction pattern that's being used by the element. This role doesn't change any behavior about the component, but it communicates to any assistive technology about how the component should work. Capturing the combination of structure, states and interactions. And there are about 70 concrete total area roles and I would judge roughly half of them are relevant to UI component development. You are probably very familiar with the very straightforward rules, the button checkbox, radio button link, text box and so on. These are captured by native browser elements and are the lowest level building block for everything that we built. But there are more complex roles as well. These are really the the main building blocks for the complex interactions that you're seeing in most applications. And really, if you just knew these six interactions, you were in really good shape. These are ListBox combo box grid dialogue and you and tablets. And so in order to kind of show this off. We're gonna play a little game called Name That Role. Normally, this would be an audience participation section of the talk, but because of our circumstances and the lag between the video and the chat, I'm just going to do the audience partition patient myself. And so we're going to look at an interaction pattern and try to. Jeremy, you want me to do it used here. So first, here we have this contact selection. Jeremy, do you want me to be your audience member? Oh, yeah. Frosti, that'd be good. Hi, I'm good. So what what do you what interaction pattern things here. Quick go. I don't know of some great audience. The bad ones. Sorry. I don't know. So all them the good one blocks checkbox. There's a checkbox. Interactive pattern. Yeah. So you might think that. But this is really a standard listbox where you're selecting one or more values from a list of options. All right. What about this here where we have some chips that we're using as filters. Multi select chip selector pill bottle. I don't know. I don't know. It's called. All right. So, yeah, you know, those were some good ideas. So checkboxes something you might think about here or toggle buttons because you're toggling things on and off. But the surprising thing here is that ListBox is actually really appropriate here because you're selecting, again, values from a number of options. And so even though this looks very visually different from this previous example where this looks more like a traditionalist box, these are actually the same interaction pattern. Here we have like a typical Tab's. Aarakshan pattern, and you would commonly think this is like Kabbalist and tab and tab Hadel as a standard. Are your rules for that. But if these tabs are changing the browser, your URL, then you can also express this as a nav element with inkers. Right. So even though the visual is the same, you can have multiple different interaction patterns that depend on how this is affecting the browser, your URL. Similarly for for this interaction right here. There are cases where you could do something like this with ListBox. There are cases where you can use something like menu for this or combo box. There are a lot of different options that could be applied to the same visual appearance. And it can get really complex, too. You can end up having a more sophisticated component like this. And I'm sure many enterprise applications that people here have worked on. You have had to build components like this. You've had Mock's for things like this. And because there's so much going on, it can be difficult to figure out how users should interact with this with a keyboard, shortcuts can be and so on. And so the thing I really want to emphasize with all of this is that it's not always immediately obvious which pattern maps onto a component. And that's why when you're going into your design process and you're going into building something, you should always start with your set of interaction patterns. This is going to lead to the most consistent, predictable experiences in your application because you're going to be using these standardized, well understood patterns and composing them together into a larger experience. And this approach also has the benefit of making your design accessibility first. So when you start with the area interaction pattern, you're going to have a much easier time building interfaces that everyone can use. And that's kind of an example here. We can look at that complex component. I showed us a moment ago and you can see we can decompose this visual into a set of interaction patterns that make sense where we have in this example a button that opens a dialogue and that dialogue can contain other buttons, text inputs and listbox along with, again, more buttons. And when you decompose this way at the start, you are making your component easier to reason about, easier to use. And you're going to have experiences that Spaull instantly connect with. And so that is talking about how to go, about thinking about the behavior and the logic of your code. But obviously, styles are a big part of component building as well. And the main thing I've learned about styles in terms of building common components is that style customization is inevitable. And as you come to terms with this, you're going to go through the five stages of people overriding your CSX. So starting off with denial, nobody is allowed to override my CSX. They'll move into anger. People are overriding my CSX bargaining. How can I stop you from overriding my CSX sadness often? And applications are house of cards that will come crumbling down at any moment. And finally, acceptance. People need to override my CSX. But that doesn't mean you're just going to write your CSX and hope for the best. There are some practical scoping prochnik coping strategies. You can have in order to give people the kinds of customization they need while also not making your components completely impossible to maintain. So my favorite approach for dealing with this is the idea of parameter ised SAS Markson's. So this is where you are taking the styles for your component and you're capturing them inside of a SAS mix in that is accepting some configuration block that includes all of the values that can control how your component renders. And so this is an example. If you're having a component called the Swiffer picker, you are something in this configuration option that has a size option and a primary color option. And we can pull the values out of that configuration and apply them to RC as us. And when someone uses the component component, it looks like this where they're just including your mix in and passing in those parameters. And what I really like is the idea of extending this concept to then further decompose a component styles into you configurable categories. And so you could imagine these categories as being something like base, which would be your components based on. And then color typography, animations and angular material, we're working on adding density as an option like this. And this is really great because it lets you compose styles at the application level in a really granular way. And because these are SAS mixes, you can take any one of these and scoop it to a particular CSF selector such as a class and have it only apply within that section of your application, which means that you will have much more control over the amount of CFS you are generating. And this is the approach that we take today with angular material. This does make your components. It requires a little bit more effort to build because you have to put in the time to separate your styles in two different pieces. But I find that overall it leads to more maintainable experiences. You may be wondering, what about CFS variables as part of this? Right. Why not use those as part in order to deal with this problem? And since it's four variables are a part of a solution to this problems. They provide a native way to create clearly defined customization points. However, they are restricted to two single values, which requires that component authors encode every single customizable property into a variable. And this can potentially get cumbersome and difficult to maintain. It might also have performance issues if you really, really care about your render speed and your poorly filling. So the politics here can get extensive and obviously osseous as variables are not supported in older browsers ie eleven in back. Another technology that is kind of newer here is a could be thought about as a partial solution to this problem in CSF parts. And this is an emerging standard that allows people to customize certain parts that are clearly defined by a component author in a shadow dom scenario. And again, this is a future partial solution to this problem. It's not support in an older browsers like IE eleven, and it's not yet supported in Safari a lot. And this will solve some problems. Does have a lot of the same problems as traditional CFS overwriting in that people make assumptions about what styles are present and people may override styles in a way that you don't anticipate. So that is talking about styles. We talked a bit about how to go, about thinking about composing behavior and logic in your component. On one last area of composition I want to talk about is API design and in particular the idea of letting the user of your component compose parts. And here, by user, I mean the developer or the engineer that's going to be using the component. And to do this, we're gonna play another quick game that I think, Frosti, if you're there, you can you can participate in. All right. So I'm ready to name ready a component. And just real quick, name as many features as you can for this component. If you're watching on the stream, trying not to overwhelm the chat with this. So Frosti, data table filters, sort page selectors, call them selectors, call them hiders. Yeah, they're good. That's great. That's great. These are some of the things you probably said. Something pagination filtering selection, sticky headers and column drag and dropping row is virtual scrolling in line at it, bulk get it and on and on. Data's evils are very complex and there are whole huge B.K. implementations out there, big area of work. And so if we're going to go build a component that has all of these features, then we're gonna just really need one big massive API surface. We're gonna have a component that looks like this, where it's going to be taking in its data. It's going to have sort columns and sort directions and whether or not you're doing multiple sorting and or what people can do with the sword and the pagination options and the sticky options. And, boy, that's that's a really, really big API surface. And when we build all of these features into one API surface, especially if we talk about things like client versus service, IDE data manipulation, it gets really complicated, especially as you're talking about all the permutations of these different options. And with this approach as a consumer, it's also difficult to do anything in the component that's not directly supported by that component. And that means that when your UX designer comes to you with their revolutionary, innovative new UI for pagination, then you're stuck because you don't have a way to reach in the component internals and change that. So what you can do about the. This is go about designing your UI components as composable parts that the person using that component is going to compose together. And as an example here in angular material, we have this mat table component that, you know, standard table renders rows of cells. We also have a pageant later for moving between pages of data. We have a sorehead for sorting and going into this. We made the decision that these components don't know about each other at all. They're absolutely decoupled. Instead, you set up your application so that you have a single data source that is the source of truth for all of these things in data flows from the data source independently into the table. The pageant aigner and the sorehead. And you can extend this model to also include filtering selection, state keyboard navigation and so on without any of these pieces necessarily knowing about each other. And it's not just the table in angular material. We take this approach for other components as well. You can see autocomplete as a composition of text input and an options panel, a menu as a trigger button and an options panel. And the day picker is a text input, a trigger button and a calendar panel in code. This could look something like this where you have a button. And this is just a regular standalone button. And you're adding some directive here in this example that is just pointing to the menu. Menu panel that gets opened as part of this. And this is really nice because it provides some flexibility. And it's also kind of easy to reason about if you want to style the button, you style the button. If you want to style the menu panel, you style the menu panel. If you want to swap out two different menu panel, you can swap out two different menu panel. The benefits here is that you are your coding is a much better following the idea of single responsibility because each part gets to focus on just doing one job. Again, you get more flexibility because you can change out parts. You can customize parts independently. And one thing I really like about this approach is that it surfaces native elements like you saw the button element in that example. In our table, we use native table elements in our Deepika, you know, native text fields, the. And this is really great because it reinforces the idea that people should be using native elements for accessibility and not reinventing things that already exist in the browser. There are some cons to this approach, too, though. Never believe somebody that only has positive things to tell you about something so you can end up with much more verbose API eyes when using this approach and you can end up with, in total, larger API surfaces. However, those API surfaces tend to be easier to reason about because they're split up into smaller, easier to understand sections. So concluding and wrapping all of this up, the fundamental idea that I want to get across in this talk across all of these concepts of starting with interaction patterns and decomposing styles into categories and allowing users to compose API is is that as you're going into building components focused on decomposing what you can and be very deliberate, bit deliberate about recomposition in particular, where that composition happens as you're going forward and writing design documents for new components. Sit down and write out the composition of patterns and area roles that are going to happen in your components. Write down the different types of style categories that you're going to deal with and how people might customize them. When you start off thinking about these things from the beginning, they're a lot easier to deal with. Know that customization is inevitable. So build for flexibility. And in that flexibility as you're designing how much flexibility to offer. Know that all design choices have tradeoffs, right. As you introduce flexibility, you are trading off that in some time. In some cases for verbosity or large or API surfaces. And so it's always up to the application to decide what's best for them. And this is the fundamental nature of software engineering. That's all I have to talk about today. Thank you again for joining. You can find more about the material in English TDK on Get Home on the angular components repo material about angular IO and you can find a link to these slides at Chieko Slash and G Sluff. Com twenty dash cohosts.