codelord.net

Code, Angular, iOS and more by Aviv Ben-Yosef

Understanding Angular’s Magic: Don’t Bind to Primitives

| Comments

If you do Angular, chances are you’ve seen the rule “Don’t bind to primitives” quite a few times. In this post I’ll dig into one such example where using primitives causes problems: a list of <input> elements, each bound to a string.

Our example

Let’s say you’re working on an app with books, and each book has a list of tags. A naive way of letting the user edit them would be:

1
2
3
4
5
<div ng-controller="bookCtrl">
    <div ng-repeat="tag in book.tags">
        <input type="text" ng-model="tag">
    </div>
</div>

(You will probably want to add another input for adding new tags and buttons for removing existing ones, but we’re ignoring that for simplicity’s sake.)

A live demo of the example is available here. Go ahead and edit one of the inputs. It might seem like everything is fine. But it’s not. Taking a closer look would show that the changes you make aren’t being synced back to the book.tags array.

That is because ng-repeat creates a child scope for each tag, and so the scopes in play might look like this:

bookCtrl scope = { tags: [ 'foo', 'bar', 'baz' ] }

ng-repeat child scopes: { tag: 'foo' }, { tag: 'bar' }, { tag: 'baz' }

In these child scopes, ng-repeat does not create a 2-way binding for the tag value. That means that when you change the first input, ng-model simply changes the first child scope to be { tag: 'something' } and none of that is reflected up to the book object.

You can see here where primitives bite you. Had we used objects for each tag instead of a plain string, everything would have worked since the tag in the child scopes would be the same instance as in book.tags and so changing a value inside it (e.g. tag.name) would just work, even without 2-way binding.

But, let’s say we don’t want to have objects here. What can you do?

A failed attempt

“I know!” you might be thinking, “I’ll make ng-repeat wire directly to the parent’s tags list!” Let’s try that:

1
2
3
4
5
<div ng-controller="bookCtrl">
    <div ng-repeat="tag in book.tags">
        <input type="text" ng-model="book.tags[$index]">
    </div>
</div>

This way, by binding the ng-model directly to the right element in the tags list and not using some child-scope reference, it will work. Well, kinda. It will change the values inside the list as you type. But now something else is going wrong. Here, have a look yourself. Do it, I’ll wait.

As you can see, whenever you type a character, the input loses focus. WTF?

The blame for this is on ng-repeat. To be performant, ng-repeat keeps track of all the values in the list and re-renders the specific elements that change.

But primitive values (e.g. numbers, strings) are immutable in JavaScript. So whenever a change is made to them it means we are actually throwing away the previous instance and using a different one. And so any change in value to a primitive tells ng-repeat it has to be re-rendered. In our case that means removing the old <input> and adding a new one, losing focus along the way.

The solution

What we need to do is find a way for ng-repeat to to identify the elements in the list without depending on their primitive value. A good choice would be their index in the list. But how do we tell ng-repeat how to keep track of items?

Lucky for us Angular 1.2 introduced the track by clause:

1
2
3
4
5
<div ng-controller="bookCtrl">
    <div ng-repeat="tag in book.tags track by $index">
        <input type="text" ng-model="book.tags[$index]">
    </div>
</div>

This does the trick since ng-repeat now uses the index of the primitive in the list instead of the actual primitive, which means it no longer re-renders whenever you change the value of a tag, since its index remains the same. See for yourself here.

track by is actually way more useful for improving performance in real apps, but this workaround is nice to know as well. And I find that it helps in understanding the magic in Angular a bit better.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

What I Wish I Knew Writing My First Billing System

| Comments

Billing code is probably the scariest that most coders get to work with. After all, for most of us it’s not everyday that we write code that literally moves money around.

I’ve gathered a list of a few things that you should keep in mind next time you set off to implement billing. Not all are a must have starting from day one, but you should have them in the back of your mind.

Frontend & UX

Don’t leave me hanging – There isn’t a person using the internet that doesn’t know the dread of having nothing visibly change after clicking a “Pay” button. Always show a clear indication that something’s happening. Charging takes a couple of seconds – design for this waiting your users have to endure.

Be clear about the “money time” – I love it when there’s a label near buttons saying whether that button is the one that’s gonna charge me or if there’s another screen after it.

Prevent double-clicks – This should go without saying, but you want to make sure the important buttons can’t be clicked twice, neither because of fat fingers or because the user somehow missed the indication that the click “took”. And none of that “don’t refresh this page” nonsense. Users should be able to go back and forth without fearing for their money.

Design for errors – Charging errors happen. A lot. People have typos, some cards can’t be billed for recurring charges and sometimes people just forget the billing address for a specific card. I’ve heard of people that put in bad data in the payment form just to see if the site seems “legit” and handles it correctly. You should pass along as much information as you have about the error that happened and display it to your user in a human-readable form. No general “error happened, try again” if you know the problem is the expiration date. No “CARD_EXPIRED” constants you got from the API. Write god damn words.

Business

Dunning – Sometimes things get screwed and the monthly charge of a customer will fail. It can happen because the card expired, has no credit available, was canceled, etc. You should handle these correctly and notify your user about this, “nudging” them to update their payment method. This is called “Dunning”. You might also want to automatically attempt recharging once they update their card or just because a couple of days passed.

Handle users in limbo – The other aspect of dunning is that it means you have users that haven’t paid yet for the current period, for whatever reason. You will need to decide how your product should behave for them (a grace period, changes in UI, hiding some features). I wouldn’t delete data or prevent access to business critical stuff before at least a couple of weeks have passed though.

Pre-Dunning – It’s basically better for everyone if instead of trying to charge an expired card and then mailing a “we couldn’t charge” notification you check for it beforehand. Write a bit of code so that a week before the monthly charge you check which cards will be expired and email the users asking to update their payment method now.

Plan for refunds – Every business has to refund money sometimes. You don’t have to create a super slick management backend system from day one, but you should know how refunds work and make sure your support staff has some way to issue refunds relatively fast and that your code will handle the situation appropriately.

Notify before first charge after trial ends – This is just good etiquette. We all hate when we forget some trial and see the charge on our statement later. Do the right thing and notify your users before the trial ends and charging begins. Also, this can save you chargebacks from pissed users which cost you a lot of money and can totally screw your processing fees if there are too many of them.

Backend

Make debugging and support easier – I’m very paranoid when it comes to handling money, and so I love having a kind of an “audit log” that tracks every billing related action that took place in the system. Someone tried signing up, upgrading, downgrading, charging failed, refunds. This is great for debugging.

Plan for pricing changes – When you start experimenting with different plans and pricing and need to grandfather users across plans you’ll need to know exactly which plan users signed up for, when and for how much. This can save your bacon when it’s time to move everyone around so make sure you have it all saved.

VAT and taxes – This can be a bitch, and I’m no accountant, so make sure you know if and when you should charge VAT and similar taxes. In the EU and other places it means you need to ask you users which country they’re from and adjust the price appropriately. You will also need to make sure you add the right line-items to your invoices and keep track of how much money isn’t really “yours”.

Take special care of DB transactions – You charge a user using some API, e.g. Stripe, then go ahead to save the user’s status as “premium” to the DB. The next line causes some error and the database’s transaction automatically rolls back. Essentially, you now have taken the money from the user but have no record of it in your database. Every billing-related operation should have some special logger that lets you know immediately when a rollback happens that affected the billing data.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Improving ng-repeat Performance with “track by”

| Comments

Probably one of the very first things you learned doing with Angular was how to use ngRepeat. It’s so easy and simple, you gotta love it. Here’s an example:

1
2
3
4
5
<ul class="tasks">
    <li ng-repeat="task in tasks" ng-class="{done: task.done}">
        {{task.id}}: {{task.title}}
    </li>
</ul>

The problem

Now say you have a button above that list to refresh it.

The obvious implementation for this refresh would be something along these lines in your controller:

1
$scope.tasks = newTasksFromTheServer;

This is a trivial piece of code, but it would cause ngRepeat to remove all li elements of existing tasks and create them again, which might be expensive (e.g. we have a lot of them or each li’s template is complex). That means a lot of DOM operations. That would make us sad pandas.

In something of a more real-world use case, where instead of our simple example template each task is its own directive, this might be very costly. A task directive that calculates a view model with even relatively simple operations like formatting dates is enough to make the refresh feel laggy. There’s a link to a fiddle with an example at the bottom of the post.

Why would Angular do this? Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.

The annoying solution

The first solution that comes to mind to most developers is to not replace the whole $scope.tasks list, but update in place all the existing tasks objects with the data you received from the server. That would work because it means $$hashKey property would be left intact in the original objects.

But that’s not fun, is it? I hate surgically tinkering with objects, as it is error prone, less readable and a pain to maintain.

track by to the rescue

In Angular 1.2 a new addition was made to the syntax of ngRepeat: the amazingly awesome track by clause. It allows you to specify your own key for ngRepeat to identify objects by, instead of just generating unique IDs.

This means that you can change the above to be ng-repeat="task in tasks track by task.id" and since the ID would be the same in both your original tasks and the updated ones from the server – ngRepeat will know not to recreate the DOM elements and reuse them. Boom.

To get a better feel of the difference this makes, see this fiddle. By default it doesn’t use track by and the date formatting it does is enough to make clicking “Refresh” feel noticeably slow on my computer. Try adding the track by and see how it magically becomes instant, because the task directive’s link function is no longer called on each refresh.

It’s really that easy to get a quick little boost in performance that also saves you writing annoying code.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Writing More Maintainable Angular.js Directives

| Comments

Directives are, essentially, the most powerful building blocks we have in Angular, yet for beginners they are incredibly easy to get messed up.

Here are some guidelines that I’m pleased with, and that can help new comers. I would love any feedback from fellow coders!

My assumptions: The main problems I have in maintaining “magical” code are lack of explicitness and traceability. Explicitness means I would rather write a bit more code in order for it to be clearer what my intention was and what exactly I’m trying to use. Traceability is making an effort that from any piece of code it would be clear what it depends on or what depends on it. Even with thoroughly unit-tested code I believe the following extra steps are necessary.

Favor isolated-scope directives

I pretty quickly realized that what I should use about 90% of the time are isolated directives. These are directives that have a clean slate as a scope. Their scope is clear except for those bindings which are explicitly made in the directive’s definition:

1
2
3
4
5
6
7
8
9
app.directive('myDirective', function() {
    return {
        scope: {
            // The only properties visible in our scope from the parent scope:
            foo: '='
        },
        // ...
    };
});

The major win here is that never again will I be stuck with opening a directive and sifting through the different properties referenced, trying to understand what, if any, the directive is using from its parent. This means I can more safely and quickly refactor, change and delete code.

Explicitly passing dependencies with require

Let’s start with an example. Here is the screen for editing mailboxes on the iPhone:

This might have been written in Angular like so:

1
2
3
4
5
6
<div mailboxes>
  <div ng-repeat="mailbox in mailboxes" mailbox-line edit-context="editContext">
    <div mailbox-edit mailbox="mailbox" edit-context="editContext"></div>
    <div mailbox-description mailbox="mailbox"></div>
  </div>
</div>

In the above example editContext is wired through the mailboxLine directive even though it doesn’t care about it at all, just so it can pass it along to mailboxEdit. Once these extra wirings start getting popular in your app, sometimes across several levels deep just to pass some object, you won’t like it. Take my word for it.

What are you to do?

Requires to the rescue!

Angular’s directives have an amazing ability, though not as widely spread as it should be. A directive can require that other directives will be present either on the element it is placed, or in one of its parents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.directive('mailboxEdit', function() {
    return {
        scope: {
            mailbox: '='
        },
        // We make sure the "mailboxes" directive is somewhere above us
        require: '^mailboxes',
        // "mailboxesCtrl" is the "mailboxes" directive's controller
        link: function(scope, element, attrs, mailboxesCtrl) {
            // Now we can use mailboxesCtrl.editContext
        }
    };
});

app.directive('mailboxes', function() {
    return {
        // Create a directive controller and expose the edit context in it
        controller: function($scope) {
            // Note we assign this to "this" and not "$scope"
            this.editContext = new EditContext();
        }
    };
});

As you can see above, using require means our directive can get a parent’s controller and reference it (mailboxesCtrl), as specified in the Angular docs here. The simpler HTML would now be:

1
2
3
4
5
6
<div mailboxes>
  <div ng-repeat="mailbox in mailboxes" mailbox-line>
    <div mailbox-edit mailbox="mailbox"></div>
    <div mailbox-description mailbox="mailbox"></div>
  </div>
</div>

This is awesome in so many ways:

  • No annoying wiring of things all the way down
  • require will throw an exception if the requirement fails, making it impossible to wire wrong
  • The nested directive explicitly tells us what it depends upon
  • The parent directive explicitly defines an exposed API

I find this way makes maintaining a large system a lot easier and more straightforward.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Please use hasOwnProperty: short story of a hack

| Comments

I know a lot of developers, when writing JavaScript, tend to neglect hasOwnProperty. I can understand it, too. First, it takes a lot of writing: compare if (foo.hasOwnProperty('bar')) to if (foo.bar). Also, some coders assume that if the object you’re handling is a simple object you created yourself and that you know it has no prototype inheritance, then hasOwnProperty is useless.

The problem is that hasOwnProperty is almost always relevant. As in 99% of the cases. So, why not make a habit of always using it? When you don’t, it can really bite you in the ass. Here’s a short story:

The story of a hack

I was helping a friend with his Node.js server. I happened to glance a little piece of code that was basically a cache of valid administration authentication keys. An approximation is:

1
2
3
4
5
6
7
8
9
10
11
12
var adminCache = {};
fetchAdminKeysFromDatabase().forEach(function(key) {
    adminCache[key] = true;
});

// ...

if (adminCache[request.getParameter('key')]) {
    // Assume the request is authenticated
} else {
    // Return 403
}

Seems legit, right? Well, maybe at first glance. But imagine what happens if an attacker passes in a key of "toString"? BOOM! Yes, it’s that easy to get pwned.

That’s because adminCache, like any other object, has a toString property. I can only imagine how many similar bugs are waiting out there because of this.

Of course, had the line been if (adminCache.hasOwnProperty(request.getParameter('key'))) everything would have been just fine.

So, please, use hasOwnProperty.

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

Frontend Driven Development

| Comments

For years, whenever I worked on a new feature I would first figure out the backend and get a rough implementation of the server side solution. Only then would I move on to the other parts.

I think this was because the backend is the part that does the heavy lifting. When you don’t start from the backend, it is easy to find yourself planning features that require way too much from the server. Therefore starting with the backend made sense.

For the past year or so I’ve been doing it backwards – getting the frontend roughly working first: Frontend Driven Development (I kinda hate myself for naming it this way, but I believe it gets the point across).

The Joy

This change brings about many interesting improvements to our feature development:

First, it means we start from a point that’s backend implementation agnostic. This way we focus on the right UX and flow before giving in to how technical difficulties and limitations. Some changes might be needed later, but I find it better to start design, pretending we have an “ideal world”.

Also, no matter how much design work has gone into a feature, a lot of changes and criticism would only appear once you have the UI almost done. When the team sees the feature it shoots off iterations that delay the deployment of the feature. With FDD the feature’s basic version is there to play with and tweak very early, and while the backend part is being implemented the iterations take place.

I saw designers and product people loving having a bit more time to play with the feature with less stress, since they got started with iterations earlier and didn’t hold up the feature from deployment.

Another advantage is that, usually, the frontend is the most flexible part of the system. Now you allow for more short & easy experiments, even those that are never fully implemented, without wasting time developing a backend that’ll never be used or that limits the experiments you make.

Why didn’t I do this earlier?

I believe it’s mainly because working on the frontend when the backend’s not there yet can be a pain. You either stub a lot of code in the frontend, or hard code dummy endpoints on the server, both of which are ugly and a PITA to maintain.

Whatever your environment, be it web or mobile, I bet you can make sure working on the frontend disconnected from the backend is easier, and let your team get stuff done faster!

Happy coding!

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

My Pre-Deploy Checklist

| Comments

I’m not particularly fond of checklists in my work process, but there’s one that I find very valuable whenever I use it, and usually regret it later when I haven’t.
It’s a simple list of stuff that I make sure I took care of whenever I deploy some new graphical component, add a new Ajax call, etc. Do keep in mind I usually work alone or with my partner, and we have no QA except our own diligence. Here it is:

Handle long values

There will always be someone with a freakishly long email address or unusual name, or someone that really pushed it with his “!!!!!!!!!!1oneone” comment. Leaving these unhandled usually causes layout breaks (elements fall off to different locations on screen) or text covers up another element’s space.

Tip: It’s easy to take care of these (see my post).

Treat empty sections

Displaying various empty states correctly can make all the difference in the world, from a UX perspective, e.g. between understanding I have no new messages to making me stare at the screen wondering what’s wrong.

Tip: If you element has a “blank slate” state do even as little as adding little label explaining what this means, e.g. “No photos. Add friends to see some!”

Make progress explicit

I personally despise the feeling of clicking on a button and then thinking “did it work? should I click it again?”

Tip: Add the loaders and appropriate messages that will let people know that something is happening.

Disable buttons after a click

Relating to the previous item, this helps indicate that the click “took” and also is important since some people tend to double click on stuff, which can cause things like double-posting a status or booking tickets twice, etc.

Tip: Just disable the button while the operation is happening. I like to also change the button caption as well, e.g. “Save” becomes “Saving…”.

Verify proper keyboard control

Power users would love you to death if you make sure that pressing tab takes them to the right place and that hitting return would submit the data, etc. These are little details that can change the whole way someone treats a UI.

Tip: Try to edit data with the keyboard only and see what it feels like.Also, making sure your fields have the right types so that mobile browsers display the appropriate keyboards is golden (it’s 2014 – type=“email” is a thing).

Add validations

Junior developers often miss checking that a submitted form actually has all fields filled.

Tip: A simple check to make sure values are there (and, if you’re into it, making sure the comment isn’t just “ ”) will help your users and also your database.

Happy coding!

CSS Tip: Differentiate Classes Used in Scripts

| Comments

This one is short but so good that I find myself having to reintroduce it in every project I work on and to most developers I work with.

We all know that though CSS is for styling, sometimes we have to use a class to get to an element from JavaScript, e.g. $('.save-button').

The Right Way™ to do this is by prefixing those classes with js- to differentiate them from “real” classes. Note that this does mean I sometimes will have an element with 2 classes, e.g. save-button and js-save-button.

Why?

  • Never fear removing a CSS classes because it might break some script that’s using it
  • Never fear renaming or moving a class when you change scripts because it might break the styles

Happy coding!

My Favorite Ways for Horizontally Centering DIVs

| Comments

Since there are so many ways of horizontally centering DIVs I thought I’d list my favorite ones and in which cases they’re applicable.

The obvious: text-align

In simple cases we all love this solution, setting text-align: center on the containing element. The problem is that text-align only works on inline elements, as in spans and divs that have their position set to inline-block. When you have this situation, this is the way to go.

The margin

When the div you want to center has a fixed width set, you can use the magical that is automatic margins. Just set margin: 0 auto which tells the browser to set automatic margins horizontally on the element, thus centering it in its parent.

The when-push-comes-to-shove way

Then there’s the case where the width is dynamic, and your elements are inline elements. In those cases I like the surefire way which I call “Dave Centering” (after the friend that taught me this).

This surefire solution works, but comes at a cost of adding some non-semantic elements. But being pragmatic, sometimes you gotta align those things.

Here’s the final product, and below is the explanation of the details:

1
2
3
4
5
<div class="parent">
    <div class="center-wrapper">
        <div class="center-content">This is centered in the parent</div>
    </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
.parent {
    position: relative;
}

.parent .center-wrapper {
    position: absolute;
    left: 50%;
}

.parent .center-wrapper .center-content {
    position: relative;
    left: -50%;
}

We first set the parent’s position to relative so that positioning center-wrapper will be relative to it. center-wrapper is set to start right at the center of parent, which is close but no cigar. We want its center to be the same as the center of the parent, which is why for the final and inner element we set it to be offset relatively to the left by exactly half (50%) of its width.

Center away!

I hope this saves you some time next time you’re knee deep in the DOM.

Happy coding!

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!

CSS Tip: DIVs with Equal Dynamic Heights

| Comments

Continuing my series of short CSS tips I’d like to share a trick I was taught by a friend a few years ago when we were working on some new layout for our web app.

One of the most annoying problems with CSS is that the height property is pretty limited in strength when it comes to setting it dynamically. I’d like to talk about a relatively simple problem: a 2 column layout where we don’t know how high each colum will be and we want both columns to have the same maximal height.

Since we don’t know the columns’ heights, we can’t set a fixed height on their parent. Also setting height: 100% or something like that on the columns won’t work: we are counting on them to spread to the maximum height and thus make their parent taller.

Cutting to the chase

Though there are more ways of doing this my favorite is relying on the magic properties of tables, or, more specifically, table-like elements.

The whole trick is making the two columns have the properties of two neighboring table cells, which always have the same height. Our HTML would look like this:

1
2
3
4
5
6
<div class="layout">
    <div class="columns-container">
        <div class="column"></div>
        <div class="column"></div>
    </div>
</div>

And the needed CSS is this:

1
2
3
4
5
6
7
8
9
10
11
.layout {
    display: table;
}

.layout .columns-container {
    display: table-row;
}

.layout .columns-container .column {
    display: table-cell;
}

That’s about it! And the nice part is that it’s actually not limited to 2 columns, you can have as many as you want ;)

A word of warning: some of you might try and see that it seems like this works withjust setting display: table-cell on the columns and without the rest of the styles. While that works on some browsers, from my personal experience that’s not compatible on all browsers and in general table-cell display is not supposed to be used outside the scope of a full “table simulating” structure.

I hope you learned something new. Happy coding!

“Maintaining AngularJS feels like Cobol 🤷…”

You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?

It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.

Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.

Get the modernization email course!