├── README.md
├── assets
├── badge.svg
├── ide-tabs.png
├── logo.png
└── logo.svg
└── old
└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 |
3 | ## Angular 2 Style Guide
4 |
5 | [**Official style guide**](https://angular.io/guide/styleguide)
6 |
7 | Together with the Angular core team and John Papa, we're working on an official style guide which incorporates all the best practices from:
8 |
9 | - Practices discovered during the internal usage of Angular 2 in Google.
10 | - The previous version of this style guide, which can [be found here](./old/README.md).
11 | - John Papa's AngularJS 1.x and Angular 2 [style guides](https://github.com/johnpapa/angular-styleguide).
12 |
13 | The previous version of the style guide can be found [here](./old/README.md).
14 |
15 | The official style guide can be found [here](https://angular.io/guide/styleguide).
16 |
17 |
--------------------------------------------------------------------------------
/assets/badge.svg:
--------------------------------------------------------------------------------
1 |
2 |
311 |
--------------------------------------------------------------------------------
/assets/ide-tabs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgechev/angular2-style-guide/e135b4a788bd3dca6977d9cd0884447cfec58624/assets/ide-tabs.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgechev/angular2-style-guide/e135b4a788bd3dca6977d9cd0884447cfec58624/assets/logo.png
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
176 |
--------------------------------------------------------------------------------
/old/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 |
3 | ##  Angular 2 Style Guide
4 |
5 | [](https://github.com/mgechev/angular2-style-guide)
6 | [](https://gitter.im/mgechev/angular2-style-guide?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 | The purpose of the following style guide is to present a set of best practices and style guidelines for the development of Angular 2 applications.
9 | If you are looking for an opinionated style guide for syntax, conventions, and structuring Angular 2 applications, then you can step in!
10 |
11 | **Disclaimer: This document is an alpha version which means that some of the guidelines will change and new ones will be added.**
12 |
13 | You are welcome to join the discussion of the best [practices here](https://github.com/mgechev/angular2-style-guide/issues).
14 |
15 | The guidelines described below are based on:
16 |
17 | 1. Angular 2 [source code](https://github.com/angular/angular).
18 | 2. Suggestions by [Miško Hevery](https://github.com/mhevery) from his technical review of "[Switching to Angular 2](https://www.packtpub.com/web-development/switching-angular-2)".
19 | 3. My own development experience working in a team on large-scale Angular 2 application.
20 | 4. [John Papa's AngularJS 1.x style guide](https://github.com/johnpapa/angular-styleguide), for being consistent with the directory structure and testing across the different major versions of the framework.
21 |
22 | [Codelyzer](https://github.com/mgechev/codelyzer) makes sure your project is following the Angular 2 Style Guide using static-code analysis. It is already integrated in the following projects:
23 |
24 | - [angular-cli](https://github.com/angular/angular-cli)
25 | - [angular2-seed](https://github.com/mgechev/angular2-seed)
26 | - [angular2-webpack-starter](https://github.com/AngularClass/angular2-webpack-starter)
27 |
28 | ## Table of Contents
29 |
30 | 1. [Directory Structure](#directory-structure)
31 | 2. [Directives and Components](#directives-and-components)
32 | 3. [Services and Dependency Injection](#services-and-dependency-injection)
33 | 4. [Pipes](#pipes)
34 | 5. [Routing](#routing)
35 | 6. [Forms](#forms)
36 | 7. [Reusable libraries](#reusable-libraries)
37 | 8. [Testing](#testing)
38 | 9. [Change Detection](#change-detection)
39 | 10. [TypeScript specific practices](#typescript-specific-practices)
40 | 11. [ES2015 and ES2016 specific practices](#es2015-and-es2016-specific-practices)
41 | 12. [ES5 specific practices](#es5-specific-practices)
42 | 13. [Tooling](#tooling)
43 | 14. [License](#license)
44 |
45 | ## Directory Structure
46 |
47 | * Group files by the [bounded context](http://martinfowler.com/bliki/BoundedContext.html) they belong to. When a context (directory, for instance) grows to contain more than 9 files, start to consider creating a separate context by-type for them. Your threshold may be different, so adjust as needed:
48 |
49 | ```
50 | .
51 | ├── admin
52 | │ ├── home-dashboard.component.ts
53 | │ ├── home-dashboard.component.html
54 | │ ├── home-dashboard.component.css
55 | │ ├── home-dashboard.component.spec.ts
56 | │ ├── login.component.ts
57 | │ ├── login.component.spec.ts
58 | │ ├── admin.model.ts
59 | │ ├── user-management.service.ts
60 | │ ├── order-management.service.ts
61 | │ └── index.ts
62 | │── shop
63 | │ ├── components
64 | │ │ ├── edit-profile.component.ts
65 | │ │ ├── edit-profile.component.html
66 | │ │ ├── edit-profile.component.css
67 | │ │ ├── edit-profile.component.spec.ts
68 | │ │ ├── home.component.ts
69 | │ │ ├── home.component.spec.ts
70 | │ │ ├── home.component.html
71 | │ │ ├── register.component.ts
72 | │ │ └── register.component.spec.ts
73 | │ ├── models
74 | │ │ ├── shopping-cart.model.ts
75 | │ │ ├── shopping-item.model.ts
76 | │ │ └── user.model.ts
77 | │ ├── services
78 | │ │ └── checkout.service.ts
79 | │ └── index.ts
80 | ├── components
81 | │ ├── avatar.component.ts
82 | │ ├── avatar.component.html
83 | │ ├── login-form.component.ts
84 | │ ├── login-form.component.html
85 | │ ├── login-form.component.css
86 | │ └── login-form.component.spec.ts
87 | ├── directives
88 | │ ├── form-validator.directive.ts
89 | │ ├── form-validator.directive.spec.ts
90 | │ ├── tooltip.directive.ts
91 | │ └── tooltip.directive.spec.ts
92 | ├── services
93 | │ └── authorization.service.ts
94 | └── pipes
95 | ├── format-order-name.pipe.ts
96 | └── format-order-name.pipe.spec.ts
97 | ```
98 |
99 | *Why?*: The level of reusability of logic between bounded contexts should be low. On the other hand each code unit will belong to the bounded context it is associated with and will not pollute the directory structure.
100 |
101 | *Why?*: Separation by bounded context will allow easier code reusability. In the general case a small, to medium application will contain a single bounded context, which will lead to a flat directory structure as the following:
102 |
103 | ```
104 | shop
105 | ├── components
106 | │ ├── edit-profile.component.ts
107 | │ ├── edit-profile.component.spec.ts
108 | │ ├── edit-profile.component.html
109 | │ ├── home.component.ts
110 | │ ├── home.component.spec.ts
111 | │ ├── home.component.html
112 | │ ├── register.component.ts
113 | │ └── register.component.spec.ts
114 | ├── models
115 | │ ├── shopping-cart.model.ts
116 | │ ├── shopping-item.model.ts
117 | │ └── user.model.ts
118 | ├── services
119 | │ └── checkout.service.ts
120 | └── index.ts
121 | ```
122 |
123 | *Why?*: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there is no repetitive nor redundant names.
124 |
125 | *Why?*: When there are a lot of files (9+) locating them is easier with a consistent folder structures and more difficult in flat structures.
126 |
127 | * Export and access all public members for given bounded context by its `index.ts` file located in the root directory of the context itself (for more information take a look at [this issue](https://github.com/mgechev/angular2-style-guide/issues/10)).
128 |
129 | *Why?*: Having a facade which exports the public members enforces [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)).
130 |
131 | *Why?*: Exporting all public members using a high-level facade shortens the paths to the individual code units.
132 |
133 | ```
134 | shop
135 | ├── components
136 | │ └── register.component.ts
137 | └── index.ts
138 | ```
139 | For instance, in case the `register` component is exported with the `index.ts` facade by:
140 |
141 | ```ts
142 | // index.ts
143 | export * from './components/register.component';
144 | ```
145 |
146 | On the example below, we can import it using:
147 |
148 | ```ts
149 | import {RegisterComponent} from './shop/index';
150 | ```
151 | Instead of:
152 | ```ts
153 | import {RegisterComponent} from './shop/components/register.component';
154 | ```
155 |
156 | *Why?*: Can be applied with TypeScript's compiler option: `moduleResolution: node`, which will reduce the import to:
157 |
158 | ```ts
159 | import {RegisterComponent} from './shop';
160 | ```
161 |
162 | * The second level division of the directory structure, which is associated with given bounded context and excludes the nested bounded context, should be by either code unit type (as illustrated on the examples above) or by feature like shown below:
163 |
164 | ```
165 | shop
166 | ├── edit-profile
167 | │ ├── edit-profile.component.ts
168 | │ ├── edit-profile.component.spec.ts
169 | │ └── edit-profile.component.html
170 | ├── home
171 | │ ├── home.component.ts
172 | │ ├── home.component.spec.ts
173 | │ └── home.component.html
174 | ├── register
175 | │ ├── register.component.ts
176 | │ └── register.component.spec.ts
177 | ├── common
178 | │ ├── shopping-cart.model.ts
179 | │ ├── shopping-item.model.ts
180 | │ ├── checkout.service.ts
181 | │ ├── user.model.ts
182 | │ └── index.ts
183 | └── index.ts
184 | ```
185 |
186 | * In case given bounded context contains two or more child contexts, divide them into separate directories:
187 |
188 | ```
189 | shop
190 | ├── cart
191 | │ ├── components
192 | │ └── index.ts
193 | ├── checkout
194 | │ ├── components
195 | │ └── index.ts
196 | └── services
197 | ```
198 |
199 | There is a single top-level bounded context here called `shop` and two child contexts called `cart` and `checkout`.
200 |
201 | *Why?*: Grouping the contexts in such a way will stimulate lazy-loading and bundling of the child contexts together.
202 |
203 | *Why?*: This directory structure naturally follows the root-level division by bounded contexts.
204 |
205 | * Implement lazy-loading and/or bundling by bounded contexts. For instance:
206 |
207 | ```
208 | .
209 | ├── admin
210 | │ ├── components
211 | │ │ └─ admin.component.ts
212 | │ └── index.ts
213 | │── shop
214 | │ ├── cart
215 | │ │ ├── ...
216 | │ │ └── index.ts
217 | │ ├── checkout
218 | │ │ ├── ...
219 | │ │ └── index.ts
220 | │ ├── components
221 | │ │ └─ shop.component.ts
222 | │ └── index.ts
223 | ├── components
224 | │ └── app.component.ts
225 | ├── directives
226 | │ └── ...
227 | ├── services
228 | │ └── ...
229 | └── pipes
230 | └── ...
231 | ```
232 |
233 | In case the `app.component.ts` file contains the following route definition:
234 |
235 | ```ts
236 | @RouteConfig([
237 | {
238 | loader: () => System.import('../shop/index').then((m: any) => m.AdminComponent),
239 | path: '/admin'
240 | },
241 | {
242 | loader: () => System.import('../shop/index').then((m: any) => m.ShopComponent),
243 | path: '/shop'
244 | }
245 | ])
246 | export class AppComponent {...}
247 | ```
248 |
249 | And the user opens `https://example.com/shop`, during the initial load time the browser should load **only**:
250 |
251 | - All top-level code units in `components`, `directives`, `services`, `pipes`.
252 | - The entire `shop` bounded context, except its nested bounded contexts (in this case they should be loaded-lazily, just like the top-level bounded contexts).
253 |
254 | *Why?*: In big projects will be loaded only the part of the application required for the selected functionality (in this case the shop module).
255 |
256 | * Do not access directly code units located in another bounded context located on the same or lower level of nesting. The only two exceptions are: via an `AsyncRoute` (since by default such bounded contexts are loaded lazily) or in case the application is not supposed to be lazily loaded (which in case of big applications is considered as a bad practice).
257 |
258 | *Why?*: This way the lazy loading will be prevented, since the lower level bounded context will be bundled (or loaded) together with their parent bounded contexts.
259 |
260 | * Define all shared among bounded contexts components into a top-level directories. The code units can be combined into different directories by name, or in case their count grows significantly they should be grouped by type (as shown above).
261 |
262 | *Why?*: The usage of `shared` directory for the common code units requires longer import paths without bringing much value.
263 |
264 | *Why?*: The used across bounded contexts code units are supposed to be loaded/bundled before/together with all nested bounded contexts and reused across them.
265 |
266 | * Export and access all public members for given bounded context by its `index.ts` file located in the root directory of the context itself (for more information take a look at [this issue](https://github.com/mgechev/angular2-style-guide/issues/10)).
267 |
268 | *Why?*: Having a facade which exports the public members enforces [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)).
269 |
270 | *Why?*: Exporting all public members using a high-level facade shortens the paths to the individual code units.
271 |
272 | ```
273 | shop
274 | ├── components
275 | │ └── register.component.ts
276 | └── index.ts
277 | ```
278 | For instance, in case the `register` component is exported with the `index.ts` facade, on the example below, we can import it using:
279 |
280 | ```ts
281 | import {RegisterComponent} from './shop/index';
282 | ```
283 | Instead of:
284 | ```ts
285 | import {RegisterComponent} from './shop/components/register.component';
286 | ```
287 |
288 | * In case given bounded context contains two or more child contexts, divide them into separate directories:
289 |
290 | ```
291 | shop
292 | ├── cart
293 | │ ├── components
294 | │ └── index.ts
295 | ├── checkout
296 | │ ├── components
297 | │ └── index.ts
298 | └── services
299 | ```
300 |
301 | There is a single top-level bounded context here called `shop` and two child contexts called `cart` and `checkout`.
302 |
303 | *Why?*: Grouping the contexts in such a way will stimulate lazy-loading and bundling of the child contexts together.
304 |
305 | *Why?*: This directory structure naturally follows the root-level division by bounded contexts.
306 |
307 | * Implement lazy-loading and/or bundling by bounded contexts. For instance:
308 |
309 | ```
310 | .
311 | ├── admin
312 | │ ├── components
313 | │ │ └─ admin.component.ts
314 | │ └── index.ts
315 | │── shop
316 | │ ├── cart
317 | │ │ ├── ...
318 | │ │ └── index.ts
319 | │ ├── checkout
320 | │ │ ├── ...
321 | │ │ └── index.ts
322 | │ ├── components
323 | │ │ └─ shop.component.ts
324 | │ └── index.ts
325 | ├── components
326 | │ └── app.component.ts
327 | ├── directives
328 | │ └── ...
329 | ├── services
330 | │ └── ...
331 | └── pipes
332 | └── ...
333 | ```
334 |
335 | In case the `app.component.ts` file contains the following route definition:
336 |
337 | ```ts
338 | @RouteConfig([
339 | {
340 | loader: () => System.import('../shop/index').then((m: any) => m.AdminComponent),
341 | path: '/admin'
342 | },
343 | {
344 | loader: () => System.import('../shop/index').then((m: any) => m.ShopComponent),
345 | path: '/shop'
346 | }
347 | ])
348 | export class AppComponent {...}
349 | ```
350 |
351 | And the user opens `https://example.com/shop`, during the initial load time the browser should load **only**:
352 |
353 | - All top-level code units in `components`, `directives`, `services`, `pipes`.
354 | - The entire `shop` bounded context, except its nested bounded contexts (in this case they should be loaded-lazily, just like the top-level bounded contexts).
355 |
356 | *Why?*: In big projects will be loaded only the part of the application required for the selected functionality (in this case the shop module).
357 |
358 | * Do not access directly code units located in another bounded context located on the same or lower level of nesting. The only two exceptions are: via an `AsyncRoute` (since by default such bounded contexts are loaded lazily) or in case the application is not supposed to be lazily loaded (which in case of big applications is considered as a bad practice).
359 |
360 | *Why?*: This way the lazy loading will be prevented, since the lower level bounded context will be bundled (or loaded) together with their parent bounded contexts.
361 |
362 | * Define all shared among bounded contexts components into a top-level directories. The code units can be combined into different directories by name, or in case their count grows significantly they should be grouped by type (as shown above).
363 |
364 | *Why?*: The usage of `shared` directory for the common code units requires longer import paths without bringing much value.
365 |
366 | *Why?*: The used across bounded contexts code units are supposed to be loaded/bundled before/together with all nested bounded contexts and reused across them.
367 |
368 | * Define only a single code unit (component, directive, service, pipe, etc.) per file. If the unit uses other internal for the given module logic you can keep it in the same module, without exporting it.
369 |
370 | *Why?*: The definitions will be easier to find simply by looking at the directory structure.
371 |
372 | *Why?*: Do not export private APIs because you need to manage them and keep them consistent for the end users.
373 |
374 | * Name the files which contain component, directives, pipes, services or other code unit with the corresponding suffix.
375 |
376 | ```
377 | // Contains the "home-dashboard" component
378 | home-dashboard.component.ts
379 |
380 | // Contains the ShoppingCart model.
381 | shopping-cart.model.ts
382 |
383 | // Contains the logic for the "checkout" service.
384 | checkout.service.ts
385 |
386 | // For redux-like architecture
387 | todo.store.ts
388 | todo.reducer.ts
389 | ```
390 |
391 | *Why?* This way files can be discovered easier using text editor's/IDE's fuzzy search and also in case of many tabs open the different code units are easy recognizable.
392 |
393 | 
394 |
395 | On the example above we it is obvious for us what type of functionality is opened in the individual tabs.
396 |
397 | * Keep the modules self-contained and coherent. Each module should have a [single reason to change](https://en.wikipedia.org/wiki/Single_responsibility_principle).
398 |
399 | *Why?*: This way the modules will be more reusable, and thus composable.
400 |
401 | * Name the modules/directories with `lower-kebab-case`.
402 |
403 | *Why?*: Using lower case will not cause problem across platforms with different case sensitivity.
404 |
405 | *Why?*: By using only English letters, numbers and dash (`-`) the file names will be consistent across platforms.
406 |
407 | *Why?*: Keeps consistency with AngularJS 1.x style guidelines.
408 |
409 | ```
410 | /* RECOMMENDED */
411 | tooltip.directive.ts
412 | user.service.ts
413 | ```
414 | **[Table of Contents](#table-of-contents)**
415 |
416 | ## Directives and Components
417 |
418 | * Use attributes as selectors for your directives.
419 |
420 | *Why?*: There could be many directives per element, which makes it more suitable to use attributes as opposed to elements.
421 |
422 | * Use `lowerCamelCase` for naming the selectors of your directives.
423 |
424 | *Why?*: Keeps the names of the properties defined in the controllers that are bound to the view consistent with the attribute names.
425 |
426 | *Why?*: The Angular 2 HTML parser is case sensitive so `lowerCamelCase` attributes are well supported.
427 |
428 | * Use custom prefix for the selector of your directives (for instance below is used the prefix `sg` from **S**tyle **G**uide).
429 |
430 | *Why?*: This way you will be able to prevent name collisions.
431 |
432 | ```ts
433 | /* AVOID */
434 | @Directive({
435 | selector: '[tooltip]'
436 | })
437 | class BootstrapTooltipDirective {}
438 |
439 | @Directive({
440 | selector: '[tooltip]'
441 | })
442 | class CustomTooltipDirective {}
443 |
444 | @Component({
445 | selector: 'app',
446 | template: `...`,
447 | directives: [CustomTooltip, BootstrapTooltip]
448 | })
449 | class AppComponent {}
450 | ```
451 |
452 | ```ts
453 | /* RECOMMENDED */
454 | @Directive({
455 | selector: '[bsTooltip]'
456 | })
457 | class BootstrapTooltipDirective {}
458 |
459 | @Directive({
460 | selector: '[myTooltip]'
461 | })
462 | class CustomTooltipDirective {}
463 |
464 | @Component({
465 | selector: 'sg-app',
466 | template: `...`,
467 | directives: [CustomTooltip, BootstrapTooltip]
468 | })
469 | class AppComponent {}
470 | ```
471 |
472 | * Name directives' controllers with `Directive` suffix and components' controllers with `Component` suffix. The name of any directive or component should be formed following the rule `BasicDescription` + `Directive` or `Component`.
473 |
474 | ```ts
475 | /* RECOMMENDED */
476 | @Directive({
477 | selector: '[sgTooltip]`
478 | })
479 | class TooltipDirective {}
480 |
481 | @Component({
482 | selector: 'sg-button',
483 | template: `...`
484 | })
485 | class ButtonComponent {}
486 | ```
487 |
488 | *Why?*: When any code unit is imported the consumer will know how to use it based on its name, i.e. `ButtonComponent` means that:
489 |
490 | - This is a controller of a component.
491 | - The component should be used as an element.
492 |
493 | *Why?*: In case a name suffix is used the class is easier to find with IDE's/text editor's fuzzy search.
494 |
495 | * Use [`@HostListener`](https://angular.io/docs/ts/latest/api/core/HostListener-var.html) and [`@HostBinding`](https://angular.io/docs/ts/latest/api/core/HostBinding-var.html) instead of the [`host`](https://angular.io/docs/ts/latest/api/core/Host-var.html) property of the [`@Directive`](https://angular.io/docs/ts/latest/api/core/Directive-decorator.html) and [`@Component`](https://angular.io/docs/ts/latest/api/core/Component-decorator.html) decorators:
496 |
497 | *Why?*: The name of the property, or method name associated to [`@HostBinding`](https://angular.io/docs/ts/latest/api/core/HostBinding-var.html) or respectively [`@HostListener`](https://angular.io/docs/ts/latest/api/core/HostListener-var.html) should be modified only in a single place - in the directive's controller. In contrast if you use [`host`](https://angular.io/docs/ts/latest/api/core/Host-var.html) you need to modify both the property declaration inside the controller, and the metadata associated to the directive.
498 |
499 | *Why?*: The metadata declaration attached to the directive is shorter and thus more readable.
500 |
501 | ```ts
502 | /* AVOID */
503 | @Directive({
504 | selector: '[sgSample]',
505 | host: {
506 | '(mouseenter)': 'onMouseEnter()',
507 | 'attr.role': 'button'
508 | }
509 | })
510 | class SampleDirective {
511 | role = 'button';
512 | onMouseEnter() {...}
513 | }
514 | ```
515 |
516 | ```ts
517 | /* RECOMMENDED */
518 | @Directive({
519 | selector: '[sgSample]'
520 | })
521 | class SampleDirective {
522 | @HostBinding('attr.role') role = 'button';
523 | @HostListener('mouseenter') onMouseEnter() {...}
524 | }
525 | ```
526 |
527 | * Use element selectors for components.
528 |
529 | *Why?*: There could be only a single component per element.
530 |
531 | *Why?*: Components are the actual elements in our applications, compared to directives which only augment the elements.
532 |
533 | * Use `kebab-case` for naming the element selectors of your components.
534 |
535 | *Why?*: Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements/).
536 |
537 | ```ts
538 | /* AVOID */
539 | @Component({
540 | selector: '[sg-button]',
541 | template: `...`
542 | })
543 | class ButtonComponent {}
544 | ```
545 |
546 | ```ts
547 | /* RECOMMENDED */
548 | @Component({
549 | selector: 'sg-button',
550 | template: `...`
551 | })
552 | class ButtonComponent {}
553 | ```
554 |
555 | * Name events without the prefix `on`.
556 |
557 | * Name your event handler methods with the prefix `on` followed by the event name.
558 |
559 | ```ts
560 | /* AVOID */
561 | @Component(...)
562 | export class VoterComponent {
563 | @Output() onVoted = new EventEmitter();
564 | }
565 |
566 |
567 | ```
568 | ```ts
569 | /* RECOMMENDED */
570 | @Component(...)
571 | export class VoterComponent {
572 | @Output() voted = new EventEmitter();
573 | }
574 |
575 |
576 | ```
577 |
578 | *Why?*: This is to be consistent with built-in events like button clicks:
579 |
580 | ```ts
581 |
582 | ```
583 |
584 | *Why?*: Angular allows for an [alternative syntax](https://angular.io/docs/ts/latest/guide/template-syntax.html#!#binding-syntax) `on-*`. If the event itself was prefixed with `on` this would result in an `on-onEvent` binding expression.
585 |
586 | ```ts
587 | /* RECOMMENDED */
588 |
589 |
590 | ```
591 |
592 | * Keep the components as simple and coherent as possible but not too simple.
593 |
594 | *Why?*: Simple coherent components are easier to reason about, more reusable and composable.
595 |
596 | *Why?*: Components which are too primitive may lead to scattering and harder management of the user interface of our applications.
597 |
598 | * Keep the components' templates as lean as possible and inline them inside of the [`@Component`](https://angular.io/docs/ts/latest/api/core/Component-decorator.html) decorator.
599 |
600 | *Why?*: Keeping the components' templates short implies simple, composable components.
601 |
602 | *Why?*: Keeping the template next to the component's controller makes it easier to lookup the component's structure and/or modify it.
603 |
604 | * Extract the more complex and bigger templates, longer than 15 lines of code, into a separate file and put them next to their controllers' definition.
605 |
606 | *Why?*: In case a big and complex template is inlined in the component metadata it may shift the focus from the component's logic defined within the controller.
607 |
608 | * Locate the template of the component in the same directory where the component's logic resides in.
609 |
610 | * Use absolute URLs for paths to the templates of the components (set using `templateUrl`).
611 |
612 | *Why?*: `moduleId` is not supported by all module loaders. In case we use `module.id` we will reduce the portability of our code to only CommonJS.
613 |
614 | * Use [`@Input`](https://angular.io/docs/ts/latest/api/core/Input-var.html) and [`@Output`](https://angular.io/docs/ts/latest/api/core/Output-var.html) instead of the `inputs` and `outputs` properties of the [`@Directive`](https://angular.io/docs/ts/latest/api/core/Directive-decorator.html) and [`@Component`](https://angular.io/docs/ts/latest/api/core/Component-decorator.html) decorators:
615 |
616 | *Why?*: The name of the property, or event name associated to [`@Input`](https://angular.io/docs/ts/latest/api/core/Input-var.html) or respectively [`@Output`](https://angular.io/docs/ts/latest/api/core/Output-var.html) should be modified only on a single place.
617 |
618 | *Why?*: The metadata declaration attached to the directive is shorter and thus more readable.
619 |
620 | ```ts
621 | /* AVOID */
622 | @Component({
623 | selector: 'sg-button',
624 | template: `...`,
625 | inputs: [
626 | 'label'
627 | ],
628 | outputs: [
629 | 'change'
630 | ]
631 | })
632 | class ButtonComponent {
633 | change = new EventEmitter();
634 | label: string;
635 | }
636 | ```
637 |
638 | ```ts
639 | /* RECOMMENDED */
640 | @Component({
641 | selector: 'sg-button',
642 | template: `...`
643 | })
644 | class ButtonComponent {
645 | @Output()
646 | change = new EventEmitter();
647 | @Input()
648 | label: string;
649 | }
650 | ```
651 | * Do not rename inputs and outputs.
652 |
653 | *Why?*: May lead to confusion when the output or the input properties of a given directive are named a given way but exported differently as a public API.
654 |
655 | ```ts
656 | /* AVOID */
657 | @Component({
658 | selector: 'sg-button',
659 | template: `...`
660 | })
661 | class ButtonComponent {
662 | @Output('changeEvent') change = new EventEmitter();
663 | @Input('labelAttribute') label: string;
664 | }
665 | /*
666 | * Need to be consumed the following way:
667 | *
668 | *
669 | *
670 | */
671 | ```
672 |
673 | ```ts
674 | /* RECOMMENDED */
675 | @Component({
676 | selector: 'sg-button',
677 | template: `...`
678 | })
679 | class ButtonComponent {
680 | @Output() change = new EventEmitter();
681 | @Input() label: string;
682 | }
683 | /*
684 | * Need to be consumed the following way, which is much more straightforward:
685 | *
686 | *
687 | *
688 | */
689 | ```
690 |
691 | * Prefer inputs over the [`@Attribute`](https://angular.io/docs/ts/latest/api/core/Attribute-var.html) parameter decorator.
692 |
693 | *Why?*: The input creates one-way binding which means that we can bind it to an expression and its value gets automatically updated. We can inject a property with [`@Attribute`](https://angular.io/docs/ts/latest/api/core/Attribute-var.html) to a controller's constructor and get its value a single time.
694 |
695 | ```ts
696 | /* AVOID */
697 | @Component({
698 | selector: 'sg-button',
699 | template: `...`
700 | })
701 | class ButtonComponent {
702 | label: string;
703 | constructor(@Attribute('label') label) {
704 | this.label = label;
705 | }
706 | }
707 | ```
708 |
709 | ```ts
710 | /* RECOMMENDED */
711 | @Component({
712 | selector: 'sg-button',
713 | template: `...`
714 | })
715 | class ButtonComponent {
716 | @Input() label: string;
717 | }
718 | ```
719 |
720 | * Detach components and directives which are not visible from the view in order to prevent the change detection running over them.
721 |
722 | ```ts
723 | /* RECOMMENDED */
724 | @Directive({
725 | selector: '[pane]'
726 | })
727 | class Pane {
728 | @Input() title;
729 | view: EmbeddedViewRef;
730 | constructor(private _changeDetectorRef: ChangeDetectorRef) {}
731 | // Attaches the change detector
732 | show() {
733 | this._changeDetectorRef.reattach();
734 | }
735 | // Detaches the change detector
736 | hide() {
737 | this._changeDetectorRef.detach();
738 | }
739 | }
740 | ```
741 |
742 | * Do not directly access native elements injected to the controller's constructors with `ElementRef`.
743 |
744 | *Why?*: This way the application will get tightly coupled to the platform and thus won't be able to run independently from it. For instance, a web application injecting native DOM elements won't be able to run in WebWorker, nor be rendered on the server-side.
745 |
746 | ```ts
747 | /* AVOID */
748 | @Component({
749 | selector: 'sg-text-field',
750 | template: ``
751 | })
752 | class TextFieldComponent {
753 | value: string;
754 | constructor(el: ElementRef) {
755 | el.nativeElement.querySelector('input[type="text"]')
756 | .onchange = () => this.value = el.value;
757 | }
758 | }
759 | ```
760 |
761 | ```ts
762 | /* RECOMMENDED */
763 | import {NgModel} from 'angular2/common';
764 |
765 | @Component({
766 | directives: [NgModel],
767 | selector: 'sg-text-field',
768 | template: ``
769 | })
770 | class TextFieldComponent {
771 | value: string;
772 | }
773 | ```
774 |
775 | * Keep the component templates logicless.
776 |
777 | *Why?*: Keeping the logic of the components in their controller, instead of template will bring a lot of benefits such as better testability, maintability, reusability.
778 |
779 | * Do not manipulate element referenced within the template.
780 |
781 | *Why?*: This way the application will get tightly coupled to the platform and thus won't be able to run independently from it. For instance, a web application injecting native DOM elements won't be able to run in WebWorker, neither be rendered on the server-side.
782 |
783 | ```ts
784 | /* AVOID */
785 | @Component({
786 | selector: 'sg-items-list',
787 | template: `
788 |
789 |
790 | `
791 | })
792 | class ItemListComponent {
793 | values: string[] = [];
794 | add(val) {
795 | this.values.push(val);
796 | }
797 | }
798 | ```
799 |
800 | ```ts
801 | /* RECOMMENDED */
802 | import {NgModel} from 'angular2/common';
803 |
804 | @Component({
805 | directives: [NgModel],
806 | selector: 'sg-items-list',
807 | template: `
808 |
809 |
810 | `
811 | })
812 | class ItemListComponent {
813 | value: string;
814 | values: string[] = [];
815 | add() {
816 | this.values.push(this.value);
817 | }
818 | }
819 | ```
820 |
821 | * Always explicitly implement the interfaces associated with the used by given component life-cycle hooks.
822 |
823 | *Why?*: In case the interface associated to given life-cycle hook is implemented one will get compile-time errors in case the hook is not implemented properly (for instance, the method name is misspelled).
824 |
825 | ```ts
826 | /* AVOID */
827 | @Component({
828 | selector: 'sg-button',
829 | template: `...`
830 | })
831 | class ButtonComponent {
832 | ngOnInit() {
833 | console.log('The component got initialized');
834 | }
835 | }
836 | ```
837 |
838 | ```ts
839 | /* RECOMMENDED */
840 | import {OnInit} from 'angular2/core';
841 |
842 | @Component({
843 | selector: 'sg-button',
844 | template: `...`
845 | })
846 | class ButtonComponent implements OnInit {
847 | ngOnInit() {
848 | console.log('The component got initialized');
849 | }
850 | }
851 | ```
852 | **[Table of Contents](#table-of-contents)**
853 |
854 | ## Services and Dependency Injection
855 |
856 | * Limit the usage of [`forwardRef`](https://angular.io/docs/ts/latest/api/core/forwardRef-function.html).
857 |
858 | *Why?*: The usage of [`forwardRef`](https://angular.io/docs/ts/latest/api/core/forwardRef-function.html) indicates either a cyclic dependency or inconsistency in the services' declaration (i.e. the dependent service is declared before its dependency). In both cases there is usually a better approach to be used.
859 |
860 | * Do not declare global providers in the [`bootstrap`](https://angular.io/docs/ts/latest/api/platform/browser/bootstrap-function.html) call, use a top-level component instead.
861 |
862 | *Why?*: The providers registered in the [`bootstrap`](https://angular.io/docs/ts/latest/api/platform/browser/bootstrap-function.html) method are meant for overriding existing providers rather than declaring dependencies.
863 |
864 | **[Table of Contents](#table-of-contents)**
865 |
866 | ## Pipes
867 |
868 | * Name pipes' controllers with `Pipe` as suffix. The name of any pipe should be formed observing the *BasicDescription + Pipe* rule.
869 |
870 | * Always implement the `PipeTransform` interface when building a Pipe.
871 |
872 | *Why?* This ensures your custom Pipes will conform to the framework requirements should they change in the future.
873 |
874 | * Name your pipe `name` property in camelCase.
875 |
876 | ```ts
877 | /* AVOID */
878 | @Pipe({
879 | name: 'sg-transform-something'
880 | })
881 | class TransformSomething {
882 | transform(input: any): any {
883 | //...
884 | }
885 | }
886 | ```
887 |
888 | ```ts
889 | /* RECOMMENDED */
890 | @Pipe({
891 | name: 'sgTransformSomething'
892 | })
893 | class TransformSomethingPipe implements PipeTransform {
894 | transform(input: any): any {
895 | //...
896 | }
897 | }
898 | ```
899 |
900 | * Use [impure pipes](https://angular.io/docs/ts/latest/api/core/PipeMetadata-class.html) only when they need to hold state.
901 |
902 | *Why?*: The change detection can optimize pure pipes since they hold the [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) property.
903 |
904 | * Wherever stateful (impure) pipes are required, implement the `OnDestroy` interface.
905 |
906 | *Why?* It is likely that a stateful pipe may contain state that should be cleaned up when a binding is destroyed. For example, a subscription to a stream of data may need to be disposed, or an interval may need to be cleared.
907 |
908 | **[Table of Contents](#table-of-contents)**
909 |
910 | ## Routing
911 |
912 | * Name the routes the same way the components associated with them are called without the `Component` suffix.
913 |
914 | *Why?*: This way there is only a single name associated to a given route. This avoids confusion in case a given route is called in a different way to the components associated with it.
915 |
916 | **[Table of Contents](#table-of-contents)**
917 |
918 | ## Forms
919 |
920 | * Do not use expression for name of a control.
921 |
922 | *Why?*: When the name of the control is dynamically generated it will be hard to reference it inside of the controller associated to the form, and may lead to confusion.
923 |
924 | ```html
925 | /* AVOID */
926 |
929 | ```
930 | ```ts
931 | @Component(...)
932 | class SampleComponent {
933 | foobar = 'foo';
934 | }
935 | ```
936 |
937 | ```html
938 | /* RECOMMENDED */
939 |
942 | ```
943 | ```ts
944 | @Component(...)
945 | class SampleComponent {}
946 | ```
947 | **[Table of Contents](#table-of-contents)**
948 |
949 | ## Reusable libraries
950 |
951 | Waiting for announcement of official module format by [angular-cli](https://github.com/angular/angular-cli).
952 |
953 | **[Table of Contents](#table-of-contents)**
954 |
955 | ## Testing
956 |
957 | * Use [Jasmine](https://jasmine.github.io/) for unit testing.
958 |
959 | *Why?*: It is well supported and popular in the JavaScript community.
960 |
961 | *Why?*: It is widely popular in the Angular community. Jasmine is used for testing both AngularJS 1.x and Angular 2.
962 |
963 | *Why?*: It has up-to-date type definitions so you can have great development experience using TypeScript.
964 |
965 | * Use Karma as test runner.
966 |
967 | * Place your test files side-by-side with your client code.
968 |
969 | *Why?*: The tests are easier to find since they are always next to the code they are testing.
970 |
971 | *Why?*: When you update source code it is easier to go update the tests at the same time.
972 |
973 | *Why?*: The code may act as documentation of the tested component.
974 |
975 | * Name the test file the following way `NAME_OF_THE_TESTED_UNIT.spec.EXT`:
976 |
977 | ```
978 | /* RECOMMENDED */
979 | about.ts
980 | about.spec.ts
981 | ```
982 |
983 | * Place integration tests and tests that cover multiple code units into a separate `tests` directory in your project's root.
984 |
985 | **[Table of Contents](#table-of-contents)**
986 |
987 | * Use protractor for End-to-End testing.
988 |
989 | *Why?*: It provides high-level API on top of the [Selenium WebDriver](http://www.seleniumhq.org/projects/webdriver/) and is supported by the Angular core team.
990 |
991 | * Name the End-to-End test files using the following convention: `NAME_OF_THE_TESTED_UNIT.e2e.EXT`.
992 |
993 | ## Change detection
994 |
995 | * Use [`OnPush`](https://angular.io/docs/ts/latest/api/core/ChangeDetectionStrategy-enum.html) change detection strategy for [pure/dumb components](http://teropa.info/blog/2015/10/18/refactoring-angular-apps-to-components.html#toward-smart-and-dumb-components) that accepts as input immutable data.
996 |
997 | *Why?*: Angular will optimize the performance of your application dramatically by not performing change detection over the entire sub tree with root the given pure component.
998 |
999 | **[Table of Contents](#table-of-contents)**
1000 |
1001 | ## TypeScript specific practices
1002 |
1003 | * Use [TSLint](http://palantir.github.io/tslint/) for linting your code.
1004 |
1005 | *Why?*: In big teams, code following the same practices is easier to read and understand.
1006 |
1007 | * Be as explicit in the type definitions as possible (i.e. use `any` as rarely as possible).
1008 |
1009 | *Why?*: `any` makes TypeScript behaves like a dynamic language. This way we gave up all the benefits we get from static typing such as better IDE/text editor support and compile-time type-checking.
1010 |
1011 | ### TypeScript code style
1012 |
1013 | * Use `_` in private variable and property names.
1014 |
1015 | *Why?*: It is more obvious when debigging without source maps which property is public and which is private.
1016 |
1017 | *Why?*: It is considered bad practice to access private members in your templates. When such members are named with `_` it is more obvious that they should not be explosed to the template.
1018 |
1019 | ```ts
1020 | class MyClass {
1021 | public myPublicString:string = 'foobar, but public';
1022 | private _myPrivateString:string = 'foobar';
1023 |
1024 | // exception: property field
1025 | private _answerToEverything:number = 42;
1026 |
1027 | public get answerToEverything():number {
1028 | return this._answerToEverything;
1029 | }
1030 |
1031 | public set answerToEverything(value:number) {
1032 | this._answerToEverything = value;
1033 | }
1034 | }
1035 | ```
1036 |
1037 | ### TypeScript dependency injection
1038 |
1039 | * Use the [`@Injectable()`](https://angular.io/docs/ts/latest/api/core/Injectable-decorator.html) decorator instead of explicitly declaring the dependencies using [`@Inject(TOKEN)`](https://angular.io/docs/ts/latest/api/testing/inject-function.html).
1040 |
1041 | *Why?*: Using [`@Injectable()`](https://angular.io/docs/ts/latest/api/core/Injectable-decorator.html) the TypeScript compiler will emit the required type metadata, which makes the code using [`@Inject()`](https://angular.io/docs/ts/latest/api/testing/inject-function.html) unnecessary verbose and less readable.
1042 |
1043 | ### TSLint
1044 |
1045 | * Use [TSLint](http://palantir.github.io/tslint/) for linting your JavaScript and be sure to customize the TSLint options file and include in source control. See the [TSLint docs](https://github.com/palantir/tslint) for details on the options.
1046 |
1047 | *Why?*: Provides a first alert prior to committing any code to source control.
1048 |
1049 | *Why?*: Provides consistency across your team.
1050 |
1051 | ```json
1052 | {
1053 | "rules": {
1054 | "class-name": true,
1055 | "curly": false,
1056 | "eofline": true,
1057 | "indent": "spaces",
1058 | "max-line-length": [true, 140],
1059 | "member-ordering": [true,
1060 | "public-before-private",
1061 | "static-before-instance",
1062 | "variables-before-functions"
1063 | ],
1064 | "no-arg": true,
1065 | "no-construct": true,
1066 | "no-duplicate-key": true,
1067 | "no-duplicate-variable": true,
1068 | "no-empty": true,
1069 | "no-eval": true,
1070 | "no-trailing-comma": true,
1071 | "no-trailing-whitespace": true,
1072 | "no-unused-expression": true,
1073 | "no-unused-variable": true,
1074 | "no-unreachable": true,
1075 | "no-use-before-declare": true,
1076 | "one-line": [true,
1077 | "check-open-brace",
1078 | "check-catch",
1079 | "check-else",
1080 | "check-whitespace"
1081 | ],
1082 | "quotemark": [true, "single"],
1083 | "semicolon": true,
1084 | "triple-equals": true,
1085 | "variable-name": false
1086 | }
1087 | }
1088 | ```
1089 | **[Table of Contents](#table-of-contents)**
1090 |
1091 | ## ES2015 and ES2016 specific practices
1092 |
1093 |
1094 |
1095 | ## ES5 specific practices
1096 |
1097 | * Use the internal JavaScript DLS provided by Angular 2 for annotating constructor functions.
1098 |
1099 | *Why?*: This syntax is simpler, more readable and closer to the original TypeScript version of the code.
1100 |
1101 | *Why?*: The DLS uses function expressions which are closer to the non-hoisted ES2015 classes.
1102 |
1103 | ```js
1104 | /* AVOID */
1105 | function Component() {
1106 | //...
1107 | }
1108 | Component.annotations = [
1109 | new ng.core.ComponentMetadata({
1110 | //...
1111 | }),
1112 | new ng.router.RouteConfig([
1113 | //...
1114 | ])
1115 | ];
1116 | ```
1117 |
1118 | ```ts
1119 | /* RECOMMENDED */
1120 | var Component = ng.
1121 | Component({
1122 | //...
1123 | })
1124 | .RouteConfig([
1125 | //...
1126 | ])
1127 | .Class({
1128 | constructor: function () {
1129 | //...
1130 | }
1131 | });
1132 | ```
1133 |
1134 | ### JSHint
1135 |
1136 | * Use [JSHint](http://www.jshint.com/) for linting your JavaScript and be sure to customize the [JSHint](http://www.jshint.com/) options file and include in source control. See the [JSHint](http://www.jshint.com/docs/) docs for details on the options.
1137 |
1138 | *Why?*: Provides a first alert prior to committing any code to source control.
1139 |
1140 | *Why?*: Provides consistency across your team.
1141 |
1142 | ```json
1143 | {
1144 | "bitwise": true,
1145 | "camelcase": true,
1146 | "curly": true,
1147 | "eqeqeq": true,
1148 | "es3": false,
1149 | "forin": true,
1150 | "freeze": true,
1151 | "immed": true,
1152 | "indent": 2,
1153 | "latedef": "nofunc",
1154 | "newcap": true,
1155 | "noarg": true,
1156 | "noempty": true,
1157 | "nonbsp": true,
1158 | "nonew": true,
1159 | "plusplus": false,
1160 | "quotmark": "single",
1161 | "undef": true,
1162 | "unused": false,
1163 | "strict": false,
1164 | "maxparams": 10,
1165 | "maxdepth": 5,
1166 | "maxstatements": 40,
1167 | "maxcomplexity": 8,
1168 | "maxlen": 140,
1169 |
1170 | "asi": false,
1171 | "boss": false,
1172 | "debug": false,
1173 | "eqnull": true,
1174 | "esnext": false,
1175 | "evil": false,
1176 | "expr": false,
1177 | "funcscope": false,
1178 | "globalstrict": false,
1179 | "iterator": false,
1180 | "lastsemic": false,
1181 | "laxbreak": false,
1182 | "laxcomma": false,
1183 | "loopfunc": true,
1184 | "maxerr": false,
1185 | "moz": false,
1186 | "multistr": false,
1187 | "notypeof": false,
1188 | "proto": false,
1189 | "scripturl": false,
1190 | "shadow": false,
1191 | "sub": true,
1192 | "supernew": false,
1193 | "validthis": false,
1194 | "noyield": false,
1195 |
1196 | "browser": true,
1197 | "node": true,
1198 |
1199 | "globals": {
1200 | "angular": false,
1201 | "ng": false
1202 | }
1203 | }
1204 | ```
1205 |
1206 | ### JSCS
1207 |
1208 | * Use [JSCS](http://jscs.info/) for checking your coding styles your JavaScript and be sure to customize the [JSCS](http://jscs.info/) options file and include in source control. See the [JSCS docs](http://jscs.info/rules) for details on the options.
1209 |
1210 | *Why?*: Provides a first alert prior to committing any code to source control.
1211 |
1212 | *Why?*: Provides consistency across your team.
1213 |
1214 | ```json
1215 | {
1216 | "excludeFiles": ["node_modules/**", "bower_components/**"],
1217 |
1218 | "requireCurlyBraces": [
1219 | "if",
1220 | "else",
1221 | "for",
1222 | "while",
1223 | "do",
1224 | "try",
1225 | "catch"
1226 | ],
1227 | "requireOperatorBeforeLineBreak": true,
1228 | "requireCamelCaseOrUpperCaseIdentifiers": true,
1229 | "maximumLineLength": {
1230 | "value": 140,
1231 | "allowComments": true,
1232 | "allowRegex": true
1233 | },
1234 | "validateIndentation": 2,
1235 | "validateQuoteMarks": "'",
1236 |
1237 | "disallowMultipleLineStrings": true,
1238 | "disallowMixedSpacesAndTabs": true,
1239 | "disallowTrailingWhitespace": true,
1240 | "disallowSpaceAfterPrefixUnaryOperators": true,
1241 | "disallowMultipleVarDecl": null,
1242 |
1243 | "requireSpaceAfterKeywords": [
1244 | "if",
1245 | "else",
1246 | "for",
1247 | "while",
1248 | "do",
1249 | "switch",
1250 | "return",
1251 | "try",
1252 | "catch"
1253 | ],
1254 | "requireSpaceBeforeBinaryOperators": [
1255 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
1256 | "&=", "|=", "^=", "+=",
1257 |
1258 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
1259 | "|", "^", "&&", "||", "===", "==", ">=",
1260 | "<=", "<", ">", "!=", "!=="
1261 | ],
1262 | "requireSpaceAfterBinaryOperators": true,
1263 | "requireSpacesInConditionalExpression": true,
1264 | "requireSpaceBeforeBlockStatements": true,
1265 | "requireLineFeedAtFileEnd": true,
1266 | "disallowSpacesInsideObjectBrackets": "all",
1267 | "disallowSpacesInsideArrayBrackets": "all",
1268 | "disallowSpacesInsideParentheses": true,
1269 |
1270 | "jsDoc": {
1271 | "checkAnnotations": true,
1272 | "checkParamNames": true,
1273 | "requireParamTypes": true,
1274 | "checkReturnTypes": true,
1275 | "checkTypes": true
1276 | },
1277 |
1278 | "disallowMultipleLineBreaks": true,
1279 |
1280 | "disallowCommaBeforeLineBreak": null,
1281 | "disallowDanglingUnderscores": null,
1282 | "disallowEmptyBlocks": null,
1283 | "disallowTrailingComma": null,
1284 | "requireCommaBeforeLineBreak": null,
1285 | "requireDotNotation": null,
1286 | "requireMultipleVarDecl": null,
1287 | "requireParenthesesAroundIIFE": true
1288 | }
1289 | ```
1290 | **[Table of Contents](#table-of-contents)**
1291 |
1292 | ## Tooling
1293 |
1294 | - [codelyzer - set of tslint rules Angular 2](https://github.com/mgechev/codelyzer).
1295 | - [Sublime Text snippets Angular 2](https://github.com/evanplaice/angular2-snippets).
1296 | - [WebStorm snippets Angular 2](https://github.com/d3viant0ne/angular2-webstorm-templates)
1297 |
1298 | ## License
1299 |
1300 | MIT
1301 |
--------------------------------------------------------------------------------