Skip to content

calcaide/angular1-styleguide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 

Repository files navigation

Angular 1 styleguide

Table of contents

Introduction

This style guide is deprecated and no longer updated. I use it as a reminder or cheat sheet for maintaining legacy code.

Is important to mention that this guide is strictly based in ES6 and in MVC pattern architecture. Angular 1.x can ve used in other approaches, but you don't will find it here.

Since Angular 1.5.x and above it is possible to use it with a component based architecture but I never used Angular in this approach, when I decided to move to that architecture I stopped using Angular in favor of React.

This style guide is the result of in-depth working with Angular (since 2013), having discussions with co-workers and other developers, reading a lot of articles and other style guides that I will mention in reference style guides.

⬆ back to top

Reference style guides

⬆ back to top

Project structure

There are different ways to structure an Angular project- Here are sme important articles (in my humble opinion) about it:

Example of app structure:

some-ui-project
|
|- common --> Common Angular artifacts and modules in the UI context.
|	|
|	|-- models
|	|-- dataServices
|	|-- directives
|	|-- modules
|
|- clients --> Clients section angular artifacts and module definition.
|	|
|	|-- clients.module.js
|	|-- clients.ctrl.js
|	|-- clients.model.js
|	|-- clients.dataSrv.js
|	|-- clients.tmpl.html
|
|- dashboard
|- users
|- root --> Views and controllers that are part of the root state.
|- app.js --> the root module definition.
|- app.config.js --> The app .config blocks definition.
|- app.states.js --> The UI-Router states definition for the entire app.

Also I cover huge modules, for example think in clients, is a big module that is made it for other smaller modules.

Example of huge module:

clients
|
|- clients.module.js
|- clients.ctrl.js
|- detail
|	|
|	|-- clientsDetail.ctrl.js
|	|-- clientsDetail.tmpl.html
|	|-- clientsDetail.module.js
|	|-- ...
|
|- list
|	|
|	|-- clientsList.ctrl.js
|	|-- clientsList.tmpl.html
|	|-- clientsList.module.js
|	|-- ...

The importance of modularization

All pieces have to encapsulate in a module (modules that make sense) for the sake of the mental health of the developers. Once all is a module, is very easy to work with it, like a Lego.

In the example above (example of app structure), the dashboard module, do you want to use it in another app? Copy the /dashboard folder to your project, register the dashboard module in the app module definition (app.js) and add a route to it (app.states.js), that's all. This also benefits an agile development and the teamwork.

Is important to mention that I use the UI-Router instead NG-Router. The UI-Router is a state based router that offers abstract states, nested states and nested views, basic to develop a structure of a project like this.

  • app.js: the root module, the app definition.
  • app.config.js: all the config blocks encapsulated in one module. In this way, you don't pollute the root module with config blocks.
  • app.states.js: all the app states tree defined in one file. Also, this way, you don't pollute the root module with states.
  • /root: views and controllers that are part of the root state. I use an abstract state as the first state. This state is called root, and any other state in the application is a nested state of the root. Root states defines the main template that is composed of multiple named views, those named views contains common components that are present in any section of the app like a top bar menu or whatever.
  • /common: contains common Angular artifacts and modules in the UI context. I use to keep all the module artifacts (controller, service, etc.) together in a folder (like clients module in the example) that typically refers to a section or component of the app, but some artifacts are used for more than one module. I consider those artifacts are orphans, let's put an example with a directive that is used in dashboard and in clients section, where you will put this directive? in dashboard folder or clients folder? The answer is in /common folder. The same with a common module. That's why is the unique folder that follows the sort by type structuring approach, for the variety of artifacts and modules that can contain.
  • /clients: represents a state/section of the app and wraps all the artifacts that form it. There are simple sections like clients in the example of app structure or little bit complex like clients in the example of huge module. The huge module example is for a section formed by subsections, following the example, you have a section /clients, but inside this section, you will have two subsections: list and detail. The list of the clients and when you want to go deep in a client of the list, you will have a detail client section.

In the example of app structure I am using a structuring for modules approach in all the project except inside the /common folder, due to his nature (explained above) has to follow sort by type.

⬆ back to top

Naming conventions

  • Directories: there is no secret, just use lowerCamelCase as style. For the name itself, use the feature that represents.

  • Filename: I follow a pattern that describes the component feature and its type: feature.type.js.

    List of types:

    • Root module: app.js.
    • Common module: clients.module.js.
    • Configuration blocks: app.config.
    • Controller: clients.ctrl.js.
    • Data model: clients.model.js.
    • Data service: clients.dataSrv.js.
    • Util or business service: clients.util.js.
    • Shared method: clients.sharedModel.js.
    • Directives: scrollTo.drtv.js.
    • Decorator: scrollTo.dctr.js.
    • Component: clients.comp.js.

Angular artifacts:

All the code example below are focused on registrate/create the module or artifact, not in the definition of the artifacts itself. The definitions are cover in the recipes section.

Modules:

  • Filename:
    • Root module: app.js.
    • Common module: feature.module.js -> clients.module.js.
    • Style: lowerCamelCase.
    • Name: the name is formed by a set of arguments:
      • First: application name, for example: awCrm (awesome CRM).
      • Second: module feature, for example: clients.
      • Third or more: what is need, like an action that represents the module: new, list, detail, etcetera.
      • Examples:
        • awCrm.clients.list.
        • awCrm.users.new.
        • awCrm.dashboard.

⬆ back to top

Configuration blocks:

  • Filename: app.config.js.
  • Style: lowerCamelCase.
  • Name: the name is formed by two arguments (some people prefer to use just config):
    • First: application name.
    • Second: config.
    • Example: awCrm.config.
  • Example in code:
const config = angular
	.module('awCrm.config', [])
	.config(['$provide', ($provide) => new LogDecorator($provide)]);
	.name;

export default config;

⬆ back to top

Controllers

  • Filename: clientsList.ctrl.js.
  • Style: UpperCamelCase.
  • Name: use the controller feature to name it.
  • Example: ClientsListCtrl.
  • Example of register a controller in code:
import ClientsListCtrl from './clientsList.ctrl.js';

const clientsList = angular
	.module('awCrm.clients.list', [])
	.controller('ClientsListCtrl', ClientsListCtrl)
	.name;

export default clientsList;

⬆ back to top

Data model

  • Filename: clientsList.model.js.
  • Style: UpperCamelCase.
  • Name: use the data model feature to name it.
  • Example: ClientsListModel.
  • Example of register a data model in code:
import ClientsListModel from `./clientsList.model.js`;

const clientsList = angular
	.module('awCrm.clients.list')
	.service('ClientsListModel', ClientsListModel)
	.name;

export default clientsList;

⬆ back to top

Data service, util service and shared model

I put these three artifacts together because their are identical in terms of naming conventions.

  • Filename:
    • Data services: clients.dataSrv.js.
    • Util or business service: clients.util.js.
    • Shared model: clients.sharedModel.js.
  • Style: lowerCamelCase.
  • Name: use the feature that their represents to name it.
  • Examples:
    • Data service: clientsDataSrv.
    • Util or business service: clientsUtil.
    • Shared model: clientsSharedModel.
  • Example of register a data service, util and shared model in code:
import clientsDataSrv from './clients.dataSrv.js';
import clientsUtil from './clients.util.js';
import clientsSharedModel from './clients.sharedModel.js';

const clientsList = angular
	.module('awCrm.clients.list', [])
	.service('clientsDataSrv', clientsDataSrv)
	.service('clientsUtil', clientsUtil)
	.service('clientsSharedModel', clientsSharedModel)
	.name;

export default clientsList;

⬆ back to top

Directives

  • Filename: scrollTo.drtv.js.
  • Style: directives are tricky because they have a name to register it in Angular and other to use it in a view (is created automatically).
    • Register: lowerCamelCase.
    • Use in a view: dash-separator.
  • Name: use the feature that represents to name it.
  • Example: scrollTo.
  • Example of register a directive in code:
import scrollTo from './scrollTo.drtv.js';

const module = angular
	.module('awCrm.directives.scrollTo', [])
	.directive('scrollTo', scrollTo)
	.name;

export default module;

⬆ back to top

Artifacts Recipes

Dependency injection

To include the dependency injection in the Angular artifacts implemented as ES6 classes it's recommended to use the next pattern:

class SomeCtrl {
	
	static get $inject(){
		return ['DepModule'];
	}

	constructor(DepModule){
		this.DepModule = DepModule;
	}
}

Why a static getter? a getter in ES6, adds a pseudo-property in the class prototype that can be read using classInstance.property. That is what Angular need when instantiating an artifact.

⬆ back to top

Modules

Root module:

import depModule from 'path/to/depModule';

angular
	.module('awCrm', [
		depModule1,
		depModule2,
		depModule3
	]);

Common module:

import SomeCtrl from './some.ctrl.js';
import SomeSrv from './some.srv.js';

const someModule = angular
	.module('awCrm', [])
	.service('someSrv', SomeSrv)
	.controller('SomeCtrl', SomeCtrl)
	.name;

export default someModule;
  • Declare modules in a const and use chaining with the getter syntax. Why?.
  • Assign the module name (notice the .name at the end) to a const to return that name. That makes able to import in other modules or dependencies array.
  • Use the import name (for example, SomeCtrl in the example) when registering an artifact (controller, service, etcetera). Why?.
  • Declare the dependencies of the artifact within through the $inject service (see dependency injection). This makes clear the module definitions and keeps DRY each artifact.
  • When a module has more than 2 dependencies, is recommended to declare it as a list instead of inline.

⬆ back to top

Config blocks

Register the block, is a little bit tricky. See the example above. Dependency injections are different than in other artifacts, in that case, we have to declare it in the module definition.

Definition of the block as a class:

class LogDecorator {
	
	constructor($provide){
		$provide.decorator('$log', ['$delegate', '$injector']){
			// code of the decorator
		}
	}

}

⬆ back to top

Controllers

class Section1Ctrl {
	
	static get $inject(){
		return ['$timeout'];
	}

	constructor($timeout){
		this.$timeout = $timeout;
		this.sumResult = null;
		this.subResult = null;
	}

	doSum(a,b){
		this.sumResult = a+b;
		this.$timeout( () => {
			console.log('delayed log');
		}, 2000);
	}

	doSub(a,b){
		this.subResult = a-b;
		return this.subResult;
	}

}

export default Section1Ctrl;
  • DOM manipulation: do not manipulate DOM in a controller, use a directive instead.
  • Use constructor:
    • To manage dependency injection ($timeout in the example). And bin the dependency to this.
    • To define public (should bind to this) and private properties.
  • ControllerAs syntax: Why?.
  • Presentational logic only (MVVM): presentation logic only inside controller, delegate to service the business logic. Why?.
  • Manually identify dependencies: use $inject (see dependency injection).

⬆ back to top

Data model

Since ES6, I use to wrap data model in a .service angular artifact instead in ES5 approach that I used to wrap it in a .factory.

class Depcontainer {
	
	static get $inject(){
		return ['$timeout', 'dep2'];
	}

	constructor($timeout, dep){ // dependency injections

		return class Section1Model {

			constructor(arg, arg2){ // data model itself
				this.property = arg;
				this.property2 = arg2;
			}

			method(){
				// some code here
			}
		};
	}
}

export default DepContainer;

⬆ back to top

Data service

I use the data service to encapsulate the responsibility to interact with the API. I use to wrap Data service in a .service() angular artifact.

let getMockClient;

class ClientsDataSrv {
	
	static get $inject(){
		return ['$http'];
	}

	constructor($http){
		this.$http = $http; // Bound injection
		this.isCalled = false; // public property

		getMockClient = () => { // private method
			return {name:'Carlos', surname:'Alcaide', age:23};
		};
	}

	getClient(idClient){
		return this.$http({
			url: '/client/'+idClient,
			method: 'GET'
		}).then( (result) => {
			return result;
		});
	}

	getClients(){
		return this.$http({
			ur: '/clients',
			method: 'GET'
		}).then( (result) => {
			return result;
		});
	}

	getStaticInfo(){
		return getMockClient();
	}

}

export default ClientsDataSrv;

Because ES6 approach:

  • The dependencies are set as a public property of the class, so you can not use $http, should use this.$http.
  • Define (if you need it them) a private method in the constructor, first you should declare a let outside the class.

⬆ back to top

Util service

Util services are useful when you want to encapsulate some logic that is not part of presentational logic. A clear example could be a util service that encapsulates date methods like today() => Thursday 09-06-2016. Is used in the view, but is not part of the presentational logic, so for this cases use a util service.

The recipe is the same that in Data service.

⬆ back to top

Shared model

Shared models are useful when you want to share models (essentially state) between controllers or whatever angular artifact. The recipe is the same that in Data service but because the shared models are ONLY for share data, they have not to deal with dependencies.

⬆ back to top

Factories

Since I use ES6 approach, I deprecated the use of factories, here the reference about that.

⬆ back to top

Filters

class ToLowerCase {
	
	constructor($timeout){
		return (input) => {
			return input.toLowerCase();
		}
	}
}

export default ToLowerCase;
  • The filter function should be a pure function, should be stateless and idempotent.

⬆ back to top

Directives

Actually, the community recommends using component instead directive artifact. Note that I am not saying use the component architecture, just use the .component angular artifact. Just in two cases is recommended still using directives: - You need to use explicitly link function. - Your component doesn't have any template or it well be specified as attribute (strict).

const AutoFocus = ($timeout) => ({
	restrict: 'A',
	link($scope, $element, $attrs){
		$scope.$watch($attrs.autoFocus, (newValue, oldValue) => {
			if(!newValue){
				return;
			}
		});
	}
});

AutoFocus.$inject = ['$timeout'];

export default AutoFocus;

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published