How to Create a Simple Modal Dialog Directive in Angular.js

<h3 id="modals-are-easy">Modals are Easy</h3> <p>I&rsquo;ve used a dozen or so modal / lightbox plugins over the years, almost exclusively jQuery-based. But you know what I didn&rsquo;t realize until fairly recently? Modals are easy to build yourself from scratch. So let&rsquo;s make one in the Angular fashion.</p> <p></p> <h3 id="the-requirements">The Requirements</h3> <p>I want to be able to create a modal with the following HTML:</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;modal-dialog</span> <span class="na">show=</span><span class="s">'modalShown'</span> <span class="na">width=</span><span class="s">'750px'</span> <span class="na">height=</span><span class="s">'90%'</span><span class="nt">&gt;</span> <span class="nt">&lt;p&gt;</span>Modal Content Goes here<span class="nt">&lt;p&gt;</span> <span class="nt">&lt;/modal-dialog&gt;</span> </code></pre></div> <p>And I&rsquo;d like it to have the following features:</p> <ul> <li>Toggle it by setting a single $scope variable to true or false (above, the variable is <code>modalShown</code>)</li> <li>Optionally specify the height and width as either a px or % value.</li> <li>Overlay the rest of the screen</li> <li>Close by either clicking an X in the corner or outside the dialog</li> </ul> <h3 id="the-directive-code">The Directive Code</h3> <p>This is a fairly simple directive, with a link function that&rsquo;s only a few lines long. The <code>show: &#39;=&#39;</code> in the isolated scope sets up a 2-way binding between the variable given to the <code>show</code> attribute and the <code>show</code> variable on our scope. Setting this to true or false will toggle our modal dialog. And we check for height and width attributes and, if set, give the modal dialog an inline style. Finally, the <code>hideModal()</code> method simply sets the <code>show</code> variable to false.</p> <div class="highlight"><pre class="highlight javascript"><code><span class="nx">app</span><span class="p">.</span><span class="nx">directive</span><span class="p">(</span><span class="dl">'</span><span class="s1">modalDialog</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="p">{</span> <span class="na">restrict</span><span class="p">:</span> <span class="dl">'</span><span class="s1">E</span><span class="dl">'</span><span class="p">,</span> <span class="na">scope</span><span class="p">:</span> <span class="p">{</span> <span class="na">show</span><span class="p">:</span> <span class="dl">'</span><span class="s1">=</span><span class="dl">'</span> <span class="p">},</span> <span class="na">replace</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// Replace with the template below</span> <span class="na">transclude</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// we want to insert custom content inside the directive</span> <span class="na">link</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">scope</span><span class="p">,</span> <span class="nx">element</span><span class="p">,</span> <span class="nx">attrs</span><span class="p">)</span> <span class="p">{</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">dialogStyle</span> <span class="o">=</span> <span class="p">{};</span> <span class="k">if</span> <span class="p">(</span><span class="nx">attrs</span><span class="p">.</span><span class="nx">width</span><span class="p">)</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">dialogStyle</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">attrs</span><span class="p">.</span><span class="nx">width</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="nx">attrs</span><span class="p">.</span><span class="nx">height</span><span class="p">)</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">dialogStyle</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">attrs</span><span class="p">.</span><span class="nx">height</span><span class="p">;</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">hideModal</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">scope</span><span class="p">.</span><span class="nx">show</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="p">};</span> <span class="p">},</span> <span class="na">template</span><span class="p">:</span> <span class="dl">'</span><span class="s1">...</span><span class="dl">'</span> <span class="c1">// See below</span> <span class="p">};</span> <span class="p">});</span> </code></pre></div> <h3 id="the-template">The Template</h3> <p>The template is also fairly basic. Note that the <code>ng-transclude</code> indicates where your modal content will go. And also note the use of <code>ng-style</code>, which will translate a javascript object into an inline style.</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">'ng-modal'</span> <span class="na">ng-show=</span><span class="s">'show'</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">'ng-modal-overlay'</span> <span class="na">ng-click=</span><span class="s">'hideModal()'</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">'ng-modal-dialog'</span> <span class="na">ng-style=</span><span class="s">'dialogStyle'</span><span class="nt">&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">'ng-modal-close'</span> <span class="na">ng-click=</span><span class="s">'hideModal()'</span><span class="nt">&gt;</span>X<span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">'ng-modal-dialog-content'</span> <span class="na">ng-transclude</span><span class="nt">&gt;&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> <span class="nt">&lt;/div&gt;</span> </code></pre></div> <h3 id="the-css">The CSS</h3> <p>I&rsquo;m not going to spend any time going over this, but I will say that the following will only work in modern browsers (specifically the translate function). If you need &lt;= IE9 support, you&rsquo;ll need center the dialog using a different method.</p> <div class="highlight"><pre class="highlight css"><code><span class="nc">.ng-modal-overlay</span> <span class="p">{</span> <span class="c">/* A dark translucent div that covers the whole screen */</span> <span class="nl">position</span><span class="p">:</span><span class="nb">absolute</span><span class="p">;</span> <span class="nl">z-index</span><span class="p">:</span><span class="m">9999</span><span class="p">;</span> <span class="nl">top</span><span class="p">:</span><span class="m">0</span><span class="p">;</span> <span class="nl">left</span><span class="p">:</span><span class="m">0</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span><span class="m">100%</span><span class="p">;</span> <span class="nl">height</span><span class="p">:</span><span class="m">100%</span><span class="p">;</span> <span class="nl">background-color</span><span class="p">:</span><span class="m">#000000</span><span class="p">;</span> <span class="nl">opacity</span><span class="p">:</span> <span class="m">0.8</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.ng-modal-dialog</span> <span class="p">{</span> <span class="c">/* A centered div above the overlay with a box shadow. */</span> <span class="nl">z-index</span><span class="p">:</span><span class="m">10000</span><span class="p">;</span> <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span> <span class="nl">width</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span> <span class="c">/* Default */</span> <span class="c">/* Center the dialog */</span> <span class="nl">top</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span> <span class="nl">left</span><span class="p">:</span> <span class="m">50%</span><span class="p">;</span> <span class="nl">transform</span><span class="p">:</span> <span class="n">translate</span><span class="p">(</span><span class="m">-50%</span><span class="p">,</span> <span class="m">-50%</span><span class="p">);</span> <span class="nl">-webkit-transform</span><span class="p">:</span> <span class="n">translate</span><span class="p">(</span><span class="m">-50%</span><span class="p">,</span> <span class="m">-50%</span><span class="p">);</span> <span class="nl">-moz-transform</span><span class="p">:</span> <span class="n">translate</span><span class="p">(</span><span class="m">-50%</span><span class="p">,</span> <span class="m">-50%</span><span class="p">);</span> <span class="nl">background-color</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span> <span class="nl">box-shadow</span><span class="p">:</span> <span class="m">4px</span> <span class="m">4px</span> <span class="m">80px</span> <span class="m">#000</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.ng-modal-dialog-content</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span><span class="m">10px</span><span class="p">;</span> <span class="nl">text-align</span><span class="p">:</span> <span class="nb">left</span><span class="p">;</span> <span class="p">}</span> <span class="nc">.ng-modal-close</span> <span class="p">{</span> <span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span> <span class="nl">top</span><span class="p">:</span> <span class="m">3px</span><span class="p">;</span> <span class="nl">right</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span> <span class="nl">cursor</span><span class="p">:</span> <span class="nb">pointer</span><span class="p">;</span> <span class="nl">font-size</span><span class="p">:</span> <span class="m">120%</span><span class="p">;</span> <span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span> <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span> <span class="nl">font-family</span><span class="p">:</span> <span class="s2">'arial'</span><span class="p">,</span> <span class="s2">'sans-serif'</span><span class="p">;</span> <span class="p">}</span> </code></pre></div> <h3 id="triggering-the-modal">Triggering the Modal</h3> <p>Finally, to put it all together, you&rsquo;ll need to add a small bit of code to your HTML and controller.</p> <div class="highlight"><pre class="highlight html"><code><span class="nt">&lt;button</span> <span class="na">ng-click=</span><span class="s">'toggleModal()'</span><span class="nt">&gt;</span>Open Modal Dialog<span class="nt">&lt;/button&gt;</span> <span class="nt">&lt;modal-dialog</span> <span class="na">show=</span><span class="s">'modalShown'</span> <span class="na">width=</span><span class="s">'750px'</span> <span class="na">height=</span><span class="s">'60%'</span><span class="nt">&gt;</span> <span class="nt">&lt;p&gt;</span>Modal Content Goes here<span class="nt">&lt;p&gt;</span> <span class="nt">&lt;/modal-dialog&gt;</span> </code></pre></div><div class="highlight"><pre class="highlight javascript"><code><span class="nx">app</span><span class="p">.</span><span class="nx">controller</span><span class="p">(</span><span class="dl">'</span><span class="s1">MyCtrl</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$scope</span><span class="p">)</span> <span class="p">{</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">modalShown</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">toggleModal</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="nx">$scope</span><span class="p">.</span><span class="nx">modalShown</span> <span class="o">=</span> <span class="o">!</span><span class="nx">$scope</span><span class="p">.</span><span class="nx">modalShown</span><span class="p">;</span> <span class="p">};</span> <span class="p">});</span> </code></pre></div> <h3 id="all-together">All Together</h3> <p>And that&rsquo;s it! I put all the code together in a demo <a href="http://jsbin.com/aDuJIku/2">on JSBin</a>. Check it out. And I&rsquo;ve put a slightly more polished and configurable version of this directive up <a href="http://github.com/adamalbrecht/ngModal">on Github</a>.</p>