Flash Messages with Rails and Angular.

Collaboration over duplication.

When working with an Angular.js and Rails stack, you’re constantly making decisions about whether something should be handled on the client or the server. Sometimes, you run into a feature that needs to be handled on both sides, and it can lead to a lot of unnecessary duplication.

I recently read a terrific blog post by Shopify about how they were running into this problem, and it was percolating in the back of my mind when I started thinking about flash messages.

Flash messages, for the uninitiated, are those little notices or alerts that pop up after you perform an action. A green “You’ve been logged in!”, or a red “You’re not authorized to view this page.” Rails provides a built-in way of dealing with them with the Flash hash, and here’s a typical example:

<!-- _flash_messages.html.erb -->
<% if notice && !notice.blank? %>
<div class=”alert alert-success”>
<%= notice %>
</div>
<% end %>
<% if alert && !alert.blank? %>
<div class=”alert alert-danger”>
<%= alert %>
</div>
<% end %>

So this isn’t a perfect solution, but it generally works. The problem is that with my app, there are some flash messages that need to be generated without re-rendering the view.

For example, when blocking a user, I handle the server logic through an AJAX request, and update the DOM with Angular. Because the view has already been compiled into straight HTML, those alert divs never got rendered and so they don’t exist on the page.

I started thinking I’d need to have two sets of flash messages; one controlled by Rails, the other by Angular. Here’s a theoretical look at what might have been:

<!-- _flash_messages.html.erb -->
<% if notice && !notice.blank? %>
<div class=”alert alert-success”>
<%= notice %>
</div>
<% end %>
<% if alert && !alert.blank? %>
<div class=”alert alert-danger”>
<%= alert %>
</div>
<% end %>

<div ng-controller="FlashMessages as flash">
<div class="alert alert-success" ng-show="flash.notice">
{{flash.notice}}
</div>
<div class="alert alert-danger"ng-show="flash.alert">
{{flash.alert}}
</div>
</div>

*Shudders*. This is the kind of stuff I have nightmares about. The big problem, of course, is the duplication, but it also introduces new problems. What if the server sends a flash message and then Angular adds one too? Would I have two overlapping messages? Would I need to write some JS to have Angular delete the Rails message before showing its own?

No, this would not do. I decided that the solution was to have Angular handle all of the flash-message logic.

There are a number of ways to pass data from Rails to Angular. A common approach, and one I ultimately decided to use, is with data attributes. Something like:

<!-- _flash_messages.html.erb -->
<div ng-controller="FlashMessagesController as flash" 
data-notice="<%= flash[:notice] %>"
data-alert="<%= flash[:alert] %>">

<div class="alert alert-notice" ng-show="flash.notice">
{{flash.notice}}
</div>

<div class="alert alert-danger" ng-show="flash.alert">
{{flash.alert}}
</div>
</div>

Then, in your Angular controller, you could do something like:

// flash_messages_controller.js
function FlashMessagesController($attrs) {
this.notice = $attrs.notice;
this.alert = $attrs.alert;
}

// Injection and stuff omitted for brevity

How does this work? Rails renders the HTML and as it does, it slips flash[:notice] and flash[:alert] into the Angular controller’s data attributes. If there is no message, it returns ‘nil’, which gets outputted as an empty string. Because empty strings are falsy in Javascript, we can use those attributes both for the content itself, and as the trigger for showing/hiding that content.

Let’s Refactor!

We’re making progress! The problem, though, is that there’s still the matter of two near-identical divs, with the only difference, at the DOM level, being the class name. This is fine when our flash messages are so simple, but they aren’t extendible at all without causing problems. I know I’ll want a ‘dismiss’ button, and who knows what else I may want to implement down the line.

The solution is pretty straightforward. Instead of having two separate message variables (one for alert message, one for notice message), I’ll have a single message variable, with another variable to designate the type of message.

First, I took a look at what Rails’ Flash object actually looks like. Here’s what it outputs when you call .to_json on it:

// If there's a notice message:
[ ["notice", "You have successfully logged in!"] ]

// If there are no flash messages:
[]

Messages are stored as arrays, with the first item being the type of message and the second being the message itself. Let’s just throw that into a data attribute, and evaluate it in our controller:

<!--_flash_messages.html.erb -->
<div ng-controller="FlashMessagesController as flash" data-initial="<%= flash.to_json.html_safe %>" ng-cloak>
<div class="flash" ng-show="flash.show" ng-class="flash.show">
<div class="close" ng-click="flash.show = false">
<%= fa_icon "close" %>
</div>
{{flash.message}}
</div>
</div>

// flash_messages_controller.js
function FlashMessagesController($scope, $attrs) {
var initial = eval($attrs.initial);

if (initial.length) {
this.show = $attrs.initial[0][0];
this.message = $attrs.initial[0][1];
}
}

We’re evaluating that string, which either returns an empty array, or an array of arrays. If it has a length greater than zero, we know Rails is communicating with us.

Understanding These Variables

There are two important variables, as far as our Angular controller is concerned: show, and message.

While you might expect ‘show’ to be a boolean, it’s instead referencing the type of message to show: either ‘alert’, ‘notice’, or undefined/false. Because it defaults to undefined if we don’t pass in anything from rails (and because both ‘alert’ and ‘notice’ are truthy values), we can use it in our ng-show to control whether it should be visible or not, and also in our ng-class to determine which styles to apply. Here’s some SASS for clarification:

/* components.css.scss */
.flash {
&.notice {
background: $green;
}
&.alert {
background: $red;
}
}

So, lovely. We’ve jumped through a few hoops to get Rails to display its flash messages. The whole point of this, though, is for Angular to be able to update them as well! And it turns out we’re not quite there yet.

The Other Half: Angular

So I’m pretty new to Angular — this is the first big project I’ve done with it, and definitely the first where I’m trying to be “smart” with it and not end up with a big pile of scope soup.

Those with more experience than I will have immediately spotted the issue: We’re storing our flash message properties on the controller. That controller’s scope is limited to the div that holds our messages; there isn’t a nice, clean way to update those values from outside of that box. We can shove them onto the $rootScope, but I don’t think that’s ever a good idea.

Yay clipart!

Thankfully, factories exist! Factories are providers that can be injected into controllers, to share behaviour and/or data between them. I’ve typically used factories for making $resource calls, but it turns out you can do quite a bit more with them. Here’s what it looks like. Changes are bold.

<!-- _flash_messages.html.erb -->
<div ng-controller="FlashMessagesController as flash" data-initial="<%= flash.to_json.html_safe %>" ng-cloak>
<div ng-show="flash.factory.show" ng-class="flash.factory.show"
class="flash">
<div class="close" ng-click="flash.factory.setShow(false)">
<%= fa_icon "close" %>
</div>
{{flash.factory.message}}
</div>
</div>

// flash_messages_controller.js
function FlashMessagesController($scope, $attrs, FlashMessage) {
this.factory = FlashMessage;
  var initial = eval($attrs.initial);
if (initial.length) {
FlashMessage.setShow($attrs.initial[0][0]);
FlashMessage.setMessage($attrs.initial[0][1]);
}
}

// flash_message_factory.js
app.factory(“FlashMessage”, function() {
this.message = ‘’;
this.show = ‘’;
  return {
setShow: function(show) {
this.show = show;
},
setMessage: function(message) {
this.message = message;
}
};
});

So, what’s going on here? Instead of storing ‘show’ and ‘message’ on FlashMessagesController, we’re storing them on our FlashMessageFactory. Retrieving them is simple: by setting the factory as a property on the controller, we can go controller.factory.property (or, in this example, flash.factory.message).

To set the properties, I wrote two quick setter functions. Interestingly, it works just fine to set it normally, with flash.factory.show = ‘notice’, but factories are meant to return things, and it just felt weird to be returning an irrelevant object.

To update the flash message from Angular, you just need to inject FlashMessageFactory into whatever controller that needs to do the updating, and use its setShow() and setMessage() methods. Example:

function UsersController($scope, FlashMessage) {
// A bunch of stuff omitted

if ( user.blocked ) {
FlashMessage.setShow("notice");
FlashMessage.setMessage("You have blocked"+user.name);
}
}

Closing Thoughts

I’m pretty happy with this solution! The problem is, I’m still pretty new to Angular, so I don’t know if there’s a much better way that I just haven’t come across.

Got a better solution? Drop me a line and let me know =)

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.