modern web app ui design
sebastian frost:welcome, everybody. it's not working. [laughs] so hello, everybody. first of all, thankyou all for coming. it's great to be here, and it'san awesome conference so far. and we're reallyhappy to be here. this is gernot. gernot hoflechner: and thisis my handsome and colleague, sebastian.
sebastian frost: we are bothorganizing the angular meet up in berlin. so if you are ever in berlin,feel free to check by. and also, if you wantto talk in berlin, feel free to reach out to us. we are glad and happyto have speakers here. today we're goingto talk about design patterns for largeangular applications that actually scale.
and we both work forsmall improvements and a large angular application. small improvements is a tool forfeedback and performance review goal planning, andcontinuous feedback. our grew heavily overthe last three years and especially, we have powerfuladministrative screens that make our code base really huge. so let me tell you our story. so in the beginning therewas void and darkness.
and then we started to addcode-- little bit of code-- and we added moreand more features, and our app kept growing. so in the beginningit was pretty easy to add functionality. our code base grew, and so on. and if we will nowlook at this graph-- this really scientificgraph, by the way-- we could say that our effortfor adding new features
increased, while ourscalability kind of decreased. especially when we wereadding more features, changing features, businessrequirements change, and so on. but i guess that'snot big new to you, because we insoftware development-- that's a problemwe have all day. so another thing isthat our team also grew. so this also has a significantimpact on the code base because suddenlypeople add code,
you don't understand anymore,and they understand your code, and so on. so this is alsoincreasing effort of adding new features whiledecreasing our scalability. so in mid 2014,our scalability was kind of tending to thebottom of this slide. and we were about to doubleour team size in one month. and that's exactly the pointwhere we asked ourselves, this is not going tobe any better, right?
and we committedourselves to the goal of finding the holy grail ofscaling fronted applications. so we did step back andanalyze our code base. it wasn't that bad at all, sincewe followed most of the best practices and we alsolearned from our faults, which helped us a lot. we managed to get out ofthe bad state of having a scope soup, which everyangular application kind of starts with.
if you're not familiarwith scope soup, and so on, feelfree to check out a talk by [inaudible]who also gave a lightning talk yesterday. and he was at ourmeet up in berlin, and he basicallyexplains how to get out of the scope soup to a reallycomponent based architecture. so this component basedarchitecture we had already, and i'm going to askgernot, now, to explain this
to us a little bit more. gernot hoflechner: thank you. so let's define a littlebit what components are. components takeinputs and outputs. they have clearlydefined boundaries through this interface. they up basically shieldedaway from the outside world and the rest of the application. because really, they take allthat data in and they have,
probably, callbacksthat then communicate to the outside world. is a good approach, buthow does this actually look in a real world application? you will probably havequite a big component tree at some point. and there are two mainquestions that arise here-- how to share functionalitybetween these components, and how to give thesecomponents some kind of context.
how do they know about therest of the application? let's look at sharingfunctionality first. in this scope tree, the two,blue highlighted components will need to do kindof the same thing. let's imagine some kindof made up ui for this. on the left side, we havesome kind of message list which is renderingmessage items. and within thismessage item, an author is displayed, for example.
and by clicking onthis author name, you also want totrigger a filter, so that the messagelist is really triggering onlymessages that were written by this one author. on the right sideof your screen, you might have adistinct filter panel. and within thisfilter panel, there's also this kind of authorfilter waiting to trigger
a filtering of the list. if we look at this inthe component tree again, we see that they arein different parts of the component tree. they are in some kindof sibling state. and the question now, really,how can they share a code? they can't, really. the logic needsto stream upwards. you cannot encapsulate thefilter logic in one of these
components because the data thatis needed for this filtering operation is reallyowned, in this case, by the control already. so all logic streams upwards. all logic streams toa common ancestor, which means that you end upwith a pretty fat controller. i also mentioned the problemabout really sharing context. for this we will look at anothersub tree in this component tree.
so our goal is really to deletea message of this list now. at the bottom of the subtree, you see a delete button. it's just some kind ofgeneric delete button that contains specificstyling and similar things. it's a button we alsowant to use elsewhere in the application, in partsthat are not really connected to messages. if we now want to give thisdelete button the functionality to delete a message, weneed to pass this function
through the wholecomponent tree. the owner of this functionis, again, the controller. it needs to pass itto the message list, to the message item,to the delete button, so that the deletebutton can actually trigger the user interaction. you click something, amessage gets deleted. the delete message function, inthis case, is giving context. the function itselfis encapsulating
all the context ofthis particular part of your application. within the deletemessage function you will have referencesto other messages, for example, orto any other side effects this might trigger. for example, if we takethe filtering example-- after deleting a message,some kind of free filtering might have to happen.
it's really thisone function that provides the context thatneeds to be passed down through the whole tree. if we look at this in code,we see that this is already a little problematic. this might be thecontroller template for this kind of miniapplication we have here. and we see that themessage list needs to get a reference to thison-delete message-- function,
sorry. the message list itselfneeds to pass this function to the message item. we, again, have an on-deletecallback defined here. when, finally, themessage item can pass it on to the delete button. we see that allthese components had to define a specific interfaceto handle this delete message function.
even if, like themessage list, they were not really interestedin the operation themselves. the only reason for themto define this interface was so they can pass it on. if a component tree getslarger, this is not necessarily going to get easier. the problem is that we are kindof violating the input output rule here. we are pollutingthese components
by giving them things they arenot directly interested in. this might leadto a problem when you want to move functionalityaround, for example. let's say this blue componenthas some kind of functionality, and we now want the greencomponent to do the same thing. we want really tomove functionality from one component to the other. you would think it'san operation that takes you to work on twofiles-- two components--
but it's not the case. you have to touch all of yourcomponent tree, basically. you touch at least 10different components, and this is justnot really perfect. such isolated componentsare great in theory. you want to have to haveinputs for them and outputs, but nothing more. the problem is that thiscomponent driven approach doesn't really answer howto connect these components
with each other. sebastian will nowshow you how we tackled this problemin our application, and small improvements. sebastian frost: yes,so thank you, gernot. and when you havea problem, it's always good to startwith asking a question. so let's all ask ourselves,what do we want actually? one of the problems we saw isthat the dependence of our view
components to each other is kindof decreasing our availability to add functionality todifferent components. we have this commonancestor problem, and so on. so i would say onething we really want is to be independentof the dom structure, so that we are able to changeand reuse our ui components as easily as we want. and that's exactly the firstthing that we want to do. second is that we want toseparate our ui components
from our business logic. that means that we have adistinguished place where the business logiclives, and we can easily change the businesslogic without having to touch the ui layout. for instance,imagine your back end decides to send you pushnotification suddenly. and you're like,ok, wow, now i have to extend all my components,they have to update themselves.
how do i do that? and it's all spread outin my component tree. if we centralize thisin a specific place, we can just simply addthis push notification to our business logic. and if we look at that, thecomponent driven approach was almost reachingthat goal for us. so the missing step is reallyto get rid of the dependencies. and we achieved that bysplitting the components
into one component that needsto interact with the business layout-- or the business logic--and the other component that doesn't. and that's why we will nowtalk about smart and dumb components. smart and dumb components is aconcept recently also mentioned by dan abramoff in a blog post. and let's define what aresmart and dumb components, and what are thedifferent responsibilities
between these two. we could also callthat the levels of awareness to our businesslogic these components have. first, the dumb component. the dumb component isreally just getting only our ui specific actions. so it's, for instance,a button or a form. we also have boundinputs and bound outputs, so a really clearinterface to that component.
it keeps and mutatesonly its internal state, so we can place it anywherein the application. it has no knowledgeof the outside world, so it's really reusable. and when you thinkabout angular 1 it basically also means thatwe have no service interaction. so we only haveinputs and outputs, bound methods, and bindedattributes, basically. it might inject somehelpers, but nothing else.
and it receives, of course,callbacks and bindings via properties. next, the smartcomponent is wrapping one or more dumb components,and connects them with our business logic. so its main purposeis to separate the dumb components, or ourui, from our business layer. yeah, there's thebusiness layer. that is achieved by providingcontext to the dumb component.
so it will get the contextout of the business logic and pass it down toour dumb components. it also provides the callbacksfor the dumb components to interact withthe business logic. so for instance,taking user interaction and giving them to ourbusiness logic layer. speaking in angular1 world, we would say that inject servicesand gets the context out of services, and also causeservices to pass actions.
let's see how the smartcomponent tree would look. we basically have couplesof smart and dumb components everywhere placed inour ui, and they are now easily exchangeable. we could just movearound the logic. and if we need to addfunctionality-- for instance, the delete gernotmentioned-- we would just add it to the smart component,include a dumb component, wire it together,and be done with it.
so let's take a lookat our example app. in this case, we want to filterour messages by an author. we have, on the right, afilter panel, and on the left, our messages list. and a user wants to click eitherin the message on the author to filter the message list,or on the filter panel. how does this look in code? first, let's take a lookat the message list. it's basically just repeatingover a list of messages.
and as you see, the messageitem-- or smart component-- is receiving the context fromthis ng-repeat in this case. how does the message item smartcomponent template look like? we have all thedumb components that are displaying our message. in this case, we have themessage content dumb component, we have a delete button, andwe have our property filter. and the green thing is thatwe pass-- the green thing, yeah-- we pass the context.
as you see, thisgreen arrow there-- and we have the property filterthat has an on filter callback, for instance. so this is actuallythe interface that we implement fromour smart component. let's look at the javascriptcode for a second. on the top side we have bindto controller-- our message context to boundto our controller. then we have ourcontroller definition,
which injects someservices which i will talk about in a minute. so let's just go down a bit. and there, actually,you find the interface to our dumb componentsand the business logic. we are providing filter messagesmethod to our view model. vm stands for view model here. and we are invoking justa message filter service to filter some message.
further down, you have thedelete message interface, which will, in this case,trigger a delete message action. so here we see hereclearly the interface between smart components, dumbcomponents and the business layer. so next, how do we actuallyconnect the smart components with the business logic? i promised you to speakabout the injections we
had in our controller,and let's trace that from a more practical approach. so we want to execute, now, thisdelete action that gernot just said. so our user will click onthe delete button here. it will invoke our messageitem smart component, and our messagesmart component-- as you saw in the examplejust a couple of seconds ago-- will invoke ourdelete message action.
the delete messageaction is basically the prime thing thatcouples our business logic. so it will containall side effects. for instance, it willhandle a request to the api and say, hey, i wantto delete this message. then we wait for theresponse of the server. and if this succeeds, wewant to do something else. what else do we want to do? we want to update ourstate-- our application
state-- in this case, thata message is now deleted. it's fairly simple--straightforward. our message delete actionhas a method, delete message, it couples all the sideeffects-- in this case, just using the messageresource to delete something. and then we see we wantto notify our state layer. this state layer, then, doesall this state manipulations. it will be responsible to bundleall state mutations there. and our state layer willnotify, or throw an event
and say, hey the changehas-- something has happened to my data, to my state. and this will look, in code,pretty straightforward as well. we will delete ourinternal-- the message from our internalstate representation. in this case, it's just a map. id best hashmap where we'lldelete the message from. and then we triggerthe delete event. and what does thisevent help us to do?
it basically helps us todecentralize the state updates. that means smart componentscan now listen to this event, and implement customhandler logic. that way we don't have tohave a controller, which is executing imperatively. update this one,update that one, reset the filter, and so on. so it's really easy toadd functionality, too, because the smart componentsand can implement that custom
handler logic. so as you see here, we listen onthe events in our message list or in our filter panel. so we saw that now fromreally practical approach. and will ask gernotnow on stage again, to formalize this concept a bitmore so we can maybe reapply it to some different use cases. gernot hoflechner:so if we look at this from our conceptionalperspective,
this is, of course, auni-directional data flow system. you might all know this already. it's heavily inspiredby what facebook is doing right now withthe flow pattern-- sorry, flux pattern theywere proposing. the interesting thingis that these two layers we display here--one state layer that is responsible to holdyour application state
and to trigger changeevents, and this action layer can totally be the[inaudible] application. how you implement this isup for grabs, i would say. there are several options. what sebastian just showedyou was an approach to used several store-likecustom services. it's just one approach. it might not evenbe the best one. i would probably reflect itin our own app right now,
but it doesn't really matter. important is that youhave these two parts. you have one layer thatis triggering the change notifications, and anotherthat is triggering that a stage change actually happens. you might implement a singleglobalized store for this. like, even usingthe reducer pattern, like in the verypopular redux framework. but also, a router-basedsolution could be thought of.
an action would just triggera state change-- a url change. the state is in fact then heldand encapsulated in the url, and listeners could then go onand listen to this url change and do whatever they need to do. yesterday we heard atalk about reactive streams, today about meteor. this is somethingthey could all live on this left sideof this diagram. ideally, you shieldthis away from the rest
because then, you can switchtechnologies here more easily, and always have a clean andstable interface running. you see, we haven't really seenany kind of view here so far. and this is also great. you can bundle yourbusiness logic like that, in a way that is completelyindependent of your view. you can test it inisolation, and separate the concerns much better. the view is, in fact, hooked inby smart and dumb components,
as sebastian said. the interesting thing hereis that the smart components define how this intention works. you have a clearlydefined place on where to wire your view layer to therest of your business logic. this might be done in angular 1. the interesting thing is tofollow these patterns here, because when youfollow these patterns you might be in a positionto switch technologies
on the view layer aswell at some point, and use react or angular 2. it's not for free, of course. you have to do something. you have to work hardto really migrate the application like that. but when you followsuch a concept, you at least can do it insome kind of method-- word missing-- systematic way.
[laughter] because you just know what toexpect from your components. the dumb components take inputsoutputs, the smart components wired into the restof your application. you can followmethods and patterns to really do your refactoringand your technological change, even. let's start tosummarize this a bit, because we're almostrunning out of time.
we have here fourdifferent concepts at hand. a state layer, which holdsyour application state and is responsible forfiring change notifications to the rest of the system. smart components that wirebusiness and view layer together. they are really the ones thatprovide context for your view-- for your dumb components, whojust take inputs and outputs, and represent theactual user interface.
where a user interactswith your application. they are pure andhighly reusable. and actions-- actionsthat bundle all side effects, and triggerstate changes. so that the loop isclosed, and from the top you can start withchange notifications again and update your system. if you want to forgetanything about this talk, there are couple of thingsi want you to remember.
so the combo of smartand dumb components is very, very useful, becauseto make your ui highly flexible. when you add a uni-directionaldata flow, you get in a spot where it's very easyto separate concerns. so brad, in the keynote,mentioned yesterday uni-directionaldata flows mostly in a sense of performance. it's not about performance only. it's really about designingand architecturing
your application. because when you combine thesetwo approaches with each other, you reach a very clean andpredictable architecture that actually scales. no matter whether it'steam growth, or really technological change haveto face over the years. that's it from us. i'm gernot, this issebastian, and we will be available forquestion answering in the ask
me anything room number one. thanks a lot. sebastian frost:thanks very much.
Tidak ada komentar:
Posting Komentar