Skip to content

Instantly share code, notes, and snippets.

@MichaelBailly
Created June 6, 2016 08:47
Show Gist options
  • Select an option

  • Save MichaelBailly/3fe7e207f6dd50f43ee8171158dfcdf1 to your computer and use it in GitHub Desktop.

Select an option

Save MichaelBailly/3fe7e207f6dd50f43ee8171158dfcdf1 to your computer and use it in GitHub Desktop.

Most of those rules come from Angular Style Guide (https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md).

In this document, we call angular things the different parts of an angular module: constants, variables, services, factories, controllers, directives, filters, and components.

IIFE

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#iife

Use IIFE to enclose any code

Always use a prefix for Angular things

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#style-y020

Because Angular 1 does not support namespaces, we have to namespace our services, controllers, directives... names. Our application prefix is esn. Moreover, any esn module should add a module prefix. This module prefix can be shortened, as it's already expensive to have 2 prefixes. Eg: esnCalEvent, esnUniMessage, ...

files layout

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#naming

Use folder by feature

We use folder-by-feature (https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#folders-by-feature-structure) layout. Every angular thing (component, directive, controller, service, module config, router) is in its own file. By convention, we use the following file name: [dashed angular thing name].[angular thing type].js. Moreover, we do not make distinctions between services, values and factories. Examples:

.factory('esnCalCalendars') => calendars.service.js
.directive('esnChatMessageDisplay') => message-display.directive.js

We use [module name].routes.js for everything related to the frontend router configuration. We use [module name].config.js for everything else related to application configuration. We use [module name].run.js for everything related to the run part of the application. Finally, we use a layout folder for everything related to the more global application layout.

That gives us the following structure for a classic ESN module, namely 'linagora.esn.example' :

example.module.js
example.config.js
example.routes.js
example.run.js
somefeature/
somefeature/inbox.service.js
somefeature/inbox-view.component.js
somefeature/inbox-message.component.js
otherfeature/
otherfeature/folder-view.component.js
layout/
layout/inbox-sidebar.component.js

There should be a direct relationship between angular things and file names. As such, looking a component 'esnExampleInboxMessage', I'm sure that the component is in the example module, and its file is 'inbox-message.component.js'.

unit test files are alongside the source file

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#organizing-tests

Because it's likely that the unit test files will evolve when the source file evolve, puting them side by side decrease the folder lookup that the developer should do to get the test file out of the source file. Test files are named .spec.js. thus the previous layout becomes:

example.module.js
example.config.js
example.routes.js
example.run.js
somefeature/
somefeature/inbox-message.component.js
somefeature/inbox-message.component.spec.js
somefeature/inbox.service.js
somefeature/inbox.service.spec.js
somefeature/inbox-view.component.js
somefeature/inbox-view.component.spec.js
otherfeature/
otherfeature/folder-view.component.js
layout/
layout/inbox-sidebar.component.js

subdir when file count becomes too high

Let's imagine the module got 20 components... In that case, move the components in a components subfolder.

example.module.js
example.config.js
example.routes.js
example.run.js
somefeature/
somefeature/inbox.service.js
somefeature/inbox.service.spec.js
somefeature/components/inbox-message.component.js
somefeature/components/inbox-message.component.spec.js
somefeature/components/inbox-view.component.js
somefeature/components/inbox-view.component.spec.js
somefeature/components/...
otherfeature/
otherfeature/folder-view.component.js
layout/
layout/inbox-sidebar.component.js

The golden number in the style guide is 7 : when there is 7 or more of "angular thing" in a module, create a folder for the "angular thing".

Angular things code

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#named-vs-anonymous-functions https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#function-declarations-to-hide-implementation-details

Always use the order: declaration, definition, implementation :

'use strict';

// declaration
.factory('esnExampleInbox', esnExampleInbox);

// definition
function esnExampleInbox(svc1, svc4) {

  return {
    load: load,
    refresh: refresh
  };

  // implementation
  function load() {
    ...
  }

  function refresh() {
    ...
  }
}

Directives

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#controlleras-view-syntax https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#controlleras-with-vm

Use the bindTocontroller, controller as to use controllers

'use strict';

.directive('esnExampleStar', esnExampleStar);

function esnExampleStar(svc1, svc4) {

  return {
    controller: 'esnExampleStarController',
    controllerAs: 'starCtl',
    bindTocontroller: true,
    link: link
  };

  function link(scope, elem, attrs) {
    ...
  }
}

embed directive controllers

As far as possible, embed controllers into the directive definition

'use strict';

.directive('esnExampleStar', esnExampleStar);

function esnExampleStar(svc1, svc4) {

  return {
    controller: esnExampleStarController,
    controllerAs: 'starCtl',
    bindTocontroller: true
  };
}

function esnExampleStarController(chewing, gum) {
  ...
}

controllers

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#function-declarations-to-hide-implementation-details

get rid of the $scope

We use the self keyword to keep a reference to the object context, so we won't use "vm" as stated in the guide, and stick to self.

In controllers, always expose bindable members (the things the controller is exposing) on top of the function:

'use strict';

.controller('esnExampleStarController', esnExampleStarController);

function esnExampleStarController(svc1, svc4) {
  var self = this;

  self.vote = vote;
  self.votes = [];

  function vote() {
    ...
  }

}

You'll still need the $scope when you want to listen on an event ($on), or watch ($watch) some variable.

logic-less controllers

https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#defer-controller-logic-to-services https://toddmotto.com/rethinking-angular-js-controllers/

Make as dumb as possible controllers. Defer all logic and data holding into services.

components

Use components instead of directives whenever that's possible. Angular documentation (https://docs.angularjs.org/guide/component) is really clear as to when not using a component:

  1. when you need access to the DOM
  2. when your directive needs to be triggered by an attribute

Component controllers

As far as possible, embed component controllers into the component file

'use strict';

.component('esnExampleStar', {
  controller: esnExampleStarController,
  controllerAs: 'starctl',
  templateUrl: '/some/where.html'
});

function esnExampleStarController(svc1, svc4) {
  var self = this;

  self.vote = vote;
  self.votes = [];

  function vote() {
    ...
  }

}

routing

Use "Components as route templates" (https://docs.angularjs.org/guide/component) to define your routes.

// wrong
.state('yolo', {
  templateUrl: '/some/where.html',
  controller: 'someController'
})

// good
.state('yolo', {
  template: '<some-component></some-component>'
})
@GrahamLinagora
Copy link

Just a question : we planned to have an automatic taks to build a dist we full notation for angular dependencies if I recall well. Will this more complicated layout be handled well by ng-annotate or its equivalent ?

@strokyl
Copy link

strokyl commented Jun 7, 2016

In your example for bindToController, I think you should add a isolated scope with some binding in the directive definition. Because if I understood well bindToController make sens only in this case.

@strokyl
Copy link

strokyl commented Jun 7, 2016

I am ok for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment