Thinking in components

Why, what and how

Alex Peattie

CTO & co-founder Peg

alex@peg.co

Let's talk about

  • Why use components in the first place?
  • What are components (and what are they not)?
  • How do we use them in practice?

Why?

 

 

  • All major front-end frameworks embracing components (Angular, React, Backbone, Vue etc. etc.)
  • Web components to be added to all browsers by ~2017
  • They will make your life as a dev easier 😄!

Addy Osmani's rules for components: FIRST

  • Focused
  • Independent/Isolated
  • Reusable
  • Small
  • Testable
     

Also a great manifesto for why to use components!

  • Changed code and had it break a different part of the application
  • Tried to add a "minor" improvement only having to unravel a bunch of code you didn't understand
  • Run into a brick wall trying to test your front-end
  • Wanted to open-source a cool part of your app

If you've ever

Components might be what you need!

Case study: Peg

10K lines of JS, large "God" controllers

What?

<ng-include src='navbar.html'></ng-include>
<ng-include src='search-area.html'></ng-include>

<section class='results'>
  <ng-include src='creator.html'
    ng-repeat='creator in creators'>
  </ng-include>
</section>

Components != partials

  • Act like normal HTML elements:

     
  • All the code (HTML, CSS & JS) is packaged up together
     
  • They have inputs & outputs, just like methods
<payment-button>Pay Now!</payment-button>

How?

function starsignForBirthday(day, month) {
  // some clever stuff
  return starsign;
}
starsignForBirthday(24, 8) // "Virgo"
starsignForBirthday(1, 1) // "Capricorn"
starsignForBirthday(10000) // Error
<input id='cc' type='number' pattern='[0-9]{16}'>
<button class='payment-button disabled' type='submit'>
.payment-button {
  background: blue;

  &.success {
    background: green;
  }

  &.failed {
    background: red;
  }

  &.disabled {
    cursor: disabled;
    background: grey;
  }
}
$('#cc').on('keydown', function() {
  var isValid = $(this).valid()
  $('.payment-button').toggleClass('disabled', !isValid)
})

$('.payment-button').on('click', function() {
  var ccNum = $('#cc').val()
  $('.payment-button').text('Paying')

  $.post('https://api.stripe.com/pay', { num: ccNum }, function() {
    $('.payment-button').text('Paid').addClass('success')
  }, function() {
    $('.payment-button').text('Failed').addClass('failed')
  })
})

Credit card valid

No

Yes

Payment
status

Waiting

Processing

Success

Failure

function starsignForBirthday(day, month) {
  return starsign;
}
<payment-button credit-card='...' payment-status='...'>
</payment-button>
paymentButton = {
  bindings: {
    cardNumber: '<',
    paymentStatus: '<'
  }
}

this.paymentStatus = this.paymentStatus || 'waiting'
this.pay = function() {
  this.paymentStatus = 'processing'

  $http.post('http://api.stripe.com/pay', { num: this.cardNumber },
  function() {
    this.paymentStatus = 'success'
  }, function() {
    this.paymentStatus = 'failed'
  })
}

angular.component('<payment-button>', paymentButton) // <- pseudocode
<button ng-class='{ disabled: !component.creditCard.$valid }'
  ng-switch='component.paymentStatus' ng-click='component.pay()'>
  <span ng-switch-when='waiting'>Pay</span>
  <span ng-switch-when='processing'>Paying</span>
  <span ng-switch-when='success'>Paid</span>
  <span ng-switch-when='failure'>Failure</span>
</button>
  • Focused ✅
  • Independent/Isolated
  • Reusable
     
  • Small
  • Testable
     
<payment-button></payment-button>
<payment-button payment-status='success'></payment-button>

expect($('payment-button').text()).toEqual('Paid')

Credit card valid

No

Yes

Payment
status

Waiting

Processing

Success

Failure

<button ng-class='{ disabled: !component.creditCard.$valid }'
  ng-switch='component.paymentStatus'>
$('#cc').on('keydown', function() {
  var isValid = $(this).valid()
  $('.payment-button').toggleClass('disabled', !isValid)
})
<button
  ng-class='{
    disabled: !component.creditCard.$valid && component.paymentStatus === "waiting"
  }'
  ng-switch='component.paymentStatus'>
$('#cc').on('keydown', function() {
  var isValid = $(this).valid()

  // Horrible hack ⬇
  var paymentWaiting = $('.payment-button').text() === "Pay"
  $('.payment-button').toggleClass('disabled', !isValid && paymentWaiting)
})

$('.payment-button').on('click', function() {
  $(this).removeClass('disabled')
})
<favourite-button creator='zoella'>
  <button-toggler toggled='creator.favourited'>
    <button ng-click='add()'>Favourite</button>
    <button ng-click='remove()'>Favourited</button>
  </button>

  <notification position='bottom-center' notify-status='request.status'
   notify-status-when='200'>
    {{ creator.name }} has been favourited
  </notification>

  <notification position='bottom-center' notify-status='request.status'
   notify-status-when='4xx'>
    Oops something went wrong!
  </notification>
</favourite-button>
<favourite-button creator='zoella'>
  <button-toggler toggled='creator.favourited'>
  </button>

  <notification position='bottom-center'
   notify-status='request.status'
   notify-status-when='200'>

Favourite button
Responsible for favouriting/unfavouriting creators
Inputs: the creator in question

 

Button toggler
Responsible for toggling the visibilty of its child buttons
Inputs: is it currently toggled?

Notification
Responsible for notifying the user about successful/failed HTTP requests
Inputs: the HTTP request status it's watching, which code triggers the notification to appear

Conclusion

  • Components are the future
  • They help you avoid spaghetti code
  • Good components should be FIRST (focused, isolated, reusable, small, testable)
  • Think about components like methods (thinking about inputs and outputs)
  • Keep components small by making them work together (composing them)

Thank you!

any questions?

PS - slides are online at:

https://slides.com/alexpeattie/components