Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Angular Styleguide

Opinionated Angular styleguide of industry-best practices for teams by @deavon, with input from John Papa, Todd Motto, and the AngularJS team.

A standardized approach for developing Angular applications in teams. This styleguide touches on concepts, syntax, and conventions.

Table of Contents

  1. Modules
  2. TypeScript & ES6
  3. Lodash
  4. Controllers
  5. Components
  6. Services and Factories
  7. Directives
  8. Filters
  9. Performance
  10. Angular wrapper references
  11. Minification and annotation
  12. Code Patterns
    1. Single Responsibility
    2. Naming
    3. Folders-by-Feature Structure
    4. Application Structure LIFT Principle
    5. Application Structure
    6. Misc
  13. Official TypeScript, Angular, & Lodash Docs

Modules

  • Definitions: Declare modules without a variable using the setter and getter syntax, and always use export default for angular.module to guarantee single responsibility and allow import into other module files.

    /* avoid */varapp=angular.module('app.subModule',[]);app.controller();
    /* recommended */exportdefaultangular.module('app.subModule',[]).controller();

    Note: Using angular.module('app', []); sets a module, whereas angular.module('app'); gets the module. Only set once, and get for all other instances.

  • Config files: Once a module's config becomes as large or larger than its module file, always separate this into its own *.config.ts file. Also, always place the config and module files within the same directory.

    /* avoid */// settings.module.tsimport{UserService}from'./user.service';exportdefaultangular.module('app.settings',[]).service('userService',UserService)/* @ngInject */.config(($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code});
    /* recommended */// settings.config.ts/* @ngInject */exportdefault($stateProvider: ng.ui.IStateProvider)=>{// 30+ lines of code}// settings.module.tsimport{UserService}from'./user.service';importSettingsConfigfrom'./settings.config';exportdefaultangular.module('app.settings',[]).service('userService',UserService).config(SettingsConfig);
  • Methods: Pass ES6 class references into module methods rather than assigning as a function callback

    /* avoid */angular.module('app',[]).controller('MainController',functionMainController(){}).service('SomeService',functionSomeService(){});
    /* recommended */// main.controller.tsexportclassMainController{}// some.service.tsexportclassSomeService{}// app.module.tsexportdefaultangular.module('app',[]).controller('MainController',MainController).service('SomeService',SomeService);

    Why?: This aids with readability and reduces the volume of code "wrapped" inside the Angular framework

Back to top


TypeScript & ES6

  • Third-party module definition import file: Use a global definition file (i.e. all.d.ts) within your project's tsconfig.json to make definitions, for all plain-JavaScript third-party libraries, implicitly available to all code files in your project.

Also, as unit tests are executed within their own separate context, always create a definition file only for your tests (i.e. all.spec.d.ts).

```typescript
/* avoid */
/// <reference path="../typings/angularjs/angular.d.ts"/>
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
```javascript
/* recommended */
// tsconfig.json
{
"files": [
"all.d.ts"
]
}
```
```typescript
// all.d.ts
/// <reference path="../typings/angularjs/angular.d.ts"/>
```
```typescript
// dashboard.controller.ts
export class DashboardController
{
/* @ngInject */
constructor(private $q: ng.IQService)
{
// initialize controller
}
}
```
  • Angular dependency injector functions: To limit redundancy, always specify the private accessor for all arguments of any Angular-based class' constructor. This will register these as properties within your class. It's best to keep injected dependency objects private.

    Remember: When resolving dependencies through Angular's DI mechanism, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones.

    Remember: For Angular DI arguments, always specify their corresponding type (i.e. $q: ng.IQService or customService: CustomService). For all other function declarations, specify argument types whenever possible.

    /* avoid */import{UserService}from'./user.service';exportclassDashboardController{private$q;privateuserService;/* @ngInject */constructor(userService,$){this.$q=$q;this.userService=userService;}}
    /* recommended */import{UserService}from'./user.service';exportclassDashboardController{/* @ngInject */constructor(private$q: ng.IQService,privateuserService: UserService){// initialize controller}}
  • Type safety: Prioritize the type safety of core components, like common Services, over one-time use Filters, Directives, etc. Try to share as many types as possible, and define or declare them within the scope of their use or in a place that makes sense for the whole app. Do not rush type safety.

    Why?: The more type safe properties, arguments, and method return types are specified, the less your application will be prone to errors, and the easier your code will be to manage. Type safety also improves code readability.

Remember: If in doubt, use the any type. You can always add typing in later iterations. It's better to wait to code something the right way than to need to course correct somewhere down the road because something was rushed. Come to a consensus with your team on what typing structure and standard makes sense.

  • Use implicit accessors: For cleaner, more readable code, understand that TypeScript assumes a property or function is public when no accessor is specified. Only specify the accessor for private or protected class members.

    // avoidexportclassDashboardController{privatecampaigns: Campaign[];public$q: ng.IQService;publicfilters: FilterTypes;publicpreloadHiddenTiles: boolean=true;/* @ngInject */constructor($q: ng.IQService){this.$q=$q;}publicopen(){}}
    // recommendedexportclassDashboardController{privatecampaigns: Campaign[];filters: FilterTypes;preloadHiddenTiles: boolean=true;/* @ngInject */constructor(private$q: ng.IQService){// initialize controller}open(){}}

    Why?: It improves code readability and reduces clutter.

    Remember: The less redundant syntax, the better.

  • Hoisting: In JavaScript, functions and variables are hoisted. Hoisting is JavaScript's behavior of moving declarations to the top of a scope (the global scope or the current function scope).

example coming soon

**Note**: ES6 Classes are not hoisted, which will break your code if you rely on hoisting
**Note:** An important distinction between function declarations and class declarations is that function declarations are hoisted and class declarations are not. You first need to declare your class and then access it, otherwise code like the following will throw a ReferenceError:

example coming soon

  • Use default exports sparingly: Ensures that class and object names are identical throughout your source code, and limits confusion. export default is safe to use for the single-purpose files of anonymous functions, *module.ts, or Filters.

    /* avoid */// settings.controller.tsexportdefaultclassSettingsController{}/* avoid */// maps-api.service.tsexportdefaultclassMapsApiService{}// profile.module.tsimportArbitrarySettingsNamefrom'./settings.controller';importDifferentApiNamefrom'./maps-api.service';angular.module('app',[]).controller('SettingsController',ArbitrarySettingsName).service('MapsApiService',DifferentApiName);
    /* recommended */// settings.controller.tsexportclassSettingsController{}/* recommended */// maps-api.service.tsexportclassMapsApiService{}// profile.module.tsimport{SettingsController}from'./settings.controller';import{MapsApiService}from'./maps-api.service';angular.module('app',[]).controller('SettingsController',SettingsController).service('MapsApiService',MapsApiService);
  • Avoid or limit the use of experimental TypeScript, or ES7 features, like @decorators.

Back to top


Lodash

  • Third-party helper function priority: There's some overlap in helper functions available between plain JavaScript, Angular, and Lodash. In the event that you need to use one of these functions, always opt for the Lodash method first, then its Angular equivalent second, and lastly, plain JavaScript:

    /* avoid */Object.keys(this.users).forEach(key=>{varuser=users[key];// Do something});
    /* avoid */angular.forEach(this.users,(user,key)=>{// Do something});
    /* recommended */_.each(this.users,user=>{// Do something});

Back to top


Controllers

  • controllerAs syntax: Controllers are classes, so use the controllerAs syntax at all times

    <!-- avoid --><divng-controller='MainController'>
    {{ someObject }}
    </div>
    <!-- recommended --><divng-controller='MainController as vm'>
    {{ vm.someObject }}
    </div>
  • In the DOM we get a variable per controller, which aids nested controller methods, avoiding any $parent calls

  • The controllerAs syntax uses this inside controllers, which gets implicitly bound to $scope.

    /* avoid */exportclassMainController{constructor(private$scope: ng.IScope){this.$scope.someNumber=0;this.$scope.doSomething=function(){this.$scope.someNumber=5;};}}
    /* recommended */exportclassMainController{someNumber=0;doSomething(){this.someNumber=5;}}
  • Only use $scope in controllerAs when necessary; for example, publishing and subscribing events using $emit, $broadcast, $on, or $watch. Try to limit the use of these, however, and treat $scope as a special use case:

    /* recommended */exportclassMainController{someNumber: boolean;doSomething(){this.someNumber=5;}}
  • Presentational logic only (MVVM): Presentational logic only inside a controller, avoid Business logic (delegate to Services)

    /* avoid */exportclassMainController{users: User[];constructor(private$http: ng.IHttpService){this.init();}init(){this.$http.get('/users').success((response: Users[])=>{this.users=response;});}}
    /* recommended */import{UserService}from'./user.service';exportclassMainController{users: User[];constructor(privateuserService: UserService){this.init();}init(){this.userService.getUsers().then((response: Users[])=>{this.users=response;});}}

    Why? : Controllers should fetch Model data from Services, avoiding any "business logic". Controllers should act as a ViewModel, and control the data flowing between the Model and the View presentational layer.

Placing business logic within your Controllers makes testing Services impossible.

Back to top


Components

Defer Logic to Services

  • Defer logic in a component by delegating to services.

    Why?: Logic may be reused by multiple components when placed within a service and exposed via a function.

    Why?: Logic in a service can more easily be isolated in a unit test, while the calling logic in the component can be easily mocked.

    Why?: Removes dependencies and hides implementation details from the component.

    Why?: Keeps the component slim, trim, and focused.

example coming soon

Keep Components Focused

  • Define a component for a view, and try not to reuse the component for other views. Instead, move reusable logic to factories and keep the component simple and focused on its view.

    Why?: Reusing components with several views is brittle, and good end-to-end (E2E) test coverage is required to ensure stability across large applications.

example coming soon

Back to top


Services and Factories

  • All Angular Services are singletons, using .service() or .factory() differs in the way Objects are created.

  • Services: Act as a constructor function and are instantiated with the new keyword. Use this for public methods and variables

    // some.service.tsexportclassSomeService{someSharedLogicMethod(){}}// app.module.tsangular.module('app',[]).service('SomeService',SomeService);
  • Factories: Use Factories sparingly.

AngularJS Team Note: The use of module.factory() is specifically for when you are not using classes. The module.service() method was specifically designed for when you want to define your services as classes (or instantiable types). So there is actually no point in trying to hack together a way to register a class via the module.factory() method. Just use module.service() instead.

example coming soon

**Why?**: Primitive values cannot update alone using the revealing module pattern

Back to top


Directives

  • DOM manipulation: Takes place only inside Directives, never a Controller / Service.

Never use jQuery, or any third-party components dependent upon jQuery. Use JQLite instead with angular.element within Directives only.

```typescript
/* avoid */
// upload.controller.ts
export class UploadController
{
constructor()
{
$('.dragzone').on('dragend', () => {
// handle drop functionality
});
}
}
// common.module.ts
import {UploadController} from './upload.controller';
angular.module('app.common', [])
.controller('UploadController', UploadController);
```
```typescript
/* recommended */
// drag-upload.directive.ts
export function dragUploadDirective(): ng.IDirective {
return {
link: function (scope: ng.IScope, element: ng.IAugmentedJQuery) {
element.on('dragend', () => {
// handle drop functionality
});
}
};
}
// common.module.ts
import {dragUploadDirective} from './drag-upload.directive';
angular.module('app')
.directive('dragUpload', dragUploadDirective);
```
**Why?**: Proper directive use enforces the separation of concerns and DRY principles very effectively, and most importantly, prevents the DOM from losing sync with your app's state. reduces the number of total forms of state management in your app.
  • Declaration restrictions: Only use custom element and custom attribute methods for declaring your Directives ({ restrict: 'EA' }) depending on the Directive's role

    Why?: Comment and class name declarations are confusing and should be avoided. Comments do not play nicely with older versions of IE. Using an attribute is also the safest method for browser coverage.

  • controllerAs: Use the controllerAs syntax inside Directives as well

    /* avoid */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
    /* recommended */// drag-upload-directive.controller.tsexportclassDragUploadDirectiveController{dragCompleted: boolean;}// drag-upload.directive.tsexportfunctiondragUpload(){return{controllerAs: 'vm',controller: DragUploadController};}// common.module.tsangular.module('app.common',[]).directive('dragUpload',dragUpload);
  • Use the Controller syntax: When your Directive's link function grows beyond a few lines of code.

    // color-swatch.directive.tsimport{SomeService}from'../services/some.service';classColorSwatchController{color: Color;/* @ngInject */constructor(private$element: ng.IAugmentedJQuery,private$attrs: ng.IAttributes,privatesomeService: SomeService){// Link function}onChange(){this.$element.css(...);this.someService.doWork();}}/* @ngInject */exportfunctioncolorSwatchDirective(): ng.IDirective{return{bindToController: true,controller: ColorSwatchController,controllerAs: 'vm',replace: true,restrict: 'E',scope: {color: '=',},template: require('./color-swatch.html')}}

Back to top


Filters

  • Keep it light: Make your filters as light as possible. They are called often throughout the $digest loop, so creating a slow filter will reduce app performance significantly.

  • Global filters: Create global filters using angular.module('', []).filter() syntax only. Never use local filters inside Controllers / Services

    /* avoid */exportclassSomeController{startsWithLetterA(items: SomeItemType[]){returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}angular.module('app',[]).controller('SomeController',SomeController);
    /* recommended */// starts-with-letter-a.filter.tsexportfunctionstartsWithLetterAFilter(){return(items: SomeItemType[])=>{returnitems.filter((item: SomeItemType)=>{return/^a/i.test(item.name);});};}// starts-with-letter-a.filter.tsimport{startsWithLetterAFilter}from'./starts-with-letter-a.filter.ts';angular.module('app.common',[]).filter('startsWithLetterA',startsWithLetterAFilter);

    Why?: This enhances testing and reusability

Back to top


Performance

  • One-time binding syntax: In versions of Angular (>= v1.3), use the one-time binding syntax {{ ::value }} everywhere you possibly can

    <!-- avoid --><h1>{{ vm.title }}</h1><h1>{{ "global.default_title" | translate }}</h1>
    <!-- recommended --><h1>{{ ::vm.title }}</h1><h1>{{ ::"global.default_title" | translate }}</h1>

Why?: Binding once removes the watcher from the scope's $$watchers array after the undefined variable becomes resolved, thus improving performance of each dirty-check

  • $scope.$digest: Use $scope.$digest over $scope.$apply, where it makes sense.

    $scope.$digest();

Why?: $scope.$apply calls $rootScope.$digest, which causes the entire application $$watchers to dirty-check again. Using $scope.$digest will dirty check only the current and child scopes from the initiated $scope

Back to top


Angular wrapper references

  • $document and $window: Use $document and $window at all times to aid testing and Angular references

  • $timeout and $interval: Use $timeout and $interval over their native counterparts to keep Angular's two-way data binding up to date

    /* avoid */exportfunctiondragUploadDirective(){return{link: function($scope,$element,$attrs){setTimeout(function(){//},1000);}};}
    /* recommended */exportfunctiondragUploadDirective($timeout: ng.ITimeoutService){return{link: ($scope: any,$element: ng.IAugmentedJQuery,$attrs: ng.IAttribute)=>{$timeout(()=>{//},1000);}};}

Back to top


Minification and annotation

  • ng-annotate: Use ng-annotate for Gulp as ng-min is deprecated, and comment functions that need automated dependency injection using /* @ngInject */

    exportclassMainController{/* @ngInject */constructor(private$log: ng.ILogService){this.$log.debug('MainController loaded.');}}
  • Which produces the following output with the $inject annotation

    functione(e){this.$log=e,this.$log.debug("MainController loaded.")}returne.$inject=["$log"],e

Back to top


Code Patterns

Single Responsibility

Rule of 1

  • Define 1 component per file.

Why?: One component per file promotes easier unit testing and mocking.

Why?: One component per file makes it easier to read, maintain, and avoid collisions with teams in source control.

Why?: One component per file avoids hidden bugs that often arise when combining components in a file where they may share variables, create unwanted closures, or unwanted coupling with dependencies.

The following example defines the app.profile module and its dependencies, defines a controller, and defines a service all in the same file.

```typescript
/* avoid */
// profile.ts
class UserProfileController
{
}
class UserService
{
}
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

The same components are now separated into their own files.

```typescript
/* recommended */
// user-profile.controller.ts
export class UserProfileController
{
}
// user.service.ts
export class UserService
{
}
// profile.module.ts
import {UserProfileController} from './user-profile.controller';
import {UserService} from './user.service';
angular.module('app.profile', [])
.controller('UserProfileController', UserProfileController)
.service('userService', UserService);
```

Back to top

Small Functions

  • Define small functions, no more than 75 LOC (less is better).

Why?: Small functions are easier to test, especially when they serve one, distinct purpose.

Why?: Small functions promote reuse.

Why?: Small functions are easier to read.

Why?: Small functions are easier to maintain.

Why?: Small functions help avoid hidden bugs that come with large functions that share variables with external scope, create unwanted closures, or unwanted coupling with dependencies.

Back to top

IIFE

  • Given full adherence to the Single Responsibility principle, modern build tooling (i.e. webpack), and ECMAScript 6 (ES6) modules, manually wrapping your code in immediately-invoked function expressions (IIFE) or closures is no longer necessary. Webpack, Browserify, and other tools already do this for you at build time, and is the superior mechanism moving forward.

    /* avoid */(function(){'use strict';classPersistentCacheService{}angular.module('app.common',[]).service('persistentCache',PersistentCacheService);})();
    /* avoid */exportdefaultfunction(ngModule){/*@ngInject*/functionAnalyticsController($scope,$state){}ngModule.controller('analyticsController',AnalyticsController);}

Why?: The less syntax redundancy you can have in your codebase, the less time must be spent on maintenance and the easier your code will be to read and understand.

Back to top


Naming

File Naming Conventions

  • Use consistent names for all components following a pattern that describes the component's feature then (optionally) its type. The ideal pattern is feature-name.type.ts.

Why?: Naming conventions help provide a consistent way to find content at a glance. Consistency within the project is vital. Consistency with a team is important. Consistency across a company provides tremendous efficiency.

Why?: Provides a consistent way to quickly identify components.

Why?: Provides pattern matching for any automated tasks.

There are 3 naming conventions for most assets:

Asset TypeFile NameAngular TokenClass or Function Name
Controllerfeature-name.controller.tsfeatureNameControllerFeatureNameController
Servicefeature-name.service.tsfeatureNameServiceFeatureNameService
Componentfeature-name.component.tsfeatureNameComponentFeatureNameComponent
Directivefeature-name.directive.tsfeatureNamefeatureNameDirective()
Factoryfeature-name.factory.tsfeatureNameFactoryfeatureNameFactory()
Filterfeature-name.filter.tsfeatureNamefeatureNameFilter()

Back to top


Folders-by-Feature Structure

  • Create folders named for the feature they represent. When a folder grows to contain more than 7 files, start to consider creating a folder for them. Your threshold may be different, so adjust as needed.

    Why?: A developer can locate the code, identify what each file represents at a glance, the structure is flat as can be, and there are no repetitive nor redundant names.

    Why?: The LIFT guidelines are all covered.

    Why?: Helps reduce the app from becoming cluttered through organizing the content and keeping them aligned with the LIFT guidelines.

    Why?: When there are a lot of files (10+) locating them is easier with a consistent folder structures and more difficult in flat structures.

example coming soon

Back to top


Application Structure LIFT Principle

LIFT

  • Structure your app such that you can Locate your code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to stay DRY. The structure should follow these 4 basic guidelines.

    Why LIFT?: Provides a consistent structure that scales well, is modular, and makes it easier to increase developer efficiency by finding code quickly. Another way to check your app structure is to ask yourself: How quickly can you open and work in all of the related files for a feature?

    When I find my structure is not feeling comfortable, I go back and revisit these LIFT guidelines

    1. Locating our code is easy
    2. Identify code at a glance
    3. Flat structure as long as we can
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY

Locate

  • Make locating your code intuitive, simple, and fast.

    Why?: I find this to be super important for a project. If the team cannot find the files they need to work on quickly, they will not be able to work as efficiently as possible, and the structure needs to change. You may not know the file name or where its related files are, so putting them in the most intuitive locations and near each other saves a ton of time. A descriptive folder structure can help with this.

example coming soon

Identify

  • When you look at a file you should immediately know what it contains and represents.

    Why?: You spend less time hunting and pecking for code, and become more efficient. Even if it means having longer file names. Be descriptive with file names, and keep the contents of the file to exactly 1 component. Avoid files with multiple controllers, multiple services, or a mixture. There are rare exceptions to the 1-per-file rule in instances of very small but closely-related features, after which the file is still easily identifiable.

Flat

  • Keep a flat folder structure as long as possible. When you get to 7+ files, begin considering separation.

    Why?: Nobody wants to search 7 levels of folders to find a file. Think about menus on web sites … anything deeper than 2 should take serious consideration. In a folder structure there is no hard and fast number rule, but when a folder has 7-10 files, that may be time to create subfolders. Base it on your comfort level. Use a flatter structure until there is an obvious value (to help the rest of LIFT) in creating a new folder.

T-DRY (Try to Stick to DRY)

  • Be DRY, but don't go nuts and sacrifice readability.

    Why?: Being DRY is important, but not crucial if it sacrifices the others in LIFT.

    example coming soon

Back to top


Application Structure

Modularity

Many Small, Self Contained Modules
  • Create small modules that encapsulate one responsibility.

    Why?: Modular applications make it easy to plug and go as they allow the development teams to build vertical slices of the applications and roll out incrementally. This means we can plug in new features as we develop them.

    example coming soon

Back to top


Others

  • The less unique forms of state management you can have throughout your app, the better. Some state management examples include: CSS visibility and DOM manipulation outside of Directives (very, very bad), local variables, Controller variables, DOM manipulation through directives, any server-side output changes, DOM manipulation outside of directives, etc. You can easily see how one form could contradict or conflict with another form, and ultimately lead to your app code losing sync with the DOM and causing some fatal error.
  • Use $resource instead of $http when possible. The higher level of abstraction will save you from redundancy.
  • Never use globals. Resolve all dependencies using Dependency Injection; this will prevent bugs and monkey patching when testing.
  • Think twice when working with $rootScope, or potentially polluting it.

Back to top


Official TypeScript, Angular, & Lodash Docs

For anything else, including API reference, check the official documentation:


Contributing

1. Discuss the change in a GitHub issue.
2. Open a Pull Request, reference the issue, and explain the change and why it adds value.
3. The Pull Request will be evaluated and either merged or declined.

About

Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices. http://www.rift.it

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors