11/11/2014

Angular: Creating attributes in HTML Elements

Angular gives the HTML page a lot more power to build its own destiny. We can embed view only concerns directly into the HTML. This helps separate the business logic from the the display logic. Of course this has positive and negative implications. One positive I found this week was the ability to create HTML Element attributes dynamically with no JavaScript.

I needed to create an iframe with conditionally set attributes. My goal was to avoid using JavaScript and the link function. I reasoned that the attributes should be a UI (view in the MVC model) concerns. It took some research, some trial, and mostly error to figure out. What I eventually found, I feel is a good way to go about creating HTML element attributes with only minor JavaScript, and as much Angular HTML craftiness as possible.

The directive definition is simple. We create the directive, and expect certain values in the scope that will be used by the template. In the example, the template is inline to keep things together. However, in practice the template is an external html file. Maintaining the template inline is a bit crazy, and even more so with the number of quote permutations this solution requires.

angular.module('test',[])
.directive('myframe', function (){
    return{
        restrict: 'E',
        template: '<iframe src="http://jsfiddle.net/" 
                       ng-attr-sandbox="{{sandbox === \'none\' ? \'\' : sandbox}}" 
                       ng-attr-seamless="{{seamless !== undefined ? \'seamless\' : undefined}}">
                   </iframe>',
        scope: {
            sandbox: '@',
            seamless: '@'
        }
    }
})

The directive is very simple. We restrict the directive to element replacement, and pull sandbox and seamless values from the parent scope into the directive's isolate scope. The Angular magic is in the template. The "ng-attr-something" tag tells Angular that the expression results in an attribute name of something. For example, ng-attr-sandbox tells Angular to potentially create a sandbox= attribute on the compiled html. Angular evaluates the expression in the ng-attr tag and outputs any result that is truthy. Having the expression evaluate to an empty string or undefined will tell Angular not to include that attribute.

ng-attr-seamless="{{seamless !== undefined ? \'seamless\' : undefined}}"
This clause tells Angular to output the seamless attribute and set it to equal "seamless" as long as the seamless variable on scope is not undefined.

ng-attr-sandbox="{{sandbox === \'none\' ? \'\' : sandbox}}"
The sandbox clause was a little different. I wanted sandbox='' to output as a default. Therefore, I check to see if sandbox has a literal value of "none". If the incoming html has sandbox="none" then the sandbox attribute does not appear in the compiled output. If the incoming html has any other value for sandbox, then that value gets passed through to the attribute. If the incoming html is missing a sandbox attribute entirely then sandbox='' is output.

I would be interested in hearing how others have solved this conundrum. If you, like me, are pounding your head against the keyboard looking for answers, hopefully this trick can help. A fiddle to my solution is embedded below.