├── 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 |
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 |
--------------------------------------------------------------------------------