Skip to content

Commit

Permalink
Fixes and improvements
Browse files Browse the repository at this point in the history
- Upgrade to Angular 2.1.2, also update some deps
- Change the way how templates and styles are imported
- Freeze deps versions
- Ensure code coverage works properly
- Example component with HTTP interaction & related tests for it
  • Loading branch information
kozlice committed Nov 11, 2016
1 parent 8817a0e commit 2378d41
Show file tree
Hide file tree
Showing 30 changed files with 543 additions and 155 deletions.
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
# An opinionated Angular 2 + Webpack boilerplate
# An Angular 2 + Webpack boilerplate with examples

**Work in progress.** This kit is not created for learning purposes, but rather as a base for my own upcoming projects. I'm not sure that world needs _yet another_ Angular 2 + webpack boilerplate, but initially it was shared as a solution for [TypeScript code coverage issue](https://github.com/AngularClass/angular2-webpack-starter/issues/178).
Angular 2 application with some examples (currently only HTTP service and component). Please, feel free to create issues and PRs.

Feel free to create issues and PRs, though, if you know how to do this thing better.
Templates and stylesheets are embedded into JS bundle with help of [angular2-template-loader](https://github.com/TheLarkInn/angular2-template-loader). SASS/SCSS is used for styling.

### What's inside

Angular 2 example application (examples for components, HTTP, forms, routing are yet to come - most likely when Angular 2 is out of RC state).

Templates and stylesheets are embedded into JS bundle. I chose SASS for styling.

`index.html` is generated using [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin). Some popular analytics/metrics are yet to be added there (e.g. Google Analytics or New Relic).
`index.html` is generated using [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin).

Hot module replacement is not provided here. Possibly I'll add it later. Same goes for service workers.

Expand All @@ -30,14 +24,30 @@ You won't find end-to-end tests in this project (usually people use Protractor f

### Building bundle(s)

Use `npm run build` and you will get JS bundles, their maps and `index.html` in `dist` directory. To build for production, use `NODE_ENV=production npm run build` (webpack configuration is chosen according to this environment variable).
Use `npm run build` and you will get JS bundles, their maps and `index.html` in `dist` directory.

To build for production, use `NODE_ENV=production npm run build` (webpack configuration is chosen according to this environment variable).

### Documentation

Run `npm run docs` to generate documentation for TypeScript ([typedoc](https://github.com/TypeStrong/typedoc) is used for that) and SASS stylesheets (done with [kss-node](https://github.com/kss-node/kss-node)). This might seem a little bit unusual to have docs for styles, but I find KSS very nice tool to keep them understandable.
Run `npm run docs` to generate documentation for TypeScript ([typedoc](https://github.com/TypeStrong/typedoc) is used for that) and SASS stylesheets (done with [kss-node](https://github.com/kss-node/kss-node)).

### Dev notes

- Webpack is configured to resolve modules not only from `node_modules` directory, but also from `src`. This is done to keep imports simple and avoid situations like `import { MyService } from '../../../app.service'`. Especially important for tests, since they are in a separate directory. If you are using JetBrains IDE, mark `src` directory as resource root

- Upgrade to newer version of `istanbul-instrumenter-loader` (`0.2.0` to `1.0.0`) breaks code coverage

- Upgrade to `awesome-typescript-loader@2.*.*` is not possible, as it is targeted to work with webpack 2, which is yet in beta and I don't want to use it until it's released

### Notes for development
## TODOs

Modules are resolved not only from `node_modules` directory, but from `src` as well. This is done to keep imports simple and avoid situations like `import { MyService } from '../../../app.service'`.
- Examples
- Typeahead with throttle/distinct
- Pipe
- AuthGuard in router
- Forms
- Input and Output
- rxjs-based WebSocket

If you are using JetBrains IDE, mark `src` directory as resource root.
- Comments on tests
98 changes: 50 additions & 48 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"angular2",
"webpack",
"typescript",
"rxjs",
"boilerplate",
"starter"
],
Expand All @@ -27,62 +28,63 @@
"coverage:remap": "remap-istanbul -i coverage/coverage.json -o coverage/coverage.json -t json -e node_modules,tests,karma.entry.ts",
"coverage:report": "istanbul report",
"build": "webpack",
"preinstall": "npm run clean",
"postinstall": "typings install",
"docs": "npm run docs:typedoc && npm run docs:kss",
"docs:typedoc": "typedoc --options ./typedoc.json ./src/app",
"docs:kss": "kss --source ./src --destination ./docs/styleguide",
"start": "webpack-dev-server --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base ./dist"
},
"devDependencies": {
"@angular/common": "2.0.0",
"@angular/compiler": "2.0.0",
"@angular/core": "2.0.0",
"@angular/forms": "2.0.0",
"@angular/http": "2.0.0",
"@angular/platform-browser": "2.0.0",
"@angular/platform-browser-dynamic": "2.0.0",
"@angular/router": "3.0.0",
"awesome-typescript-loader": "^1.1.1",
"codelyzer": "^0.0.26",
"core-js": "^2.4.1",
"css-loader": "^0.23.1",
"es6-shim": "^0.35.1",
"file-loader": "^0.8.5",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.22.0",
"istanbul-instrumenter-loader": "^0.2.0",
"jasmine-core": "^2.4.1",
"json-loader": "^0.5.4",
"karma": "^0.13.22",
"karma-coverage": "^1.1.1",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.0.5",
"karma-phantomjs-launcher": "^1.0.1",
"karma-source-map-support": "^1.1.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"kss": "^3.0.0-beta.14",
"node-sass": "^3.8.0",
"phantomjs-prebuilt": "^2.1.8",
"raw-loader": "^0.5.1",
"reflect-metadata": "^0.1.3",
"remap-istanbul": "^0.6.4",
"rimraf": "^2.5.2",
"@angular/common": "2.1.2",
"@angular/compiler": "2.1.2",
"@angular/core": "2.1.2",
"@angular/forms": "2.1.2",
"@angular/http": "2.1.2",
"@angular/platform-browser": "2.1.2",
"@angular/platform-browser-dynamic": "2.1.2",
"@angular/router": "3.1.2",
"angular-in-memory-web-api": "^0.1.14",
"angular2-template-loader": "0.6.0",
"awesome-typescript-loader": "1.1.1",
"codelyzer": "0.0.26",
"core-js": "2.4.1",
"es6-shim": "0.35.1",
"flexboxgrid-sass": "8.0.5",
"html-loader": "0.4.4",
"html-webpack-plugin": "2.24.1",
"istanbul-instrumenter-loader": "0.2.0",
"jasmine-core": "2.5.2",
"json-loader": "0.5.4",
"karma": "1.3.0",
"karma-coverage": "1.1.1",
"karma-jasmine": "1.0.2",
"karma-mocha-reporter": "2.2.0",
"karma-phantomjs-launcher": "1.0.2",
"karma-source-map-support": "1.2.0",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "1.8.0",
"kss": "3.0.0-beta.16",
"node-sass": "3.11.2",
"normalize-scss": "6.0.0",
"phantomjs-prebuilt": "2.1.13",
"raw-loader": "0.5.1",
"reflect-metadata": "0.1.8",
"remap-istanbul": "0.7.0",
"rimraf": "2.5.4",
"rxjs": "5.0.0-beta.12",
"sass-lint": "^1.8.2",
"sass-loader": "^3.2.3",
"source-map-loader": "^0.1.5",
"style-loader": "^0.13.1",
"to-string-loader": "^1.1.4",
"ts-helpers": "^1.1.1",
"tslint": "^3.14.0",
"tslint-loader": "^2.1.5",
"typedoc": "^0.4.4",
"typescript": "^2.0.2",
"typings": "^1.3.2",
"webpack": "^1.13.1",
"webpack-dev-server": "^1.14.1",
"sass-lint": "1.10.2",
"sass-loader": "4.0.2",
"skeleton-scss": "2.0.4",
"source-map-loader": "0.1.5",
"ts-helpers": "1.1.2",
"tslint": "3.15.1",
"typedoc": "0.5.1",
"typescript": "2.0.8",
"typings": "1.3.2",
"webpack": "1.13.3",
"webpack-dev-server": "1.16.2",
"webpack-md5-hash": "0.0.5",
"zone.js": "^0.6.23"
"zone.js": "0.6.26"
}
}
2 changes: 2 additions & 0 deletions src/app/_variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$heading-color: #333;
$heading-highlight-color: #fa2800;
23 changes: 9 additions & 14 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { AppService } from './app.service';

/**
* Styles required here are common for all components (SASS/SCSS versions of normalize.css and flexboxgrid),
* so encapsulation is not used. Other components have their styles scoped with `ViewEncapsulation.Emulated`.
*/
@Component({
selector: 'da-app',
template: require('./app.html'),
styles: [
require('./app.scss')
],
encapsulation: ViewEncapsulation.Native,
providers: [
AppService
]
templateUrl: './app.html',
styleUrls: ['./app.scss'],
encapsulation: ViewEncapsulation.None,
providers: []
})
export class AppComponent {
constructor(public appService: AppService) {

}
}
export class AppComponent {}
18 changes: 17 additions & 1 deletion src/app/app.html
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
<h1 class="app__title">{{ appService.getTitle() }}</h1>
<div class="container container-fluid">
<div class="row around-xs center-xs">
<div class="col-xs">
<h1 class="app__title">Example Angular 2 application</h1>
</div>
</div>

<nav class="row around-xs center-xs">
<div class="col-xs">
<a [routerLink]="['/']" routerLinkActive="active">Articles</a>
</div>
</nav>

<hr />

<router-outlet></router-outlet>
</div>
28 changes: 21 additions & 7 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { HttpModule, JsonpModule } from '@angular/http';
import { FormsModule } from '@angular/forms';

import { routing, appRoutingProviders } from './app.routing';
import { AppComponent } from 'app/app.component';
import { ArticleComponent } from './article/article.component';
import { ArticleListComponent } from './article/article-list.component';

/**
* TODO: Example service (HTTP interaction), routing, nested directives, pipe + related tests
*/
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
imports: [
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
routing
],
declarations: [
AppComponent,
ArticleListComponent,
ArticleComponent
],
providers: [appRoutingProviders],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {}
14 changes: 14 additions & 0 deletions src/app/app.routing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ArticleComponent } from './article/article.component';
import { ArticleListComponent } from './article/article-list.component';

const appRoutes: Routes = [
{ path: 'articles/:id', component: ArticleComponent },
{ path: '', component: ArticleListComponent },
];

export const appRoutingProviders: any[] = [];

export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
18 changes: 2 additions & 16 deletions src/app/app.scss
Original file line number Diff line number Diff line change
@@ -1,16 +1,2 @@
$heading-color: #333;
$heading-highlight-color: #fa2800;

// App title
//
// :hover - Highlights when hovering
//
// Styleguide base
.app__title {
font-size: 4rem;
color: $heading-color;

&:hover {
color: $heading-highlight-color;
}
}
@import "~normalize-scss/sass/normalize";
@import "~flexboxgrid-sass";
7 changes: 0 additions & 7 deletions src/app/app.service.ts

This file was deleted.

33 changes: 33 additions & 0 deletions src/app/article/article-list.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core';

import { ArticleService } from './article.service';
import { Article } from './article.model';
import { Response } from '@angular/http';

/**
* A simple component, which fetches articles list from HTTP API and displays them.
*/
@Component({
selector: 'da-article-list',
templateUrl: './article-list.html',
styleUrls: ['./article-list.scss'],
encapsulation: ViewEncapsulation.Emulated,
providers: [ArticleService]
})
export class ArticleListComponent implements OnInit, OnDestroy {
private articles: Article[];
private error: Response;
private isLoading: boolean = true;

constructor(private articleService: ArticleService) {}

ngOnInit(): void {
this.articleService.getAll().subscribe(
(data) => this.articles = data,
(error) => this.error = error,
() => this.isLoading = false
);
}

ngOnDestroy(): void {}
}
7 changes: 7 additions & 0 deletions src/app/article/article-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div *ngIf="isLoading">
Loading&hellip;
</div>

<article *ngFor="let article of articles" class="article-list-item">
<h4><a [routerLink]="['/articles', article.id]">{{ article.title }}</a></h4>
</article>
Empty file.
43 changes: 43 additions & 0 deletions src/app/article/article.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { ArticleService } from './article.service';
import { Article } from './article.model';
import { Response } from '@angular/http';

/**
* A simple component, which fetches article from HTTP API and displays it.
*/
@Component({
selector: 'da-article',
templateUrl: './article.html',
styleUrls: ['./article.scss'],
encapsulation: ViewEncapsulation.Emulated,
providers: [ArticleService]
})
export class ArticleComponent implements OnInit, OnDestroy {
private article: Article;
private error: Response;
private isLoading: boolean = true;

constructor(
private route: ActivatedRoute,
private articleService: ArticleService
) {

}

/**
* TODO: Note about non-observable param
*/
ngOnInit(): void {
let id = +this.route.snapshot.params['id'];
this.articleService.get(id).subscribe(
(data) => this.article = data,
(error) => this.error = error,
() => this.isLoading = false
);
}

ngOnDestroy(): void {}
}
Loading

0 comments on commit 2378d41

Please sign in to comment.