Common conventions for scalable applications

In all fairness, we have already tackled a good number of the common concerns that modern web developers confront when building applications, small and large alike, nowadays. Therefore, it makes sense to define an architecture that will separate the aforementioned concerns into separate domain folders, catering to media assets and shared code units.

At the time of writing, a commonly agreed pattern for defining project directories embraces the idea of structuring files by features, or contexts. Sometimes, two contexts may require sharing the same entities, and that is fine (as long as it does not become a common thing in our project, which would denote a serious design issue). The following example, applied to our previous work on pomodoro components, depicts this scheme:

.
├── tasks feature
│   ├── Task model
│   ├── Tasks service
│   ├── Task table component
│   ├── 
Task pomodoros component
│   └── Task tooltip directive

├── timer feature
│   └── Timer component

├── admin
│   ├── Authentication service
│   ├── Login component
│   └── Editor component


└── shared
    ├── components shared across features
    ├── pipes shared across features
    ├── directives shared across features
    ├── global models and services
    └── shared media assets

As we can see, the first step is to define the different features our application needs, keeping in mind that each one should make sense on its own in isolation from the others. Once we define the set of features required, we will create a folder for each one. Each folder will be filled then with the components, directives, pipes, models, and services that shape the feature it represents. Always remember the principles of encapsulation and reusability when defining your features set.

If the number of files required for any given feature exceeds a logical threshold, then it is fine to organize things a bit and split our files into different folders by type. Let's figure out that our tasks feature has grown out of control and we have up to 30 files in the folder, between components, directives, pipes, services, test specs, and the like. Identifying code units would become a burden, so applying an additional layer or organization by type would definitely help:

.
├── tasks feature
│   ├── components/
│   │   └── component files...
│   ├── directives/
│   │   ├── directive X
│   │   └── directive Y
│   ├── pipes/
│   │   └── pipe files...
│   ├── models/
│   │   └── models...
│   └── services/
│       └── services...
├── timer feature
│   └── Timer component
├── admin
│   ├── Authentication service
│   ├── Login component
│   └── Editor component
└── shared
    ├── ...
    └── Etc

As we can see, it is perfectly fine to have in the same project feature folders with all files at the same level and feature folders containing an additional level of nesting by type. Where to set the threshold is up to you but common sense says that 12 or 15 code units in the same feature folder make a good case for an additional nesting level based on types. We can also combine them by introducing additional nesting levels based on multiple features that fall under the umbrella of an upper context as well, hosting its implementation in a sub-tree by type:

.
├── tasks feature
│   ├── tasks component and template
│   ├── tasks-editor feature/
│   │   └── task-editor components and templates
│   ├── tasks-list feature/
│   │   ├── components/
│   │   │   └── tasks-list components and templates
│   │   └── pipes/
│   │   │   └── tasks-list-specific pipes
│   └── task-reports feature/
│       └── services...
...

File and module naming conventions

Each one of our feature folders will host a wide range of files so we need a consistent naming convention to prevent filename collisions while we ensure that the different code units are easy to locate.

The following list summarizes the current conventions enforced by the community:

  • Each file should contain a single code unit. Simply put, each component, directive, service, pipe, and so on should live in its own file. This way, we contribute to a better organization of code.
  • Files and directories are named in lower-kebab-case.
  • Files representing components, directives, pipes, and services should append a type suffix to their name: video-player.ts will become video-player.component.ts.
  • Any component's external HTML template or CSS style sheet filename will match the component filename, including the suffix. Our video-player.component.ts might be accompanied by video-player.component.css and video-player.component.html.
  • Directive selectors and pipe names are camelCased, while component selectors are lower-kebab-cased. Plus, it is strongly advised to add a custom prefix of our choice to prevent name collisions with other component libraries. For example, following up our video player component, it may be represented as <vp-video-player>, where vp- (which stands for video-player) is our custom prefix.
  • Modules are named by following the rule of taking a PascalCased self-descriptive name, plus the type it represents. For example, if we see a module named VideoPlayerComponent, we can easily tell it is a component. The custom prefix in use for selectors (vp- in our example) should not be part of the module name.

Models and interfaces require special attention though. Depending on your application architecture, model types will feature more or less relevance. Architectures such as MVC, MVVM, Flux, or Redux tackle models from different standpoints and grades of importance. Ultimately, it will be up to you and your architectural design pattern of choice to approach models and their naming convention in one way or another. This book will not be opinionated in that sense, although we do enforce interface models in our example application and will create modules for them.

Ensuring seamless scalability with facades or barrels

Each component and shared context of business logic in our application is intended to integrate with the other pieces of the puzzle in a simple and straightforward way. Clients of each subdomain are not concerned about the internal structure of the subdomain itself. If our timer feature, for example, evolves to the point of having two dozen components and directives that need to be reorganized into different folder levels, external consumers of its functionalities should remain unaffected.

This can be done using facade modules that conceal the internal structure of the code by exposing always the same layer of endpoints, hiding the implementation details outside the boundaries of the feature.

In our previous example, the tasks feature evolves from being a handful of files inside the same folder into a type-driven set of folders. What if we are using several of its components elsewhere in our application? No worries! A facade module like this would definitely help:

app/tasks/tasks.ts

import TaskComponent from './task.component';
import TaskDetailsComponent from './task-details.component';

export {
  TaskComponent,
  TaskDetailsComponent
}

Then, we can import any of these components from any other distant corner of our application, like this:

app/example/example.ts

import { TaskDetailsComponent } from '../tasks/tasks'

What if the tasks feature keeps growing and needs to be refactored into different folders? We would just update the path references in our app/tasks/tasks.ts facade module, and our client code at app/example/example.ts would remain the same.

In the Angular lingo, this kind of design pattern also receives the name of barrel. In Angular's own words:

A barrel is an Angular library module consisting of a logical grouping of single-purpose modules such as Component and Directive.

Take that into account in order to avoid confusion when bumping into this term in the future. Barrels are also usually grouped and distributed in larger packages named bundles. An example of this is the angular2/bundles/router.js bundle, for instance. We will not create packaged bundles in this book, but we will thoroughly use the ones that come with Angular 2 when implementing HTTP connection, routing, or animation functionalities.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset