In Angular 2, I\'m trying to get barrels to work as described in the documentation.
The official Angular 2 style guide t
AFAIK SystemJS
doesn't understand barrels by itself but Webpack
does.
BTW, After digging up how Angular
does it for it's modules, I've found a solution
In system.config.js you'll need to do the following things
Note: the parent directory of a index.ts
is the barrel
, ICYDK
// map tells the System loader where to look for things
var map = {
'app': 'app', // 'dist',
'rxjs': 'node_modules/rxjs',
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
'@angular': 'node_modules/@angular',
'barrel': 'path/to/your/barrel'
};
packages
(explained later)##// packages tells the System loader how to load when no filename and/or no extension
var packages = {
'app': { main: 'app/boot.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { defaultExtension: 'js' }
};
packageNames
, just like angularvar packageNames = [
'@angular/common',
...
'@angular/upgrade',
'barrel'
];
And you're done.
##
Why we used packageNames
instead of packages
is because you'll have to repeat the { main: 'index.js', defaultExtension: 'js' }
(filename and extension) for every barrel
, but angular is already doing it by looping with this.
packageNames.forEach(function(pkgName) {
packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});
Which is ultimately adding them to packages
.
Usage
import {something} from '../../barrel'; // relative path to directory of barrel
and
import {something} from 'barrel'; // name of barrel
Both work, but the later one fails to provide intellisense
and shows an error saying cannot find module 'barrel'
. Which I don't have a solution for. But I'll add it when I do.
I followed A_Singh's idea, but had to adapt it a little since my systemjs.config.js
is different, based on the tour-of-heroes tutorial as of right now. (June 3, 2016 - RC1).
This modification works, and under VisualStudio Code, Intellisense
worked well for me.
I have also, been playing with directory structure to keep things modular, and here is where the barrels made sense.
This is how I have modified the tour-of-heroes
project
app
|- main.ts
|- app.component.ts
|- app.component.css
|-+ components
| |-+ dashboard
| |- dashboard.component.css
| |- dashboard.component.html
| |- dashboard.component.ts
|-+ modules
| |-+ hero
| |- index.ts <-- //The barrel for the Hero module
| |-+ components
| | |-+ detail
| | | |- hero-detail.component.css
| | | |- hero-detail.component.html
| | | |- hero-detail.component.ts
| | |-+ list
| | |- hero-list.component.css
| | |- hero-list.component.html
| | |- hero-list.component.ts
| |-+ models
| | |- hero.model.ts
| |-+ services
| |- hero.service.ts
| |- mock-heroes.ts
And here the updated systemjs.config.js
(function(global) {
// map tells the System loader where to look for things
var map = {
'app': 'app', // 'dist',
'@angular': 'node_modules/@angular',
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
'rxjs': 'node_modules/rxjs',
// The added barrel map
'hero': 'app/modules/hero'
};
var packages = {
'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'angular2-in-memory-web-api': { defaultExtension: 'js' },
// The package definition (notice I had to declare index.js)
'hero': { main: 'index.js', defaultExtension: 'js' }
};
var ngPackageNames = [
'common',
'compiler',
'core',
'http',
'platform-browser',
'platform-browser-dynamic',
'router',
'router-deprecated',
'upgrade',
];
ngPackageNames.forEach(function(pkgName) {
packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
});
var config = {
map: map,
packages: packages
}
System.config(config);
})(this);
And finally, here is how the app.component.ts
now looks like
import { Component } from '@angular/core';
import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { HeroListComponent, HeroDetailComponent, HeroService } from './modules/hero';
@Component({
selector: 'my-app',
directives: [ROUTER_DIRECTIVES],
providers: [
ROUTER_PROVIDERS,
HeroService
],
template: `
<h1>{{title}}</h1>
<nav>
<a [routerLink]="['Dashboard']">Dashboard</a>
<a [routerLink]="['HeroList']">Hero List</a>
</nav>
<router-outlet></router-outlet>
`,
styleUrls: ['app/app.component.css']
})
@RouteConfig([
{
path: '/dashboard',
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true
},
{
path: '/hero-list',
name: 'HeroList',
component: HeroListComponent
},
{
path: '/detail/:id',
name: 'HeroDetail',
component: HeroDetailComponent
}
])
export class AppComponent {
title: string = 'Tour of Heroes';
}
One final note, you could do the import as
import { HeroListComponent, HeroDetailComponent, HeroService } from 'hero';
And it will work, however, since it is not technically a NodeJS imported module, VS Code complains that it cannot find it.
So, my personal choice is to leave the explicit ./modules/hero
, it helps me know that it is one of my modules and not an imported one and I'm happy to not see red lines.
This is all seems overly complicated.
There's no need to add anything to map, since app
, which should be containing everything is already in there. We can just create an array with the subpackages. In my case that was:
var subPackageNames = [
'+heroes',
'+heroes/shared',
'+heroes/hero-list',
'+heroes/hero-detail',
'+heroes/hero-dashboard'
];
and then modify the provided packIndex
function to take a second argument
function packIndex(pkgName, baseName) {
packages[baseName+pkgName] = { main: 'index.js', defaultExtension: 'js' };
}
now we can add our sub packages to the packages object just like angular does.
ngPackageNames.forEach(name => setPackageConfig(name, '@angular/'));
subPackageNames.forEach(name => packIndex(name, 'app/'));