Skip to content

Commit 0aae614

Browse files
zeroualzeroual
authored and
zeroual
committed
add readme to explain how the front end is structured and make some angular guideline style
1 parent 6ee819a commit 0aae614

File tree

2 files changed

+378
-1
lines changed

2 files changed

+378
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Run the commands
8383

8484
- how the application is layered ? [check here](docs/application-layers.md)
8585
- how the back end is structured ? [check here](docs/back-end-structure.md)
86-
- how front end is structured ? //TODO
86+
- how front end is structured ? [check here](docs/front-end-structure.md)
8787
- how automated tests are structured ? //TODO
8888

8989
## Copyright and license

docs/front-end-structure.md

+377
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
# Front end structure
2+
3+
The frontend should be built around a view-specific model (which is not the domain model),
4+
and should only handle presentation logic, but no business logic. These are the three layers of the frontend.
5+
6+
## The View Layer
7+
The view layer is composed of Html templates, CSS, and any Angular directives representing the different UI components
8+
9+
## The Controller Layer
10+
The controller layer is made of Angular controllers that glue the data retrieved from the backend and the view together.
11+
The controller initializes the view model and defines how the view should react to model changes and vice-versa.
12+
13+
One of the main responsibilities of the controller is to perform frontend validations.
14+
Any validations done on the frontend are for user convenience only - for example they are
15+
useful to immediately inform the user that a field is required.
16+
17+
Any frontend validations need to be repeated in the backend layer level due to security reasons,
18+
as the frontend validations can be easily bypassed.
19+
20+
## The Frontend Services Layer
21+
A set of Angular services that allow to interact with the backend and that can be injected into Angular controllers
22+
23+
# Guideline style
24+
This guide includes best practices for one-way dataflow, event delegation,component architecture and component routing.
25+
26+
The goal of this style guide is to present a set of best practices and style guidelines for one AngularJS application.
27+
28+
29+
## Directory structure
30+
31+
Since this is a large AngularJS application and has many components it's best to structure it in the following directory hierarchy.
32+
33+
* high-level divisions by functionality and lower-level divisions by component types.
34+
35+
Here is the layout:
36+
37+
```
38+
.
39+
├── app
40+
│   ├── app.js
41+
│   ├── app.config.js
42+
│   ├── app.run.js
43+
│   ├── common
44+
│   │   ├── controllers
45+
│   │   ├── directives
46+
│   │   ├── filters
47+
│   │   └── services
48+
│   ├── home
49+
│   │   ├── controllers
50+
│   │   │   ├── FirstCtrl.js
51+
│   │   │   └── SecondCtrl.js
52+
│   │   ├── directives
53+
│   │   │   └── directive1.js
54+
│ │ │ └── directive1.template.html
55+
│   │   ├── filters
56+
│   │   │   ├── filter1.js
57+
│   │   │   └── filter2.js
58+
│   │   └── services
59+
│   │   ├── service1.js
60+
│   │   └── service2.js
61+
│   └── about
62+
│   ├── controllers
63+
│   │   └── ThirdCtrl.js
64+
│   ├── directives
65+
│   │   ├── directive2.js
66+
│   │   └── directive3.js
67+
│   ├── filters
68+
│   │   └── filter3.js
69+
│   └── services
70+
│   └── service3.js
71+
├── assets
72+
├── bower_components
73+
└── index.html
74+
```
75+
76+
* The `app.js` file contain route definitions, configuration and/or manual bootstrap.
77+
78+
Conventions about component naming can be found in each component section.
79+
80+
## Naming conventions
81+
The following table is shown the naming conventions for every element:
82+
83+
Element | Naming style | Example | usage
84+
----|------|----|--------
85+
Modules | lowerCamelCase | angularApp |
86+
Controllers | Functionality + 'Controller' | AdminController |
87+
Directives | lowerCamelCase | userInfo |
88+
Filters | lowerCamelCase | userFilter |
89+
Services | UpperCamelCase | User | constructor
90+
Factories | lowerCamelCase | dataFactory | others
91+
92+
## Others
93+
94+
* Use:
95+
* `$timeout` instead of `setTimeout`
96+
* `$interval` instead of `setInterval`
97+
* `$window` instead of `window`
98+
* `$document` instead of `document`
99+
* `$http` instead of `$.ajax`
100+
* `$location` instead of `window.location` or `$window.location`
101+
* `$cookies` instead of `document.cookie`
102+
103+
This will make your testing easier and in some cases prevent unexpected behaviour (for example, if you missed `$scope.$apply` in `setTimeout`).
104+
105+
* Use promises (`$q`) instead of callbacks. It will make your code look more elegant and clean, and save you from callback hell.
106+
* Use `$resource` instead of `$http` when possible. The higher level of abstraction will save you from redundancy.
107+
* Don't use globals. Resolve all dependencies using Dependency Injection, this will prevent bugs and monkey patching when testing.
108+
109+
* Do not pollute your `$scope`. Only add functions and variables that are being used in the templates.
110+
* Prefer the usage of [controllers instead of `ngInit`](https://github.com/angular/angular.js/commit/010d9b6853a9d2718b095e4c017c9bd5f135e0b0). There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope. The expression passed to `ngInit` should go through lexing, parsing and evaluation by the Angular interpreter implemented inside the `$parse` service. This leads to:
111+
- Performance impact, because the interpreter is implemented in JavaScript
112+
- The caching of the parsed expressions inside the `$parse` service doesn't make a lot of sense in most cases, since `ngInit` expressions are often evaluated only once
113+
- Is error-prone, since you're writing strings inside your templates, there's no syntax highlighting and further support by your editor
114+
- No run-time errors are thrown
115+
* Do not use `$` prefix for the names of variables, properties and methods. This prefix is reserved for AngularJS usage.
116+
* Do not use `JQUERY` inside your app, If you must, use `JQLite` instead with `angular.element`.
117+
* When resolving dependencies through the DI mechanism of AngularJS, sort the dependencies by their type - the built-in AngularJS dependencies should be first, followed by your custom ones:
118+
119+
```javascript
120+
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
121+
return {
122+
//Something
123+
};
124+
});
125+
```
126+
127+
# Modules
128+
129+
* Modules should be named with lowerCamelCase. For indicating that module `b` is submodule of module `a` you can nest them by using namespacing like: `a.b`.
130+
131+
There are two common ways for structuring the modules:
132+
133+
0. By functionality
134+
0. By component type
135+
136+
Currently there's not a big difference, but the first way looks cleaner. Also, if lazy-loading modules is implemented (currently not in the AngularJS roadmap), it will improve the app's performance.
137+
138+
# Controllers
139+
140+
* Do not manipulate DOM in your controllers, this will make your controllers harder for testing and will violate the [Separation of Concerns principle](https://en.wikipedia.org/wiki/Separation_of_concerns). Use directives instead.
141+
* The naming of the controller is done using the controller's functionality (for example shopping cart, homepage, admin panel) and the substring `Controller` in the end.
142+
* Controllers are plain javascript [constructors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor), so they will be named UpperCamelCase (`HomePageController`, `ShoppingCartController`, `AdminPanelController`, etc.).
143+
* The controllers should not be defined as globals (even though AngularJS allows this, it is a bad practice to pollute the global namespace).
144+
145+
* Avoid use of `$scope` service to define functions and properties as part of controllers. Use `$scope` only if It's really needed:
146+
0. For publish and subscribe to events: `$scope.$emit`, `$scope.$broadcast`, and `$scope.$on`.
147+
0. For _watch_ values or collections: `$scope.$watch`, `$scope.$watchCollection`
148+
149+
* Prefer using `controller as` syntax and capture `this` using a variable:
150+
151+
```html
152+
<div ng-controller="MainCtrl as main">
153+
{{ main.things }}
154+
</div>
155+
```
156+
157+
```JavaScript
158+
app.controller('MainCtrl', function MainCtrl($http) {
159+
var vm = this;
160+
//a clearer visual connection on how is defined on the view
161+
vm.title = 'Some title';
162+
vm.description = 'Some description';
163+
164+
$http.get('/api/main/things').then(function (response) {
165+
vm.things = response.data.things; // Adding 'things' as a property of the controller
166+
});
167+
}
168+
);
169+
```
170+
171+
Avoid using `this` keyword repeatedly inside a controller:
172+
173+
```JavaScript
174+
app.controller('MainCtrl', function MainCtrl($http) {
175+
// Avoid
176+
this.title = 'Some title';
177+
this.description = 'Some description';
178+
179+
$http.get('/api/main/things').then(function (response) {
180+
// Warning! 'this' is in a different context here.
181+
// The property will not be added as part of the controller context
182+
this.things = response.data.things;
183+
});
184+
});
185+
```
186+
187+
Using a consistent and short variable name is preferred, for example `vm`.
188+
189+
The main benefits of using this syntax:
190+
* Creates an 'isolated' component - binded properties are not part of `$scope` prototype chain. This is good practice since `$scope` prototype inheritance has some major drawbacks (this is probably the reason it was removed on Angular 2):
191+
* It is hard to track where data is coming from.
192+
* Scope's value changes can affect places you did not intend to affect.
193+
* Harder to refactor.
194+
* The '[dot rule](http://jimhoskins.com/2012/12/14/nested-scopes-in-angularjs.html)'.
195+
* Removes the use of `$scope` when no need for special operations (as mentioned above). This is a good preparation for AngularJS V2.
196+
* Syntax is closer to that of a 'vanilla' JavaScript constructor
197+
198+
Digging more into `controller as`: [digging-into-angulars-controller-as-syntax](http://toddmotto.com/digging-into-angulars-controller-as-syntax/)
199+
200+
* Make the controllers as lean as possible. Abstract commonly used functions into a service.
201+
202+
Why business logic / app state inside controllers is bad?
203+
* Controllers instantiated for each view and dies when the view unloads
204+
* Controllers are not reusable - they are coupled with the view
205+
* Controllers are not meant to be injected
206+
207+
208+
* Communicate within different controllers using method invocation (possible when a child wants to communicate with its parent) or `$emit`, `$broadcast` and `$on` methods. The emitted and broadcasted messages should be kept to a minimum.
209+
* Make a list of all messages which are passed using `$emit`, `$broadcast` and manage it carefully because of name collisions and possible bugs.
210+
211+
Example:
212+
213+
```JavaScript
214+
// app.js
215+
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
216+
Custom events:
217+
- 'authorization-message' - description of the message
218+
- { user, role, action } - data format
219+
- user - a string, which contains the username
220+
- role - an ID of the role the user has
221+
- action - specific action the user tries to perform
222+
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
223+
```
224+
225+
* When you need to format data encapsulate the formatting logic into a [filter](#filters) and declare it as dependency:
226+
227+
```JavaScript
228+
module.filter('myFormat', function myFormat() {
229+
return function () {
230+
// ...
231+
};
232+
});
233+
234+
module.controller('MyCtrl', function MyCtrl($scope, myFormatFilter) {
235+
// ...
236+
});
237+
```
238+
* In case of nested controllers use "nested scoping" (the `controllerAs` syntax):
239+
240+
**app.js**
241+
```javascript
242+
module.config(function ($routeProvider) {
243+
$routeProvider
244+
.when('/route', {
245+
templateUrl: 'partials/template.html',
246+
controller: 'HomeCtrl',
247+
controllerAs: 'home'
248+
});
249+
});
250+
```
251+
**HomeCtrl**
252+
```javascript
253+
function HomeCtrl() {
254+
var vm = this;
255+
256+
vm.bindingValue = 42;
257+
}
258+
```
259+
**template.html**
260+
```html
261+
<div ng-bind="home.bindingValue"></div>
262+
```
263+
264+
# Directives
265+
266+
* Name your directives with lowerCamelCase.
267+
* Use `scope` instead of `$scope` in your link function. In the compile, post/pre link functions you have already defined arguments which will be passed when the function is invoked, you won't be able to change them using DI. This style is also used in AngularJS's source code.
268+
* Use custom prefixes for your directives to prevent name collisions with third-party libraries.
269+
* Do not use `ng` or `ui` prefixes since they are reserved for AngularJS and AngularJS UI usage.
270+
* DOM manipulations must be done only through directives.
271+
* Create an isolated scope when you develop reusable components.
272+
* Use directives as attributes or elements instead of comments or classes, this will make your code more readable.
273+
* Use `scope.$on('$destroy', fn)` for cleaning up. This is especially useful when you're wrapping third-party plugins as directives.
274+
* Do not forget to use `$sce` when you should deal with untrusted content.
275+
276+
# Filters
277+
278+
* Name your filters with lowerCamelCase.
279+
* Make your filters as light as possible. They are called often during the `$digest` loop so creating a slow filter will slow down your app.
280+
* Do a single thing in your filters, keep them coherent. More complex manipulations can be achieved by piping existing filters.
281+
282+
# Services
283+
284+
This section includes information about the service component in AngularJS. It is not dependent of the way of definition (i.e. as provider, `.factory`, `.service`), except if explicitly mentioned.
285+
286+
* Use camelCase to name your services.
287+
* UpperCamelCase (PascalCase) for naming your services, used as constructor functions i.e.:
288+
289+
```JavaScript
290+
module.controller('MainCtrl', function MainCtrl(User) {
291+
var vm = this;
292+
vm.user = new User('foo', 42);
293+
}
294+
);
295+
296+
module.factory('User', function User(name, age) {
297+
this.name = name;
298+
this.age = age;
299+
});
300+
```
301+
302+
* lowerCamelCase for all other services.
303+
304+
* Encapsulate all the business logic in services.
305+
306+
See 'Avoid writing business logic inside controllers' for an example of a controller consuming this service.
307+
* Services representing the domain preferably a `service` instead of a `factory`. In this way we can take advantage of the "klassical" inheritance easier:
308+
309+
* For session-level cache you can use `$cacheFactory`. This should be used to cache results from requests or heavy computations.
310+
* If given service requires configuration define the service as provider and configure it in the `config` callback like:
311+
312+
```JavaScript
313+
angular.module('demo', [])
314+
.config(function ($provide) {
315+
$provide.provider('sample', function () {
316+
var foo = 42;
317+
return {
318+
setFoo: function (f) {
319+
foo = f;
320+
},
321+
$get: function () {
322+
return {
323+
foo: foo
324+
};
325+
}
326+
};
327+
});
328+
});
329+
330+
var demo = angular.module('demo');
331+
332+
demo.config(function (sampleProvider) {
333+
sampleProvider.setFoo(41);
334+
});
335+
```
336+
337+
# Templates
338+
339+
* Avoid writing complex expressions in the templates.
340+
* When you need to set the `src` of an image dynamically use `ng-src` instead of `src` with `{{ }}` template.
341+
* When you need to set the `href` of an anchor tag dynamically use `ng-href` instead of `href` with `{{ }}` template.
342+
343+
# Routing
344+
345+
* Use `resolve` to resolve dependencies before the view is shown.
346+
* Do not place explicit RESTful calls inside the `resolve` callback. Isolate all the requests inside appropriate services. This way you can enable caching and follow the separation of concerns principle.
347+
348+
# i18n
349+
350+
* For newer versions of the framework (>=1.4.0) use the built-in i18n tools, when using older versions (<1.4.0) use [`angular-translate`](https://github.com/angular-translate/angular-translate).
351+
352+
# Performance
353+
354+
* Optimize the digest cycle
355+
356+
* Watch only the most vital variables. When required to invoke the `$digest` loop explicitly (it should happen only in exceptional cases), invoke it only when required (for example: when using real-time communication, don't cause a `$digest` loop in each received message).
357+
* For content that is initialized only once and then never changed, use single-time watchers like [`bindonce`](https://github.com/Pasvaz/bindonce) for older versions of AngularJS or one-time bindings in AngularJS >=1.3.0.
358+
```html
359+
<div>
360+
{{ ::main.things }}
361+
</div>
362+
```
363+
or
364+
```html
365+
<div ng-bind="::main.things"></div>
366+
```
367+
After that, **no** watchers will be created for `main.things` and any changes of `main.things` will not update the view.
368+
* Make the computations in `$watch` as simple as possible. Making heavy and slow computations in a single `$watch` will slow down the whole application (the `$digest` loop is done in a single thread because of the single-threaded nature of JavaScript).
369+
* When watching collections, do not watch them deeply when not strongly required. Better use `$watchCollection`, which performs a shallow check for equality of the result of the watched expression and the previous value of the expression's evaluation.
370+
* Set third parameter in `$timeout` function to false to skip the `$digest` loop when no watched variables are impacted by the invocation of the `$timeout` callback function.
371+
* When dealing with big collections, which change rarely, [use immutable data structures](http://blog.mgechev.com/2015/03/02/immutability-in-angularjs-immutablejs).
372+
373+
374+
* Consider decreasing number of network requests by bundling/caching html template files into your main javascript file, using [grunt-html2js](https://github.com/karlgoldstein/grunt-html2js) / [gulp-html2js](https://github.com/fraserxu/gulp-html2js). See [here](http://ng-learn.org/2014/08/Populating_template_cache_with_html2js/) and [here](http://slides.com/yanivefraim-1/real-world-angularjs#/34) for details. This is particularly useful when the project has a lot of small html templates that can be a part of the main (minified and gzipped) javascript file.
375+
376+
## Angular docs
377+
For anything else, including API reference, check the [Angular documentation](//docs.angularjs.org/api).

0 commit comments

Comments
 (0)