├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Oren Farhi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Angular 1.5 - ES6/ES2015 Style Guide 3 | 4 | ## Endorsements 5 | The idea for this styleguide is inspired from [john papa's angular style guide](https://github.com/johnpapa/angular-styleguide) which is focused on angular version 1.x, written with ES5. 6 | Some sections are currently taken from john papa's styleguide and are subject to change in the future (as this stylguide grows). 7 | This styleguide is also inspired from [@angularclass's boilerplates](http://github.com/angularclass) for angular & es2015. 8 | 9 | ## Purpose 10 | *Opinionated Angular.js written with ES6/ES2015 and module loading style guide for teams by [@orizens](//twitter.com/orizens)* 11 | 12 | If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular.js applications v 1.x with ES2015 and module loading, then this is it. These styles are based on my development experience with [Angular.js](//angularjs.org) and the [Echoes Player](http://echotu.be), [Open Source Project Development (currently the es2015 branch)](https://github.com/orizens/echoes/tree/es2015). 13 | 14 | The purpose of this style guide is to provide guidance on building Angular applications with ES2015 and module loading by showing the conventions I use and in order to prepare angular v 1.x code to angular v 2. 15 | 16 | ## Inspirations 17 | [NG6-Starter](https://github.com/AngularClass/NG6-starter) 18 | [Angular, Gulp, Browserify](https://github.com/jakemmarsh/angularjs-gulp-browserify-boilerplate) 19 | 20 | ----------------------- 21 | ## NEW Quick Start with SuperNova Starter 22 | Start a new angular project with [SuperNova Starter](https://github.com/orizens/supernova-angular-1.5.x-es6-starter) which is based on this styleguide. 23 | 24 | 25 | ## See the Styles in a Sample App 26 | This guide is based on my [open source Echoes Player application](http://echotu.be) that follows these styles and patterns. You can clone/fork the code at [echoes repository](https://github.com/orizens/echoes). 27 | 28 | ##Translations 29 | TBD 30 | 31 | ## Table of Contents 32 | 33 | 1. [Single Responsibility](#single-responsibility) 34 | 1. [Modules](#modules) 35 | 1. [Components](#components) 36 | 1. [Services](#services) 37 | 1. [Testing](#testing) 38 | 1. [Testing Guidelines](#testing-guidelines) 39 | 1. [Write Tests with Stories](#write-tests-with-stories) 40 | 1. [Testing Library](#testing-library) 41 | 1. [Test Runner](#test-runner) 42 | 1. [Testing Controllers](#testing-controllers) 43 | 1. [Organizing Tests](#organizing-tests) 44 | 1. [Comments](#comments) 45 | 1. [ES Lint](#es-lint) 46 | 1. [Use an Options File](#use-an-options-file) 47 | 1. [Routing](#routing) 48 | 1. [Contributing](#contributing) 49 | 1. [License](#license) 50 | 1. [Copyright](#copyright) 51 | 52 | ## Single Responsibility 53 | 54 | ### Rule of 1 55 | 56 | - Define 1 class per file. 57 | 58 | The following example defines the several classes in the same file. 59 | 60 | ```javascript 61 | // now-playlist.services.js 62 | /* avoid */ 63 | export class NowPlaylistProvider {} 64 | export class NowPlaylistCreator {} 65 | ``` 66 | 67 | The same classes are now separated into their own files. 68 | 69 | ```javascript 70 | /* recommended */ 71 | 72 | // now-playlist-provider.service.js 73 | export default class NowPlaylistProvider {} 74 | ``` 75 | 76 | ```javascript 77 | /* recommended */ 78 | 79 | // now-playlist-creator.service.js 80 | export default class NowPlaylistCreator {} 81 | ``` 82 | 83 | **[Back to top](#table-of-contents)** 84 | 85 | ## Modules 86 | 87 | Use [Proposed Loader Specification](https://whatwg.github.io/loader/) (Former ES6/ES2015) 88 | 89 | **Why?**: it assists in bundling the app and promotes the seperation of concerns. In Addition, Angular 2 is also based on Loader's standards. 90 | 91 | ```javascript 92 | import NowPlaylist from './now-playlist'; 93 | import { NowPlaylistComponent } from './now-playlist.component'; 94 | ``` 95 | 96 | ### Module Loaders Tools 97 | 98 | - [System.js](https://github.com/systemjs/systemjs) 99 | - [Browserify](http://browserify.org/) 100 | - [Webpack](https://webpack.github.io/) 101 | - [Typescript CLI](http://www.typescriptlang.org/) 102 | - [tsd is a type definition package manager](http://definitelytyped.org/tsd/) 103 | 104 | ### Module Folder Structure 105 | 106 | - each module directory should be named with a dash seperator (kebab notation). 107 | 108 | **Why?**: it follows the web-component notation of seperating a tag name with a dash. It is easier to read and follow in the code editor. 109 | 110 | ``` 111 | // name of directory for component 112 | - now-playlist 113 | ``` 114 | 115 | ### Module files 116 | 117 | - each module should contain the following: 118 | 1. index.js - it should contain: 119 | 1. module-name.component.js - a component (directive) file defintion with class as a controller 120 | 1. template: 121 | 1. Long html template: an html template file 122 | 2. Small html template: inline backtick template in component file 123 | 1. a spec file 124 | 125 | #### index.js - module entry point 126 | 127 | should contain: 128 | * the module defintion 129 | * components/directives angular wrappers 130 | * its dependencies 131 | * config phase & function 132 | * angular's entities wrappers - services, factories, additional components/directives, other.. 133 | 134 | **Why?**: this is the file where we can hook vanilla js files into angular. This is the main point to see what this module is composed of. 135 | 136 | ```javascript 137 | import angular from 'angular'; 138 | import AngularSortableView from 'angular-sortable-view/src/angular-sortable-view.js'; 139 | import { NowPlaylistComponent } from './now-playlist.component'; 140 | 141 | export default angular.module('now-playlist', [ 142 | 'angular-sortable-view' 143 | ]) 144 | .config(config) 145 | .component(NowPlaylistComponent.selector, NowPlaylistComponent) 146 | // OR - if you defined controllerAs similary to the component's element: 147 | .component(NowPlaylistComponent.controllerAs, NowPlaylistComponent) 148 | ; 149 | // optional 150 | /* @ngInject */ 151 | function config () { 152 | 153 | } 154 | ``` 155 | #### module-name.component.js - component defintion with controller class 156 | 157 | this file should contain: 158 | 1. the component/directive **definition** as a literal object, with export. 159 | 2. the **"controller"** property should be defined as a **class**. 160 | 3. inlined with template string es6/es2015 syntax. 161 | 4. if template is huge (30 lines), consider: 162 | 4.1 break the template to smaller reusable components. 163 | 4.2 fallback to a template that should be imported from external file. 164 | 165 | **Why?**: It's easy to understand the bigger picture of this component: what are the inputs and outputs retrieved from scope. Everything is in one place and easier to reference. Moreover, this syntax is similar to angular 2 component definion - having the component configuration above the "controller" class. 166 | 167 | ```javascript 168 | import template from './now-playlist.tpl.html'; 169 | 170 | export let NowPlaylistComponent = { 171 | selector: 'nowPlaylist', 172 | template, 173 | // if using inline template then use template strings (es2015): 174 | template: ` 175 |
176 | .... 177 |
178 | `, 179 | // Optional: controllerAs: 'nowPlaylist', 180 | bindings: { 181 | videos: '<', 182 | filter: '<', 183 | nowPlaying: '<', 184 | onSelect: '&', 185 | onRemove: '&', 186 | onSort: '&' 187 | }, 188 | controller: class NowPlaylistCtrl { 189 | /* @ngInject */ 190 | constructor () { 191 | // injected with this.videos, this.onRemove, this.onSelect 192 | this.showPlaylistSaver = false; 193 | } 194 | 195 | removeVideo($event, video, $index) { 196 | this.onRemove && this.onRemove({ $event, video, $index }); 197 | } 198 | 199 | selectVideo (video, $index) { 200 | this.onSelect && this.onSelect({ video, $index }); 201 | } 202 | 203 | sortVideo($item, $indexTo) { 204 | this.onSort && this.onSort({ $item, $indexTo }); 205 | } 206 | } 207 | } 208 | ``` 209 | 210 | ## Components (as of Angular 1.5) 211 | * Use ES2015 class for controller 212 | * Use **Object.assign** to expose injected services to a class methods (make it public) 213 | 214 | **Why?** - ```Object.assign``` is a nice one liner usage for overloading services on "this" context, making it available to all methods in a service (i.e., **playVideo** method). 215 | **Why?** - it follows the paradigm of angular 2 component class (controller), where you would define "public"/"private" on constructor's argument in order to create references on "this" context. 216 | 217 | ```javascript 218 | export let NowPlaylistComponent = { 219 | // .... 220 | controller: class YoutubeVideosCtrl { 221 | /* @ngInject */ 222 | constructor (YoutubePlayerSettings, YoutubeSearch, YoutubeVideoInfo) { 223 | Object.assign(this, { YoutubePlayerSettings, YoutubeVideoInfo }); 224 | this.videos = YoutubePlayerSettings.items; 225 | 226 | YoutubeSearch.resetPageToken(); 227 | if (!this.videos.length) { 228 | YoutubeSearch.search(); 229 | } 230 | } 231 | 232 | playVideo (video) { 233 | this.YoutubePlayerSettings.queueVideo(video); 234 | this.YoutubePlayerSettings.playVideoId(video); 235 | } 236 | 237 | playPlaylist (playlist) { 238 | return this.YoutubeVideoInfo.getPlaylist(playlist.id).then(this.YoutubePlayerSettings.playPlaylist); 239 | } 240 | } 241 | } 242 | ``` 243 | 244 | ### Component Options 245 | #### controllerAs 246 | if starting a new component, use the default **$ctrl**. 247 | if migrating and want to keep the **controllerAs** custom name, define a 'selector' property the same as the camelCased controllerAs value, which will be used with the **angular.component** factory. 248 | 249 | **WHY?**: 'selector' in angular 2 indicates the "css" selector which will be used in html. When migrating to Angular 2, it will be easier to transform it to the kebab case. 250 | 251 | ``` 252 | // when controllerAs isn't explicitly defined 253 | // and used as the default "$ctrl" 254 | export let NowPlaylistComponent = { 255 | selector: 'nowPlaylist' 256 | ... 257 | } 258 | 259 | // when controllerAs is explicitly defined 260 | // and NOT used as the default "$ctrl" 261 | export let NowPlaylistComponent = { 262 | // when migrating to angular 2 => selector: 'now-playlist' 263 | selector: 'nowPlaylist' 264 | controllerAs: 'nowPlaylist' 265 | } 266 | ``` 267 | 268 | ## Services 269 | It's a best practice to write all logic in services. 270 | 271 | **Why?**: logic can be reused in multiple files. logic can be tested easily when in a service object. 272 | 273 | ### angular.service 274 | Use **angular.service** api with a class for a service. 275 | 276 | **Why?**: Services in angular 2 are classes. it'll be easier to migrate the code. 277 | 278 | ### angular.factory 279 | use **angular.service** instead. 280 | 281 | ### angular.provider 282 | export a function as provider as in angular with ES5. 283 | 284 | ## Application Structure 285 | 286 | ### Overall Guidelines 287 | [-] - a folder 288 | an * - a file 289 | 290 | ``` 291 | [-] src 292 | [-] components 293 | [-] core 294 | [-] components 295 | [-] services 296 | [-] css 297 | * app.js 298 | * index.html 299 | ``` 300 | #### src/components 301 | This directory includes **SMART components**. It consumes the **app.core** services and usually doesn't expose any api in attributes. 302 | It's like an app inside a smart phone. It consumes the app's services (ask to consume it) and knows how to do its stuff. 303 | 304 | Usage of such smart component is as follows: 305 | 306 | ```html 307 | 308 | ``` 309 | 310 | Example definition in **index.js** can be: 311 | 312 | ```javascript 313 | import angular from 'angular'; 314 | import AppCore from '../core'; 315 | import { NowPlayingComponent } from './now-playing.component.js'; 316 | import nowPlaylist from './now-playlist'; 317 | import nowPlaylistFilter from './now-playlist-filter'; 318 | import playlistSaver from './playlist-saver'; 319 | import YoutubePlayer from '../youtube-player'; 320 | 321 | export default angular.module('now-playing', [ 322 | AppCore.name, 323 | nowPlaylist.name, 324 | nowPlaylistFilter.name, 325 | playlistSaver.name, 326 | YoutubePlayer.name 327 | ]) 328 | .config(config) 329 | .component(NowPlayingComponent.selector, NowPlayingComponent) 330 | ; 331 | /* @ngInject */ 332 | function config () { 333 | 334 | } 335 | ``` 336 | 337 | #### src/core/components 338 | This directory includes system wide **DUMB components**. A Dumb Component gets data and fire events. It is communicating only through events. 339 | This is example: 340 | ```javascript 341 | 342 | ``` 343 | 344 | ## Testing 345 | Unit testing helps maintain clean code, as such I included some of my recommendations for unit testing foundations with links for more information. 346 | 347 | ### Testing Guidelines 348 | These are guidelines to follow for setup an environment for testing. 349 | 350 | #### Write Tests with Stories 351 | 352 | - Write a set of tests for every story. Start with an empty test and fill them in as you write the code for the story. 353 | 354 | **Why?**: Writing the test descriptions helps clearly define what your story will do, will not do, and how you can measure success. 355 | 356 | ```javascript 357 | it('should have a collection of media items', function() { 358 | // TODO 359 | }); 360 | 361 | it('should find fetch metadata for a youtube media', function() { 362 | // TODO 363 | }); 364 | 365 | ``` 366 | 367 | #### Testing Library 368 | 369 | - Use [Jasmine](http://jasmine.github.io/) or [Mocha](http://mochajs.org) for unit testing. 370 | 371 | **Why?**: Both Jasmine and Mocha are widely used in the Angular community. Both are stable, well maintained, and provide robust testing features. 372 | 373 | Note: When using Mocha, also consider choosing an assert library such as [Chai](http://chaijs.com). I prefer Mocha. 374 | 375 | #### Test Runner 376 | 377 | - Use [Karma](http://karma-runner.github.io) as a test runner. 378 | 379 | **Why?**: Karma is easy to configure to run once or automatically when you change your code. 380 | 381 | **Why?**: Karma hooks into your Continuous Integration process easily on its own or through Grunt or Gulp. 382 | 383 | **[Back to top](#table-of-contents)** 384 | 385 | ### Testing Controllers 386 | Since controllers are written as classes, unit tests should be written to check functionality. 387 | Follow this post for [testing angular 1.x components written with ES2015](http://orizens.com/wp/topics/testing-angular-1-x-es2015-component-with-jasmine/) 388 | Steps to follow: 389 | 390 | 1. import the component javascript file 391 | 2. setup a mocked module 392 | 3. setup spies (if needed) 393 | 4. use ```$controller``` to instanciate a new controller. 394 | 1. 1st argument is the component's controller ("class"). 395 | 2. 2nd argument is an object with a new scope and possibly anh mocked objects/spies from previous steps. 396 | 5. use ```scope.$digest``` to apply compilation and mock an "angular" data digest cycle (optional in some cases - TBD) 397 | 398 | ### Organizing Tests 399 | 400 | - Place unit test files (specs) side-by-side within the component's code. 401 | - Place mocks in a **tests/mocks** folder 402 | 403 | **Why?**: Unit tests have a direct correlation to a specific component and file in source code. 404 | **Why?**: Mock files (json) should be agnostic to the component which is using them. Multiple components specs might use the same jsom mocks. 405 | 406 | **[Back to top](#table-of-contents)** 407 | 408 | ## Comments 409 | TBD 410 | 411 | ## ES Lint 412 | 413 | ### Use an Options File 414 | - Use [eslint.org](http://eslint.org/) to define es2015 support 415 | - Use **.eslintrc.json** file for linting and support es2015 features 416 | 417 | **Why?**: Provides a first alert prior to committing any code to source control. 418 | **Why?**: Provides consistency across your team. 419 | 420 | ```javascript 421 | { 422 | "parserOptions": { 423 | "sourceType": "module", 424 | "ecmaVersion": 6, 425 | "ecmaFeatures": { 426 | "modules": true, 427 | "arrowFunctions": true, 428 | "blockBindings": true, 429 | "destructuring": true, 430 | "classes": true 431 | } 432 | }, 433 | "rules": { 434 | "indent": [ 435 | "error", 436 | 2 437 | ], 438 | "quotes": [ 439 | 2, 440 | "single" 441 | ], 442 | "linebreak-style": [ 443 | 2, 444 | "unix" 445 | ], 446 | "semi": [ 447 | 2, 448 | "always" 449 | ] 450 | }, 451 | "env": { 452 | "es6": true, 453 | "browser": true, 454 | "jasmine": true, 455 | "commonjs": true 456 | }, 457 | "globals": { 458 | "angular": true, 459 | "inject": true 460 | }, 461 | "extends": "eslint:recommended" 462 | } 463 | ``` 464 | More To Come... 465 | 466 | **[Back to top](#table-of-contents)** 467 | 468 | ## Routing 469 | TBD 470 | 471 | ## Contributing 472 | 473 | 1. Open an issue for discussion 474 | 2. Create a pull request to suggest additions or changes 475 | 476 | ## License 477 | 478 | Share your thoughts with an issue or pull request 479 | 480 | ### Copyright 481 | 482 | Copyright (c) 2015-2016 [Oren Farhi](http://orizens.com) 483 | 484 | **[Back to top](#table-of-contents)** 485 | --------------------------------------------------------------------------------