Responsive Takes Flight: Building The 1st Responsive Airline Website by Oliver Dore @ ng-europe 2014


Hi, everyone. So I’m really happy to be here
today to talk about a project that I’ve been involved
in for over a year now. So thanks to Patrick and
Brad for inviting me here. My name is Oliver Dore. I’m, like a lot of you,
a front-end application developer. I started out my career on the
server side in .NET and PHP for my sins. And for the past
five or six years now, I’ve been focusing a
lot more on UI development. With how capable browsers have
become over the past few years and the evolution of great
frameworks like Angular, my passion is really in
combining good engineering practice with great
design and experience. So over the years,
I’ve worked on projects like Four Seasons
Hotels and Resorts. And my first real foray
into responsive design was a marketing content hub
for Google called Big Insights. So I’m a developer at Work & Co. We’re a digital design
and development company. Work & Co was founded on this
philosophy of leaner teams and closer collaboration between
disciplines and a more agile workflow in creating
these digital products. I’m based in
Brooklyn, but we also have operations in
San Francisco, Rio, and this beautiful
city of Paris. But enough about that. So what I really want
to talk about today is the responsive redesign
of virginamerica.com and how AngularJS
was used to realize both the technical and
creative challenges. So I guess to start, how many
of you know about Virgin America or have flown with them? OK. So not many. So for those who do– well, or
those who don’t– they’re a US airline based out
of San Francisco. And they fly to about 20
destinations within the US. US domestic airlines
are generally not good. And Virgin America
is not like flying with those other airlines. As you can see
here, their aircraft have this crazy mood lighting. Every seat has in-flight
entertainment and power circuits, on-board
Wi-Fi on every aircraft. That really boring
safety announcement that you get on airlines– they
turned that into a music video. And if you go on
to YouTube, I think this video has about
10 million views now. It’s sort of insane. You should check that out. The reason I’m
telling you all this is because this
is an airline that wants to do things differently. But the truth is, their old
website didn’t reflect that. So let’s go back in
time a little bit. This is the site that
they had for several years before the redesign. It’s kind of busy, and
there’s a lot going on. But I want to draw your
attention to this link here. When you expand this link,
it reveals a component that allows the user to select
where they’re traveling from, where they’re going
to, their dates, the number of travelers,
how they want to pay, if they have a promo code. People are coming to the
site to book tickets, and the mechanism by which to do
that is hidden in a drop-down. Similar to a lot of
other airlines’ sites, this homepage
served many masters. It was a mix of
utility-focused sections, corporate information,
news– even pilot news is on this page. They also had a separate mobile
site, and for the business, it had become inefficient
to maintain two code bases. And it just looked a lot
like every other airline site out there. When you look at these
examples, there’s really no way of telling
one apart from another. It doesn’t tell you
who the company is. It doesn’t tell you
what their values are. And for Virgin, this is a real
shame because, as you saw, their in-flight experience
is completely unique. So the way we see it, an
airline’s site is a utility. People want to perform a task
and go on with their day. It doesn’t mean it
has to be boring, but it should remove obstacles. So we worked with
Virgin to establish a small number of
goals for the project. The first one was to improve
their conversion rate. More than half of
Virgin’s revenue comes through the website. They wanted to deliver a
great responsive experience– a consistent experience
across all devices. Whereas, a few
years ago, you had to convince clients of the
merits of responsive design. Now clients are
coming to us asking for that as a requirement,
and that’s kind of refreshing. And they wanted
an online presence that better represented
their brand. So we focused our design
development sprints around creating the best
booking experience on the web. And part of that was
deprioritizing features like upsells and promotions. It really was this
brute force approach, like we designed, refined,
prototyped, iterated and tested only around
those core goals. So throughout the project,
our team spent a lot of time in San Francisco. And we worked at the
Virgin America offices. We sat next to the
client, oftentimes in this windowless
conference room. We prototyped from day one. And we iterated frequently to
explore ideas and incorporate feedback. This rapid prototyping
forced us to be agile with our decision making. It also prevented us from
being precious about our ideas. As our partner
for design put it, there wasn’t this design
phase and development phase. It really was designers and
developers working together. If it didn’t work,
we’d just go back and try something
else as a team. So the prototype became the
single source of truth– the Bible. It wasn’t meeting notes,
or sketches, or PSDs. It became a reference
point for everyone involved– the shared vision
of what the product should be. We tested a ton of concepts
over several months with almost 50 users. And during those
testing sessions, developers were on
site, and we updated the prototype in real time. So feedback from one user
was incorporated, deployed, and tested with another
user an hour later. And we really had no idea
what the response was going to be to some
of these ideas. We’d taken this component
that was hidden in a drop-down previously, and we
expanded the idea to a full-width, single-view
series of components. It was a radical change. And there was a big chance
that it was going to fail. So we were very excited
that most people loved it. They were able to accomplish
their task much faster and more straightforward than before. But not everyone. One guy hated it. He still achieved
what he wanted to, but he didn’t like the design. He wanted more images. He didn’t like how it scrolled. But the truth is, you
can’t please everyone. When you please everyone,
it means playing it safe. And when you play it safe,
you end up back here again. We knew the idea was
radical, but the feedback that we got from user testing
gave us a lot of confidence to go ahead and build the app. And for developers, it was great
to be part of that process– to see the product that
you’re building being used. It gives you a much
better understanding of where to dedicate your
energy to yield the most value. So in the US this year,
for the first time, more people are accessing
the web via their phones and tablets than
through their PCs. And a lot of times
on projects, there’s a discussion that takes
place about mobile-first or desktop-first design. On this project,
it didn’t happen. This aim of a consistent
design and experience across all the
breakpoints led us to adopting what we called an
“everything first” approach. Interaction patterns
and UI elements were designed to act and
work identically across most parts of the app and
at every breakpoint, whether it’s picking a seat,
or filling out traveler info, or choosing an avatar. It minimized the amount of
breakpoint-specific mock-up and styles that we created. And despite the fact that
technical implementation was mobile-first, there were
actually very few differences for each component between
the smallest and largest breakpoints. So let’s get on to what I’m
actually here to talk about. When I was thinking
about this session, I really wanted to focus
on the major decisions that we made around
responsive and how Angular featured into that. I think the architecture’s
probably a good place to start. So why Angular, which is a great
question for this conference. We knew from the beginning it
was going to be responsive. And from working with the
technical team at Virgin, they were keen to
create a stateless API. There was a clear drive towards
the separation of concerns to increase performance
and improve stability. And through concepting
and user testing, a single-page
application had emerged as a solution best
suited to meeting not only the technical
requirements, but the creative goals, as well. Let me say that now. We’re not experts in
Angular by any means. I’m sure the code
examples that I show later will be evidence of that. We had a bit of
experience in the team, but for the most part,
most of our developers were coming on to the project
having never used it before. We did a comparison
of MVC frameworks, but I don’t want to
bore you with talking about data binding, or
dependency injection, or boilerplate. I think many of you have
been through that exercise yourselves. For us, it really came
down to two things. It was opinionated
where it mattered. It provided this framework for
developers to join the project and understand the conventions. So within a day, we
had new developers coming on and contributing to
a quickly-growing codebase. But it was also flexible
where it mattered. We knew from the user testing
in the concepting phase that Angular wasn’t
going to limit the creative vision
of the project. It would actually empower it. So this is the
application structure that we started out with. Much of it you’ll recognize
from your own Angular apps, but we broke our services
into three categories. Because we were dealing
with an API that had about 50 endpoints,
we had a service dedicated to just handling
API requests, responses, error handling, using the HTTP
interceptor to actually trap errors and message
them to a user. We had one for
functional services, as I call them– so services
dedicated to one key flow within the application. It could be a booking, or
check-in, change, cancel. And then utility services,
which were shared services across multiple methods of
controllers, directives, and views for sharing
data or methods. And I’ll go into a few
examples of that later. So underneath that, we
used Node, NPM, and Grunt not only as our development
environment, but also as our build system. For style, SASS was our
choice of preprocessor. And we used Compass for
our mixins and patterns. And then we also used a
great library called Susy. I’m not sure how many of you
have used it or heard of it, but it was used to create
a multi-breakpoint grid system that allowed the
developers to apply that grid system across the UI. And then finally, we have our–
if it updates– there we go. We had our REST API. We had a Java-based CMS,
and there was also an API into that for our
content-managed stuff. And then Jenkins was our
continuous integration server. It also pushed our
deployments to AWS. So this is a very
high-level view of the application structure. Each of the major flows
had its own directory, which include an Angular
module and then, obviously, a combination of controllers,
directives, services, and templates. Any cross-flow
functionality or templates lived in the common folder. And we used RequireJS to
resolve all these dependencies during development. The end result was one
core Angular module with each flow application
as a dependency. And to prevent
naming collisions, we namespaced each application
with their own dependencies. You can see it here. So since we’re talking about
architecture– and this isn’t strictly
Angular– but I wanted to talk about the UI
framework because we really wanted to create a UI framework
that was as modular and as extensible as Angular was going
to provide at the application level. So not only how we
structured our files, but conventions
around class names and how we structured
our mock-up to encourage
maximum reusability. We followed the BEM methodology. How many of you have
followed BEM in the past? OK. So for those who
haven’t, it stands for Block, Element, Modifier. It’s a declarative
system for CSS classes to provide a greater meaning to
the developers on your project. The format that we followed
was honed by Nicolas Gallagher and described very eloquently by
Harry Roberts in his blog post here. This is how I found about it. So this is a version
of the mock-up that we used to render
the main navigation on the tablet
breakpoint of the site. Sorry it’s a bit small. So to give you some
context, a block represents a
higher-level component, like a building block
of the application. An element is a
descendant of a block that performs a
certain function. It only makes sense within
the context of the block. And a modifier is a property
of a block or an element that modifies its
local behavior. So the result of this
was a set of components that worked very well
independently, but played well as part of larger systems. We had developers working in
multiple countries and time zones, and it allowed them to
share these components really easily and reduce repetition. But the reason I’m telling
you this is it also changed the way that
our designers worked. Rather than handing over
templates or PSDs, which I’ve had in the past,
and I’m sure you do, too, they approached
their own work with this
component-based mindset. So it made the workflow
between two disciplines much more seamless. So we combined this approach
with another methodology from Jonathan Snook
called SMACSS, which stands for Scalable and
Modular Architecture for CSS. And it’s more of a style
guide than a rigid framework. It recommends how
to organize your CSS for large applications. So by using SASS,
we used partials to break down our styles into
base styles, variables, grids, modules, and themes
that we then imported into the main stylesheet. So SMACSS was great for defining
the structure at the file level. And BEM created this unified,
declarative language for CSS that really encouraged
standardization and component reuse. One of the first questions
when you work with designers is, how does a
responsive grid work? Which I think is pretty fair. It’s actually hard to
visualize how breakpoints are going to work in practice. So we prototyped it. We used the Susy library to
scaffold the responsive grid system and defining column
width, gutters and margins. And then we explored the
boundary of each breakpoint. We set up a demo
grid very similar to this using simple
block elements. And then we used tools like
Ghostlab and BrowserStack to get a feeling of how it
was going to work in practice. So the end result of that
was a six-column grid for the small breakpoint
and a 12-column grid for medium and
large breakpoints. So now we have this responsive
framework– one for designers to use in their PSDs and
one for developers to use, which were these series of
mixins they could implement in the grid system
across the UI. So before, I talked
about maximum reusability across breakpoints. But sometimes it makes
for a better experience to optimize the UI based on
viewport size or input type. And Angular made this
incredibly easy– actually, sometimes too easy. The best example
of this on our app is probably one of
the most overlooked on any site, which
is the footer. So it’s comprised of 25 links. And we stored the
links in a constant that we then injected
into the footer directive. The benefit of
that separation was it allowed us to render a
breakpoint-specific mock-up, including different directives,
with a single source of data. So on the largest
breakpoint, the links fit neatly into
this 12-column grid. On the medium breakpoint,
we re-sort the data and re-render the mock-up
into four columns. But this is where it gets a bit
more interesting for mobile. So having 25 links on a
small viewport actually looks kind of messy. And if they’re small, they’re
also difficult to hit. So we re-sort the
data once more. And we reprioritize
the top 10 links. And we show them here. But underneath, now
we have a drop-down. When that drop-down’s
selected, we actually delegate the responsibility of
rendering the rest of the links to a modal provider,
which is here. So in this example, we
actually create a new instance of a modal and a new scope. Because that scope is a child
of the footer directive scope, it inherits the links
that have already been attached to the
footer directive scope. And so what that allows
us to do is render this modal, which takes over
the screen as a new layer on top of the application. It uses the same data as
the other breakpoints, but now allows us to apply
things like inertia scrolling and a much larger hit
area for each link. We came to see
Angular’s capability of handling data
and manipulating the DOM as powerful methods
of fine-tuning the experience. In fact, it’s really easy
to abuse, as we found out. So we tried to be smart about
where it offered real value. I think the main navigation
is a great example. The ability to select fares,
choosing a particular flight, or viewing your travel
summary– it was all about what was best for the
particular context. So a few weeks into the project,
the UI was coming together. And we’d already built out
many of these global components and were working on components
for the booking flow. But it started to
dawn on us how much repetition had crept in to
our controllers and directives for observing the
state of the window. We were adding event listeners
for scrolling and resize events and detecting the
current breakpoint in a lot of different places. It’s a need that we obviously
had quite consistently. So there was this obvious
need for centralizing not only listening for window events, but
how we set state on components outside of the
viewport– so the body, the main navigation, the
footer– these global elements. We came up with
a window service, and in tandem with a window
directive attached to the body, this really served two goals. It centralized and aggregated
the event listeners on the window for scroll
events and resize events. And using a pub-sub model,
controls and directives could subscribe and unsubscribe
from both window-based events and states on the chrome of
the application– so things like the current breakpoint,
the visibility or positioning of navigation bars, even
the theme of the site. So our first attempt
on this, we set values on the window service,
and we used watches in the window director to
conditionally add and remove classes on the
body using ngClass. And it worked, but had two
really crazy drawbacks. First, we were
updating scope values every time the user
scrolled or resized. And we were firing watches
thousands of times on the page. The performance was
really, really bad. And since we were
applying classes to the body of our
ngClass, we were also using the digest cycle. And so there was
a noticeable delay when you were scrolling up and
down to set these bars as fixed or a relative position. And it caused the UI
to stutter in contrast to the user’s input. So to get around that,
which most people probably wouldn’t recommend,
but we actually circumvented Angular’s
digest cycle. So instead, the window
director subscribes to events from the
window service. And instead of using
ngClass, we used jqLite to add and remove
classes to the body directly. This solution eliminated a
lot of the performance jank that we were seeing and made
the UI feel much more reactive. And responsive components
didn’t stop with the website. That philosophy also inspired
this principle boarding pass that you could fold up
and put in your back pocket. Again, it was whatever
was best for the context. This context just
happened to be offline. So this is how I feel
when I fill out forms. I think everyone would agree
that forms are really painful. And people just don’t
want to fill them out. We knew that, so we
thought long and hard about how to make this
inevitable process as frictionless as possible. So this is what we did. As a user inputs their data,
we perform real-time validation on the fields, not only
client-side, but when needed, validation at the
API level, as well. It not only serves to help
correct the user’s issues as they’re stepping
through the process, these feedback labels
are an opportunity to incorporate Virgin’s
voice into the process. So as a user gets closer
to completing the form, with only one field
left, the Submit button becomes a guidepost. The idea is that
it becomes really difficult to make a mistake. And when you do, you know
about it immediately. There are about 30
forms in the application now, so it was really important
to create a system that was easy to create these
forms and be maintainable moving forward. So we came up with a
series of directives. This is a very simple example,
but easier to explain. The form handler directive
serves multiple purposes. Each input field
added to the form registers itself
with the handler. When values are input,
the form handler aggregates all the data
into a single model. The handler is responsible
for validation. It not only validates and
informs the individual input as to its own
state, it allowed us to create more complex
relationships between groups of form elements when necessary. So the attributes
of the form handler were used to configure data,
and messaging, and callbacks. And some of these were
applying pre-existing data, defining a model that would
store each of the data on the input directive,
defining callbacks when the form is submitted and
all the validation rules are met, and then defining default
and error labels for the Submit button at the bottom. We were dealing
with so much data running through
this application. As I mentioned before, we
had 50 endpoints in the API. And it became really unwieldy
to store and manipulate this data– to format it to
be displayed within a view, but then reformat it to be
utilized for the REST API. I think we made a common
mistake in that we made the coupling between
the UI and the API so tight that every time the API
changed, our UI broke. But we learned
quickly on that one. So to combat that, we
created these data models that made it really
easy to define a schema, create mappings and
methods to serialize and deserialize data
from different contexts. So as the API evolved during
the course of the project, we just updated our
serialization methods and there was no longer this
dependency at the UI level that we had to address. And since the structure of
the data from all the forms was consistent, it
made it really easy to write deserializers
to take that data and extend the model
so it could be used in other parts of
the application. I think of all
the lessons that I learned throughout this
project, this introduction of intermediary data models
proved the most valuable. So the result of
this was a series of components that could be
combined to create forms– a simple interface
in the form handler that made it really easy
to write custom input directives and a
standard approach to injecting and
extracting form data. But all that was
really this opportunity to try and make the process
less frustrating for the user. And the feedback so far
has been really positive. This morning, when I was
looking at the features for 1.3, I realized that most of what we
did is actually coming in that. So I wish I had been here like
eight months ago, but whatever. So before I started
this project, I saw a talk from
my idol, Paul Irish, where he said that on average,
over 60% of page weight is contributed to images. So average page weight
for the top 100,000 sites is now up to almost
1.9 megabytes, up from 1.2 just two years ago. We didn’t have a lot
of images on the site, and I think part
of that lent itself to the goals of the site. But it had this
obvious side benefit of keeping the page weight down. There’s still work that we could
do to make it better, though. So for icons, we used Grunticon
to manage our SVG icons and background images. And that reduced the number of
HTTP requests we were making and made our assets
resolution-independent. Where images were
really necessary, we really wanted to find a way
to optimize for the viewport. So we created this image
directive and a service that leveraged an image
resize API provided by Akamai. So for each image
in the application, we defined a source image, and
that was uploaded to the CDN. Based on the design,
we determined the largest applicable
width for each breakpoint. And that was included as
attributes on the directive. The first time a new
size is requested, it’s created and
cached on the CDN. So all these responsive
image directives register themselves with an
image service that we created. And that really
served two functions. The first was subscribing to
changes in the breakpoint. And the second was
when a change occurs, a source value for
the image is updated. We also used
Modernizr to determine whether the image is
on a high-DPI display. And when that’s the case,
we multiply the size values. So the quality is then
not only determined by the size of the viewport, but
also variable pixel densities. It’s such a simple
implementation, but for us, provided
so much value. When we launched
virginamerica.com, there were about 35,000 lines
of JavaScript in this project. And even with the
entire application logic minified into one file,
the initial page load was only around 750
kilobytes, in large part due to techniques like this. Yeah, this was a saying that
went around the team a lot. So one of the first times
I heard about Angular, I saw a video of [? Mishco ?]
talking at an MTV tech meetup. And an audience member
asked him this question about what does he
do when he has tables with hundreds of rows of data. And [? Mishco’s ?] answer,
which was a good one, was that you shouldn’t do that. That there’s surely a
better way to represent that amount of data. It was really great advice,
which we completely ignored. This single-page flow
worked really well to guide the user
through the flow, but the result was single views
with hundreds of thousands of bindings. And the performance
implications were significant. So after some research,
we made heavy use of Pasquale Vazzana’s
bindonce directive. It reduced the number of
watches we had in the view by removing those watches after
the bindonce directive was evaluated. We forced it to meet
our specific needs, including the
ability to reevaluate those bindonce directives
using rootScope broadcasting. But now, as part of Angular
1.3– this is coming in. I think it’s, for us, is a huge
deal because on our heaviest data-driven components,
this increased performance by orders of magnitude. About two weeks
before we launched, during testing, the app crashed
some of our mobile devices and tablets. And we took a deeper dive
into the Chrome Dev Tools, and using the
memory profiler, we discovered several memory leaks,
spikes, and detached DOM nodes that weren’t being cleaned
up during garbage collection. It turned out that we were
exceeding our per-tab memory allocation on mobile
browsers, which forced the application to quit. This is our amazing engineer,
Igor, in the middle of that. It was this really
painstaking process of profiling, isolating,
and refactoring to reduce our memory footprint. Our job here still isn’t
done, but over several stages, we’ve being able to
reduce that by about 150%. And there’s a lot of
improvement that we can make in regards
to accessibility. We know it’s not where
it should be right now, and we’re planning for
significant improvements on this front in the future. As part of our efforts to
update the site to 1.3, we’re already looking at
incorporating the ngAria module. And of course I’m looking
forward to Marcy’s session tomorrow on accessibility
in AngularJS. So the real moment of truth was
putting it out to the users. And like I said, because it
was such a radical change, it was launched as a
closed beta, and then an open beta one week later. We received some great
feedback from members of our own industry,
including the father of responsive design,
Ethan Marcotte. And we took a lot
of the feedback the we got via our
social channels to heart, using the
beta as an opportunity to incorporate requests. So one of those was
the ability to search for a lowest fare within
a seven-day window. And by the time
the site launched, we actually added a lot
of additional polish. Even after we launched, that
feedback loop continues. And we recently introduced a
30-day fare calendar, as well. So we received some nice
press, but at the end of the day, none
of that matters. The only thing that
matters is that the site works for Virgin and
for its customers. For the company,
they now have a site that speaks to their
values and their brand, a consistent experience
across all devices, and a single code
base to maintain. And though I can’t
share numbers, so far, we’re seeing
really positive results. It would be remiss of me
not to give a shout out to Virgin and my
team at Work & Co who contributed to the project. I’m lucky enough to be up
here to speak about it, but without them, it
wouldn’t have been possible. They’re an insanely
talented bunch. And without Angular,
it certainly wouldn’t have been as
fun or as productive. As we’ve seen
today in 1.3, a lot of the most challenging
parts of executing this app are about to become
much easier when it comes to memory
issues, forms, messaging, and single binding. We owe, as a company, a lot
to the Angular community– all of you– in getting
us to this point. And we’re excited to
contribute to that community in the future. Thank you very much. [APPLAUSE]

3 Comments

Add a Comment

Your email address will not be published. Required fields are marked *