So far, we have talked about components in terms of them being dedicated to solving one task well. However, there are other constructs that can be used in Angular, such as pipes, directives, and services. A lot of our components will find themselves belonging to a common theme, such as products or user management and so on. When we realize what constructs belong to the same theme, we also realize that some of these constructs are constructs we want to use elsewhere in the application. Conversely, some constructs are only meant to be used in the context of the mentioned theme. To protect the latter constructs from unintended use, we would like to group them in a facade-like way and put a protective layer between the constructs and the rest of the application. The way to do that in pure ES2015 modules is to create a facade file, in which public constructs are exported and others are not, like so:
// an old facade file, index.ts
import { MyComponent } from 'my.component';
import { MyService } from 'my.service';
export MyComponent;
export MyService;
Imagine we have a directory consisting of the following files:
/my
MyComponent.ts
MyService.ts
MyOtherService.ts
index.ts
The intent of creating a facade file here is to ensure there is only one place from where you import all the constructs you need. In this case that would be the index.ts file. A consumer of the preceding directory would do the following:
// consumer.ts
import * as my from './my';
let component = new my.MyComponent();
let service = new MyService();
MyOtherService is not being exposed by the index.ts file though, so attempting to access it like we do in consumer.ts would lead to an error. You could theoretically specify the full path to the construct but you are supposed to be using the barrel. Barrels are usually meant to be used to easily access your constructs without having to write import statements that are five miles long, like so:
// index.ts
import { Service } from '../../../path-to-service';
import { AnotherService } from '../../path-to-other-service';
export Service;
export AnotherService;
// consumer.ts
// the long and tedious way
import { Service } from '../../../path-to-service';
import { AnotherService } from '../../path-to-other-service';
// the easier way using a barrel
import * as barrel from './index';
let service = new barrel.Service();
let anotherService = new barrel.AnotherService();
As you can see that barrel, index.ts is the one that is responsible for knowing where all your constructs are located. This also means that were you to move files around, changing directories for certain constructs, the barrel file is the only one where updating the paths to these constructs is needed.
The Angular way of dealing with this is to use Angular modules. An Angular module looks like the following:
// mvc/MvcExample/src/app/my/my.module.ts
import { NgModule } from "@angular/core";
import { MyComponent } from "./my.component";
import { MyPipe } from "./my.pipe";
@NgModule({
imports: [],
exports: [MyComponent],
declarations: [MyComponent, MyPipe],
providers: []
})
export class MyModule {}
The effect of putting MyComponent and MyPipe into the declarations property of the module is so that these components can be freely used within MyModule. For example, you can use MyPipe within the MyComponent template. However, if you want to use MyComponent outside of this module, in a component belonging to another module, you will need to export it. We do that by placing it in the array belonging to the exports property:
exports: [MyComponent]
Angular takes the concept of a module way beyond grouping. Some instructions in our NgModule are meant for the compiler so that it knows how to assemble the components. Some other instructions we give it are meant for the Dependency Injection tree. Think of the Angular module as a configuration point, but also as the place where you logically divide up your application in to cohesive blocks of code.
On the object sent to the @NgModule decorator, there are properties you can set that have different meanings. The most important properties are:
- The declarations property is an array that states what belongs to our module
- The imports property is an array that states what other Angular modules we are dependent on; it could be basic Angular directives or common functionality that we want to use inside of our module
- The exports property is an array stating what should be made available for any module importing this module; MyComponent is made public whereas MyPipe would become private for this module only
- The providers property is an array stating what services should be injectable into constructs belonging to this module, that is, to constructs that are listed in the declarations array