Skip to content

feat(blueprints): adding generation of routing config #342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,37 @@ with two sub-routes. The file structure will be as follows:
| | |-- hero.service.ts
| |-- ...
|-- app.ts
|-- route-config.ts
...
```

Afterwards to use the new route open your main app component, import
`hero-root.component.ts` and add it in the route config:
By default the cli will add the import statements for HeroList and HeroDetail to
`hero-root.component.ts`:

```
@RouteConfig([
{path:'/hero/...', name: 'HeroRoot', component: HeroRoot}
{path:'/', name: 'HeroList', component: HeroListComponent, useAsDefault: true},
{path:'/:id', name: 'HeroDetail', component: HeroDetailComponent}
])
```

The generated `route-config.ts` file is also updated with the following:

```
// DO NOT EDIT THIS FILE
// IT IS AUTO GENERATED BY ANGULAR-CLI
import {HeroRoot} from './hero/hero-root.component';

export const CliRouteConfig = [
{path:'/hero/...', name: 'HeroRoot', component: HeroRoot}
];
```

Visiting `http://localhost:4200/hero` will show the hero list.


There is an optional flag for `skip-router-generation` which will not add the route to the `CliRouteConfig` for the application.

### Creating a build

```bash
Expand Down
3 changes: 3 additions & 0 deletions addon/ng2/blueprints/ng2/files/angular-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"routes": []
}
5 changes: 3 additions & 2 deletions addon/ng2/blueprints/ng2/files/src/app/__name__.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router';

import {CliRouteConfig} from './route-config';

@Component({
selector: '<%= htmlComponentName %>-app',
Expand All @@ -11,7 +11,8 @@ import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router'
})
@RouteConfig([

])
].concat(CliRouteConfig))

export class <%= jsComponentName %>App {
defaultMeaning: number = 42;

Expand Down
7 changes: 7 additions & 0 deletions addon/ng2/blueprints/ng2/files/src/app/route-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

// DO NOT EDIT THIS FILE
// IT IS AUTO GENERATED BY ANGULAR-CLI

export const CliRouteConfig = [

];
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// DO NOT EDIT THIS FILE
// IT IS AUTO GENERATED BY ANGULAR-CLI
<%= imports %>

export const CliRouteConfig = [
<%= routeDefinitions %>
];
39 changes: 39 additions & 0 deletions addon/ng2/blueprints/route-config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var fs = require('fs-extra');
var path = require('path');

var imports, routeDefinitions;

module.exports = {
description: 'Registers the route with the router.',

locals: function (options) {
return generateLocals.call(this, options);
},

beforeInstall: function (options) {
var routeConfigPath = path.join(options.project.root, 'src', 'app', 'route-config.ts');
try {
fs.unlinkSync(routeConfigPath);
} catch (e) {
}
}
};

function generateLocals(options) {
var ngCliConfigPath = path.join(options.project.root, 'angular-cli.json');
var ngCliConfig = JSON.parse(fs.readFileSync(ngCliConfigPath, 'utf-8'));

imports = ngCliConfig.routes.map(route =>
`import {${route.component}} from '${route.componentPath}';`)
.join('\n');

routeDefinitions = ngCliConfig.routes.map(route =>
`{path: '${route.routePath}', name: '${route.component}', component: ${route.component}},`
)
.join('\n');

return {
imports,
routeDefinitions
}
}
81 changes: 68 additions & 13 deletions addon/ng2/blueprints/route/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,71 @@
var stringUtils = require('ember-cli/lib/utilities/string');
var fs = require('fs-extra');
var path = require('path');
var chalk = require('chalk');

module.exports = {
description: ''

//locals: function(options) {
// // Return custom template variables here.
// return {
//
// };
//}

// afterInstall: function(options) {
// // Perform extra work here.
// }
description: 'Generates a route and a template.',

availableOptions: [{
name: 'skip-router-generation',
type: Boolean,
default: false,
aliases: ['srg']
}, {
name: 'default',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering why I had this default property here in the original PR.... it's for the useAsDefault functionality mentioned above.

type: Boolean,
default: false
}],

beforeInstall: function (options, locals) {
if (!options.skipRouterGeneration) {
updateRouteConfig.call(this, 'add', options, locals);
}
},

afterInstall: function (options, locals) {
if (!options.skipRouterGeneration) {
return this.lookupBlueprint('route-config')
.install(options);
}
},

beforeUninstall: function (options, locals) {
updateRouteConfig.call(this, 'remove', options, locals);
},

afterUninstall: function (options, locals) {
return this.lookupBlueprint('route-config')
.install(options);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the CLI, but could this need to be uninstall by any chance ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, but it's actually intended! The route-config blueprint is responsible for auto-generating route files from a config, and that needs to be done after every root change - hence the re-install.

The bit that actually removes a route from the config file is the updateRouteConfig method below.

}
};

function updateRouteConfig(action, options, locals) {
var entity = options.entity;
var actionColorMap = {
add: 'green',
remove: 'red'
};
var color = actionColorMap[action] || 'gray';

this._writeStatusToUI(chalk[color], action + ' route', entity.name);

var ngCliConfigPath = path.join(options.project.root, 'angular-cli.json');

// TODO use default option
var route = {
routePath: `/${locals.dasherizedModuleName}/...`,
component: `${locals.classifiedModuleName}Root`,
componentPath: `./${locals.dasherizedModuleName}/${locals.dasherizedModuleName}-root.component`
};

var ngCliConfig = JSON.parse(fs.readFileSync(ngCliConfigPath, 'utf-8'));
if (action === 'add' && ngCliConfig.routes.findIndex(el => el.routePath === route.routePath) === -1) {
ngCliConfig.routes.push(route)
} else if (action === 'remove') {
var idx = ngCliConfig.routes.findIndex(el => el.routePath === route.routePath);
if (idx > -1) {
ngCliConfig.routes.splice(idx, 1);
}
}
fs.writeFileSync(ngCliConfigPath, JSON.stringify(ngCliConfig, null, 2));
}
124 changes: 124 additions & 0 deletions tests/acceptance/generate-route.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use strict';

var fs = require('fs-extra');
var ng = require('../helpers/ng');
var existsSync = require('exists-sync');
var expect = require('chai').expect;
var forEach = require('lodash/forEach');
var path = require('path');
var tmp = require('../helpers/tmp');
var root = process.cwd();
var util = require('util');
var conf = require('ember-cli/tests/helpers/conf');
var testPath = path.join(root, 'tmp', 'foo', 'src', 'app', 'my-route');
var appPath = path.join(root, 'tmp', 'foo', 'src', 'app');
var routeRegex = /name: 'MyRouteRoot'/g;
var route = {
routePath: `/my-route/...`,
component: `MyRouteRoot`,
componentPath: `./my-route/my-route-root.component`
};

var fileExpectations = function (expectation) {
expect(existsSync(path.join(testPath, 'my-route.service.spec.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route.service.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-list.component.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-list.component.spec.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-list.component.html'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-list.component.css'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-detail.component.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-detail.component.spec.ts'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-detail.component.html'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-detail.component.css'))).to.equal(expectation);
expect(existsSync(path.join(testPath, 'my-route-root.component.ts'))).to.equal(expectation);
};

describe('Acceptance: ng generate route', function () {
before(conf.setup);

after(conf.restore);

beforeEach(function () {
return tmp.setup('./tmp')
.then(function () {
process.chdir('./tmp');
})
.then(function () {
return ng([
'new',
'foo',
'--skip-npm',
'--skip-bower'
]);
});
});

afterEach(function () {
this.timeout(10000);

return tmp.teardown('./tmp');
});

it('ng generate route my-route', function () {
return ng([
'generate',
'route',
'my-route'
])
.then(_ => {
var ngCliConfig = JSON.parse(fs.readFileSync(path.join(root, 'tmp', 'foo', 'angular-cli.json'), 'utf-8'));
var routeConfigString = fs.readFileSync(path.join(appPath, 'route-config.ts'), 'utf-8').toString();
expect(routeConfigString.match(routeRegex).length).to.equal(1);

fileExpectations(true);

expect(ngCliConfig.routes[0]).to.deep.equal(route);
});
});
it('ng generate route my-route with skip-router-generation flag does not generate router config', function () {
return ng([
'generate',
'route',
'my-route',
'--skip-router-generation'
])
.then(_ => {
var ngCliConfig = JSON.parse(fs.readFileSync(path.join(root, 'tmp', 'foo', 'angular-cli.json'), 'utf-8'));

fileExpectations(true);

expect(ngCliConfig.routes.length).to.equal(0);
});
});
it('ng generate route my-route then destroy', function () {
return ng([
'generate',
'route',
'my-route'
])
.then(() => {
var ngCliConfig = JSON.parse(fs.readFileSync(path.join(root, 'tmp', 'foo', 'angular-cli.json'), 'utf-8'));
var routeConfigString = fs.readFileSync(path.join(appPath, 'route-config.ts'), 'utf-8').toString();
expect(routeConfigString.match(routeRegex).length).to.equal(1);

fileExpectations(true);

expect(ngCliConfig.routes.length).to.equal(1);
expect(ngCliConfig.routes[0]).to.deep.equal(route);
}).then(() => {
return ng([
'destroy',
'route',
'my-route'
]).then(() => {
var ngCliConfig = JSON.parse(fs.readFileSync(path.join(root, 'tmp', 'foo', 'angular-cli.json'), 'utf-8'));
var routeConfigString = fs.readFileSync(path.join(appPath, 'route-config.ts'), 'utf-8').toString();
expect(routeConfigString.match(routeRegex)).to.equal(null);

fileExpectations(false);

expect(ngCliConfig.routes.length).to.equal(0);
})
});
});
});