angular-1.x-right-way



angular-1.x-right-way

0 1


angular-1.x-right-way


On Github timruffles / angular-1.x-right-way

Angular 1.x

@timruffles @sidekicksrc

The adoption cycle

You've just finished a project

It got pretty ugly

Retrospectives :(

But wait... there...

A new hope!

A new framework!!!

Your toy apps work so well

"todo lists were never this easy before!"

You've convinced my colleagues

"omg data-binding!"

You've got buy-in from your boss

"made by Google you say?"

It's decided... the next project is in... Angular!!!

(or, please no, a rewrite)

Early features are a success

"desk beers?"

But slowly...

The beast creeps in

There are parts of the project you don't want to look at

It's all suddenly looking like

This was my story

So, I learned something (again)

Turns out web-apps are hard

Turns out design is still a thing

I've written a few Angular apps now

What did I learn?

Angular isn't a framework

???

Not big enough to be

  • model layer, nope
  • structuring apps, nope
  • large scale architecture, nope

Angular leaves big decisions up to you

Choose your own adventure framework

What happened when I assumed Angular was 'done'?

Bad defaults

e.g folder structure

The default

app
  controllers
    someCtrl.js

    # 100 more...

  directives
    someDirective.js

    # 100 more...

  templates

Monolithic

Low locality

  • related things close
  • a very under-appreciated virtue (compared to say, 'modularity')

No conventions

Much happier with...

app
  features
    home-page
      index.html
      index.js
    account
      user-widget
        index.html
        index.js
  components
    breadcrumb
      index.js
      index.html
  common
    resource
      index.js

Features folder

  • page or modal level
  • mix of reusable things from components and one offs

Conventions

  • more important the bigger the app, reduce cognitive load
  • index.js for a top-level
  • index.html for a directive's primary template

I learned Angular is small

So I still had to make decisions

Like a framework author

Framework

  • conventions
  • support good design (modularity, locality etc)
  • built it up towards your problem

I also realised...

It's lucky Angular isn't a framework

As some of Angular

Is slightly...

slightly completely

AWFUL

:(

Module system

Why?

Confusing

Factory, service, provider, constant

Yuk

Yuk

Singleton

Implementation leaks into user's designs

  • docs are at fault

Proprietary

Reuse

"You can package code as reusable modules."

Reuse

"You can package code as reusable modules."

"...as long as all your code is Angular"

Missing features

Lazy-loading

Building

So I asked myself

"Can I keep Angular and lose its module system?"

Yes

Browserify

  • npm in the browser
  • CommonJS format

My structure:

exports.controllers = {
  "SearchCtrl": SearchCtrl,
};

exports.directives = {};

exports.routes = {
  "/": {
    template: require("./search.html")
  },
}

function SearchCtrl() {
  this.message = "hello"
}

Get setup

grunt grunt-browerify grunt-browerify-watch grunt-browerify-angular grunt-browerify-templates grunt-browerify-biscuits grunt-browerify-lazy-load

</joke>

The stack

npm install --save browserify watchify stringify

Unwrap Angular's module stuff

require("angular");
require("angular-route");

var _ = require("lodash");

angular.module('events', ["ngRoute"])
.config(function($controllerProvider, $compileProvider, $routeProvider) {

  exports.controller = function(controller, name) {
    $controllerProvider.register(name, controller);
  }
  // same for filter, routes, etc
  // can use these for lazy-load

  var deps = [
    require("../create-event"),
    require("../search-events"),
  ];

  deps.forEach(function(module) {
    _.each(module.controllers || {}, exports.controller)
    // same for directives, filters etc
  });
});

Workflow

watchify -r stringify -o dist/app.js features/core/index.js

$resource

ActiveRecord: bad idea or worst idea ever?

AR is good at CRUD

AR is about persistence

  • not: forms
  • or domain logic

Putting everything in AR

Summons cthulu

I give people one piece of advice: make smaller things

Sandi Metz (we can learn things from Rubyists)

I'm happiest with

  • ServerCommand object, to encapsulate transaction
  • using $http with objects and arrays for most data

Functional programming

Better to have 100 functions for one data structure than 10 functions for 10 data structures."

  • Alan J. Perlis

Prefer $http and promises

  • keep data simple

So I learned to edit Angular

And this is generally applicable

And also why we should

Write smaller things

If we want adoption and longevity

Big things have lots of ego

So they don't play well with others

Others stop talking to them

...and they die alone

dojo Ext.JS GWT

But equally, I realised...

Brilliant bits of Angular are underused

Controller as alias syntax is required

Explicit is better than implicit.

Zen of Python, PEP 20, Tim Peters

$scope is a big shared namespace

  • write smaller things
## Scope vars are implicit

- who owns them?
<div ng-controller="GraphCtrl"
  <table >
    <tr ng-repeat="row in data">
    </tr>
  </table>

  <div class='line-graph-header'>
    <h3>{{ lineGraphTitle }}</h3>
  </line-graph>
</div>

Directives are your friends

Prefer them to controllers

More explicit

More abstraction

More aligned with the future

For example

<data-table
  controller-as="dataTable"
  width="800 + ctrl.padding"
  >
</data-graph>

<line-graph
  x-scale="ctrl.scale"
  y-scale="ctrl.yScale"
  data="dataCtrl.series"
  width="800 + ctrl.padding"
  on-inspect="dataTable.data = $series"
  >
</line-graph>
module.exports = function() {

  return {
    restrict: "E",
    controllerAs: "ctrl",
    bindToController: true,
    controller: LineGraphCtrl,
    link: lineGraph,
    scope: {
      xScale: "=",
      // ...
      onInspect: "&"
    }
  }

}
function lineGraph(scope, el, attrs, ctrl) {

  // ...
  points.on("click", function(d, i) {
    ctrl.onInspect({
      $series: d,
    });
  })
}

Directives are great, but if you add

ngModelController

...they're AMAZING

Show of hands

  • who's used it with custom directives?
<my-widget ng-model="something">

What is ng-model

  • mostly ngModelController

ngModelController is a form object

Most widgets are really <inputs>

  • user editing
  • have state
  • why reinvent the wheel?

Plays well with <form>

For example...

<input type=time> is boring

What about a big clock face to drag?

Can ng-model be used on that?

Let's try

 <form name=timeForm>

  <div class='extended-clock'>
    <svg width=1000 height=1000 >
      <g clock-input ng-model=item.time name=time></g>
        <text x=500 y=500>
          {{ item.time | date:"shortTime" }}
        </text>
        <text x=500 y=550 ng-click="toggle()">
          {{ ticking ? "Stop" : "Start" }}
        </text>
    </svg>
  </div>

</form>
function clock() {
  return {
    // we require an instance of ng-model to work
    require: "ngModel",
    link: function(scope, el, attrs, ngModel) {

      // initialize the non-angular widget, and
      // pass in our model updating fn
      var clock = donutClock({
        onInput: view2model,
        el: el[0],
      });

      // when angular detects a change to the model,
      // we update our widget
      ngModel.$render = model2view;

      function model2view(time) {
        clock.set(ngModel.$viewValue);
      }

      function view2model(fromView) {
        ngModel.$setViewValue(fromView);
      }

    }
  }
}

Example

It was a long path

I learned...

That making apps is still hard

That Angular wants me to extend it

That Angular is ok with an open relationship

We don't have to do everything together

And finally, that I should RTFM

  • (though... the docs could still be a lot better)

Questions?

  • Build Angular into your framework
  • Replace the bad (modules, $resource)
  • Use the good (directives, ngModel)

@timruffles

Creative commons work