Jade-up your Angular2 markup

Jade-up your Angular2 markup

Can you imagine the combination of Angular2 and Jade is super awesome and timesaving? If not, read the following lines and reconsider!

When building SPAs using Angular2 or AngularJS, you will use custom HTML markup extensions (aka directives) to instrument Angular how to render your SPA or how the different building blocks of your app should correspond to user interaction. Using those directives is easy and well known in the Angular Community. Nothing special here.

But, I found myself repeating almost the same markup multiple times while building different prototypes on Angular2. To get rid of those duplications, *ngFor may be a solution, but with respect to performance on mobile devices, you should avoid using those structural directives for such basic things.

At this point Jade enters the stage. Jade offers stunning but simple mechanisms to make you even more productive. You could easily build compassable mixins, derived templates and other cool things. Besides all the advantages like maintainability, readability or the time you save, it's also fun to use it!

If you have not used Jade before, see my other posts on Jade here and here

Fasten your seatbelt and prepare for some extra time that you can spent with your friends and family instead of waisting this time with tons of markup code.

Baby steps

FontAwesome is cool, I it. It takes away a lot of pain, like looking for the right images, integrating those in the build process, creating image-maps to reduce HTTP requests... you see, it's cool!

But it's frustrating to write <i class="fa fa-foo"></i> for the 3242th time. Let's build a mixin to get rid of that.

// mixins/base.jade
mixin icn(name,x)  
  if(x)
    i(class='fa fa-#{name} fa-#{x}x')
  else
    i(class='fa fa-#{name}')

Using it is simple, include the mixin file and call the mixin using +mixin-name(parameters)

include mixins/base.jade

+icn('globe')
+icn('heart',5)
+icn('github')

This will generate

<i class="fa fa-globe"></i>  
<i class="fa fa-heart fa-5x"></i>  
<i class="fa fa-github"></i>  

Forms

Let's start with forms. Building forms (as soon as designing those in CSS is done) is a stupid task. It's all about writing the same input node with tons of attributes multiple times. Here a short mixin for bootstrap based input nodes

// mixins/forms.jade

mixin input(slug, title, type, placeholder, model)  
    .form-group
        label.col-sm-2.control-label(for='#{slug}')= title
        .col-sm-7
            input.input-sm.form-control(id='#{slug}', type='#{type}',[(ngModel)]="#{model}", placeholder='#{placeholder}')

Usage is pretty the same as you've learned in the previous sample.

// index.jade
include mixins/forms.jade  
+input('firstName', 'First Name', 'text', 'John', 'customer.name')
+input('email', 'Email', 'email', [email protected]', 'customer.email')

This will render

<div class="form-group">  
  <label for="firstName" class="col-sm-2 control-label">First Name</label>
  <div class="col-sm-7">
    <input id="firstName" type="text" [(ngModel)]="customer.name" placeholder="John" class="input-sm form-control"/>
  </div>
</div>  
<div class="form-group">  
  <label for="email" class="col-sm-2 control-label">Email</label>
  <div class="col-sm-7">
    <input id="email" type="email" [(ngModel)]="customer.email" placeholder="[email protected]" class="input-sm form-control"/>
  </div>
</div>  

The interesting part here are all those Angular related directives. See [(ngModel)] for example. Because Jade will be compiled to HTML during build time, we can pass around the actual bindings as strings and use Jade String interpolation to produce the final, Angular conform Markup.

Combine mixin files

So far we've created a icn and a input mixin, both are located in different files mixins/base.jade and mixins/forms.jade, let's add mixins/index.jade to have a single file, containing all mixins.

// mixins/index.jade
include ./base.jade  
include ./forms.jade  

Now you can refactor your root jade file to just include mixins/index.jade, so you don't have to specify each mixin file.

More complex input files

The amount of HTML compared to the few lines of Jade is immersive. But this difference will become even bigger if you create more powerful mixins. Let's add some nice icons to our input form.
The sample shown input we've created a few seconds ago, should still render without any image. So change the

// mixins/forms.jade
mixin input(slug, title, type, placeholder, model, icon)  
  .form-group
    label.col-sm-2.control-label(for='#{slug}')= title
      .col-sm-7
        if(icon)
          .input-group
            span.input-group-addon
              +icn(icon)
        input.input-sm.form-control(id='#{slug}', type='#{type}', [(ngModel)]="#{model}", placeholder='#{placeholder}')

The indentation is important here. The ending input node is exactly on the same level as the if statement. So it gets rendered no matter if there is an icon or not. Another important piece is the usage of +icn(icon).

Yes, you're right were starting to nest multiple mixins. Imagine that power? Using this mixin is again simple. Change the index.jade to match the following.

// index.jade
include mixins/index.jade

+input('firstName', 'First Name', 'text', 'John', 'customer.name')
+input('email', 'Email', 'email', [email protected]', 'customer.email', 'envelope-o')

Our firstName input remains without an icon. On the other-side will you find a nice looking envelope in front of the email input field. Once again, see the generated markup.

<div class="form-group">  
  <label for="firstName" class="col-sm-2 control-label">First Name</label>
  <div class="col-sm-7">
    <input id="firstName" type="text" [(ngModel)]="customer.name" placeholder="John" class="input-sm form-control"/>
  </div>
</div>  
<div class="form-group">  
  <label for="email" class="col-sm-2 control-label">Email</label>
  <div class="col-sm-7">
    <div class="input-group"><span class="input-group-addon"><i class="fa fa-envelope-o"></i></span></div>
    <input id="email" type="email" [(ngModel)]="customer.email" placeholder="[email protected]" class="input-sm form-control"/>
  </div>
</div>  

Integration in an Angular2 project

First, let's take a look at a simple component responsible for rendering an edit form to allow users change customer's data.

import {Component, OnInit} from 'angular2/core';  
import {CustomerService} from '../../services/customer.service';  
import {RouteParams, ROUTER_DIRECTIVES} from 'angular2/router';

import {Customer} from '../../models/customer';

@Component({
    templateUrl: '/templates/customer/detail.html',
    directives: [ROUTER_DIRECTIVES]
})
export class CustomerDetailComponent implements OnInit {

    private customer: Customer;


    constructor(
        private _customerService: CustomerService,
        private _routeParams: RouteParams
        ) {
        this.customer = new Customer();
    }

    public ngOnInit() {
        this._customerService.getById(this._routeParams.get('id'))
            .subscribe(c=> this.customer = c);
    }

    public save(){
        this._customerService.update(this.customer)
            .subscribe(data=> this._router.navigate(['CustomerList']));
    }

}

This is a regular Angular2 component. There is no need for changing something here. The templateUrl remains ...detail.html

You may ask where all the magic will happen. Well it's inside of a gulp task.

If you've never used gulp or need a refreshment, check out my series of posts on Gulp, it is worth reading it!

Once Gulp is up and running in your Angular2 project, there is only a single new dependency you've to install to have things you need.

$ npm install gulp-jade --save-dev

With respect to the common ng2 project structure and systemJS, you can build html templates to the correct destination by using the following gulp task

gulp.task('private:build-ng2-templates', function(done){  
    return gulp.src('src/templates/**/*.jade')
        .pipe(jade())
        .pipe(gulp.dest('dist/templates'));

});

Hook up this task to the sequence of tasks being executed during a regular build and you're done.

Summary

I know many people bashing about Jade. But to be honest, I don't get why. (Most of the time those people were also bashing my beloved CoffeeScript in the past :D) But in these days we're using TypeScript to make JavaScript work, Less or Sass to make CSS easier and less painful. So why not using Jade to be more productive in markup?

Whats your opinion? Have you tried it with Angular? Do you have the same impressions I've shared here? Or may it adding too much complexity to your projects? Leave a comment.

Comments

comments powered by Disqus