Hi,
Angular 2 is a very promising framework and one of its biggest advantages is that it takes the power of Typescript and hence, increases the front end productivity and quality of code.
The Angular 2 team introduced in the recent releases, the modularity with the “NgModule” decorators which adds another very exciting feature that allows to structure complex applications very efficiently.
One of the trickiest things for Angular 2 newcomers it to make the first things done ! The first “Hello World” application is in itself challenging as it requires knowledge about many other tools and libraries.
The Project Features
In this post, I share with you a starter project using Visual Studio 2015. The project is characterized by:
- The application is an ASP.NET CORE web application.
- Using the latest release of Angular 2
- Using SASS as a CSS pre-processor
- Using two A2 modules (module 1) and (module 2)
- The booting applications is composed of two components: component 1 from module 1 and component 2 from module 2
- Component 2 uses service 2 from module 2
- Component 1 uses service 1 from module 1 and service 2 from module 2
- The component templates are in separate HTML files
- The component css files are in separate css files.
- The project includes one MVC controller that uses a razor layout page.
- The project supports multi-environment configurations. For production, the application uses minified js and css files.
When running, the applications looks like the following snapshot:
The resulting page will aggregate two components coming from two separate modules “Module 1” and “Module 2”.
Naming Conventions
For the purpose of an efficient code understandability and communication, we will adopt the following naming convention:
- Typescript classes are named using the UpperCamelCase convention.
- The typescript component files are suffixed with “.component.ts”. For example, the component “MyButtonComponent” will be in the file “my-button.component.ts”.
- Component classes will be suffixed with “Component”. For example, “MyButtonComponent”.
- The typescript services files are suffixed with “.service.ts”. For example the service “DataService” will be in the file “data.service.ts”.
- Angular 2 services classes are suffixed with “Service”. For example, “DataService”.
- The typescript module files are suffixed with “.module.ts”. For example the service “AdminModule” will be in the file “admin.module.ts”.
- Angular 2 module classes are suffixed with “Module”. For example, “AdminModule”.
The Project Structure
To achieve the features described in the previous section, we need a structured project composed of many files and directories.
The structure is composed of the following elements:
- wwwroot: the root hosting folder. All the static files (css, js) will be copied to this folder.
- css: the folder that will contain css files.
- lib: css lib files (for example bootstrap files)
- app: contains application css (normal and minified) files.
- components: contains the components CSS files. The css files are organized by A2 modules. For example, the subfolder XXX contains the CSS of the module XXX components.
- scripts: the folder that will contain JavaScript files
- lib: contains the lib files (for example angular 2, zonejs or systemjs files).
- app: the application js source code. Most of the files are created by compiling the typescript files located in the “ClientDev” directory.
- dev: user js files.
- dist: user minified files.
- templates: contains the components HTML templates files. The files are organized by A2 modules. For example, the subfolder XXX contains the HTML template files of the module XXX components.
- ClientDev: contains the client development files such as Typescript and Sass files.
- HTML: this subfolder contains the component HTML templates files organized by modules. Keeping the HTML templates initially here permits a safe cleaning of the wwwroot templates subfolder.
- Scripts: contains the typescript files.
- main.ts: the Angular2 bootstrap file.
- XXX: each module has its own subfolder under the “Scripts” folder.
- xxx.module.ts: the typescript module file.
- Components: this subfolder contains the components of the module “XXX”;
- Services: this subfolder contains the services of the module “XXX”;
- Style: contains the application sass files that will be compiled and injected into the wwwroot/css/app folder.
- components: this subfolder contains the individual sass files of the A2 components.
- Controllers: the ASP.NET core controllers
- Views: the ASP.NET razor files.
The Gulp Tasks
To achieve the A2 integration, it is necessary to configure the adequate gulp tasks (in gulpfile.js) that allow mostly to move and compile source files from the ClientDev folder (ts, html and sass) to the wwwroot folder (js, html and css).
Gulp Declarations
The first thing to configure are the module and the global variables that are used by gulp. This is illustrated by the snippet below:
1: var gulp = require('gulp');
2: var merge = require('merge-stream');
3: var clean = require('gulp-clean');
4: var sass = require('gulp-sass');
5: var cssmin = require('gulp-cssmin');
6: var rename = require('gulp-rename');
7: var jsmin = require('gulp-jsmin');
8:
9: var webroot = "./wwwroot/";
10: var clientDevRoot = "./ClientDev/";
11:
12:
13:
14: var paths = {
15: css: {
16: sassSource: clientDevRoot + "Style/**/*.scss",
17: appDestination: webroot + "css/app",
18: libDestination: webroot + "css/lib",
19: fontsDestination: webroot + "css/fonts"
20: },
21: js:
22: {
23: tsSource: clientDevRoot + "Scripts/**/*.ts",
24: jsSource: clientDevRoot + "Scripts/**/*.js",
25: mapSource: clientDevRoot + "Scripts/**/*.map",
26: appDestination: webroot + "scripts/app",
27: libDestination: webroot + "scripts/lib"
28: },
29: html: {
30: source: clientDevRoot + "HTML/**/*.html",
31: destination: webroot
32: }
33: };
Lines 1 to 7 declare the gulp modules to be used. The merge module is used to merge multiple tasks into one. The clean module is used to cleaning. The sass module is used to compile sass files. The cssmin and jsmin are used to css and js minification. Finally, the rename module is used to rename files.
The paths global variable indicates the source files included in the ClientDev directory and the result files that will be copied to the wwwroot directory.
The “angular_lib” task
This job copies the angular distribution files from the “node_modules” folder to “wwwroot/scripts/lib”.
1: gulp.task('angular_lib', function (done) {
2: var taskRx = gulp.src([
3: 'node_modules/@angular/**/bundles/*',
4: ]).pipe(gulp.dest(paths.js.libDestination + "/@angular"));
5: })
The “other_lib” task.
This task is about copying the other JS library files such as RxJs.
1: /* other js libraries */
2: gulp.task('other_lib', function (done) {
3: var task1 = gulp.src([
4: 'node_modules/core-js/client/shim.min.js',
5: 'node_modules/core-js/client/shim.js',
6: 'node_modules/zone.js/dist/zone.js',
7: 'node_modules/zone.js/dist/zone.min.js',
8: 'node_modules/reflect-metadata/Reflect.js',
9: 'node_modules/reflect-metadata/Reflect.js.map',
10: 'node_modules/systemjs/dist/system.src.js',
11: 'node_modules/systemjs/dist/system.js',
12: 'node_modules/systemjs/dist/system.js.map'
13: ]).pipe(gulp.dest(paths.js.libDestination));
14: var task2 = gulp.src([
15: 'node_modules/rxjs/**/*.js',
16: ]).pipe(gulp.dest(paths.js.libDestination + '/rxjs'));
17: var task4 = gulp.src([
18: 'node_modules/rxjs/**/*.map',
19: ]).pipe(gulp.dest(paths.js.libDestination + '/rxjs'));
20: var task3 = gulp.src([
21: 'node_modules/foundation/js/vendor/*.js',
22: ]).pipe(gulp.dest(paths.js.libDestination));
23: c
24: return merge(task1, task2, task3, task4);
25: });
The “lib_js” task
This task is a combination of the two previous tasks consisting in copying all the JavaScript library files.
gulp.task('lib_js', ['other_lib', 'angular_lib']);
The “app_html” task
This task consists in copying the HTML template files (and eventually some static html files) from ClientDev to wwwroot.
1: gulp.task('app_html', function () {
2: return gulp.src(paths.html.source)
3: .pipe(gulp.dest(paths.html.destination));
4:
5: });
The “app_css” task
This tasks is related to the compilation of sass files and the generation of css and minified css files that are copied to wwwroot/css/app.
1: gulp.task('app_css', function () {
2: return gulp.src(paths.css.sassSource)
3: .pipe(sass().on('error', sass.logError))
4: .pipe(gulp.dest(paths.css.appDestination))
5: .pipe(rename({ suffix: '.min' }))
6: .pipe(cssmin())
7: .pipe(gulp.dest(paths.css.appDestination));
8:
9: });
The “app_js” task
This tasks copies the js files generated from the typescript files located in ClientDev/Scripts folder to the wwwroot/scripts/app/dev. The minified version is copied to wwwroot/scripts/app/dist.
1: gulp.task('app_js', function (done) {
2: var task1 = gulp.src([
3: paths.js.jsSource
4: ]).pipe(gulp.dest(paths.js.appDestination + '/dev'))
5: .pipe(jsmin())
6: .pipe(gulp.dest(paths.js.appDestination + '/dist'));
7: var task2 = gulp.src([
8: paths.js.mapSource
9: ]).pipe(gulp.dest(paths.js.appDestination + '/dev'))
10: .pipe(gulp.dest(paths.js.appDestination + '/dist'));
11:
12: return merge(task1, task2);
13: });
The “app_angular” task.
Finally, this tasks performs all the angular staff: moving HTML templates, css files and JavaScript files. It is a combination of all the app_xxx tasks.
gulp.task('app_angular', ['app_js', 'app_html', 'app_css']);
Key Source Files
Startup.cs
The main tasks is to configure the web application. Line 24 specifies that we are using MVC and configure the default routing for our application.
1: public class Startup
2: {
3: // This method gets called by the runtime. Use this method to add services to the container.
4: // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
5: public void ConfigureServices(IServiceCollection services)
6: {
7: services.AddMvc();
8: }
9:
10: // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
11: public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
12: {
13: if (env.IsDevelopment())
14: {
15: app.UseDeveloperExceptionPage();
16: }
17: else
18: {
19: app.UseExceptionHandler("/Home/Error");
20: }
21:
22: app.UseStaticFiles();
23:
24: app.UseMvc(routes =>
25: {
26: routes.MapRoute(
27: name: "default",
28: template: "{controller=Home}/{action=Index}/{id?}");
29:
30: routes.MapRoute(
31: name: "spa-fallback",
32: template: "{*url}",
33: defaults: new { controller = "Admin", action = "Index" });
34:
35: });
36:
37:
38: }
_Layout.cshtml
This file is located in the “Views/Shared” folder and is the default layout for our views. As we target an SPA application, we won’t have a huge number of views as they will be represented by A2 HTML templates. Please notice (line 9 to 34) that we are using the multi-environment capabilities of the new ASP.NET core. For example, in a production environment, we will use the scripts located in the scripts/app/dist instead of those located in scripts/app/dev.
1: <!DOCTYPE html>
2: <html>
3: <head>
4: <base href="/Admin">
5: <meta charset="utf-8" />
6: <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7: <title>A2 Starter</title>
8:
9: <environment names="Development">
10:
11: <link href="~/css/app/site.css" rel="stylesheet" />
12: <script src="/scripts/lib/shim.js"></script>
13: <script src="~/scripts/lib/zone.js"></script>
14: <script src="/scripts/lib/Reflect.js"></script>
15: <script src="/scripts/lib/system.src.js"></script>
16: <script src="~/scripts/app/dev/Config/systemjs.config.js"></script>
17: <script>
18: System.import('app/dev/main')
19: .then(null, console.error.bind(console));
20:
21: </script>
22: </environment>
23: <environment names="Staging,Production">
24: <link href="~/css/app/site.min.css" rel="stylesheet" />
25: <script src="/scripts/lib/shim.min.js"></script>
26: <script src="~/scripts/lib/zone.min.js"></script>
27: <script src="/scripts/lib/Reflect.js"></script>
28: <script src="/scripts/lib/system.js"></script>
29: <script src="~/scripts/app/dist/Config/systemjs.config.js"></script>
30: <script>
31: System.import('app/dist/main')
32: .then(null, console.error.bind(console));
33: </script>
34: </environment>
35: </head>
36: <body>
37:
38:
39:
40: <div>
41: @RenderBody()
42: </div>
43:
44:
45: @RenderSection("scripts", required: false)
46: </body>
47: </html>
main.ts
This file is located in the ClientDev folder root. It consists in bootstrapping our A2 application by using the “AppComponent” that belongs to “Module1”.
1: /// <reference path="../../typings/index.d.ts"/>
2:
3: import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4: import { Module1Module } from './Module1/module1.module';
5: platformBrowserDynamic().bootstrapModule(Module1Module);
module1.module.ts
This file is located in ClientDev/Module1. Its is the module declaration file for the module “Module 1”. Notice that our starting point is the “AppComponent” (line 11). Notice also that we are using components and services from “Module 2” by using the “imports” directive (line 9).
1: import { NgModule } from "@angular/core";
2: import { BrowserModule } from "@angular/platform-browser";
3: import { AppComponent } from "./Components/app.component";
4: import { Component1Component } from "./Components/component1.component";
5: import { Service1 } from "./Services/service1.service";
6: import { Module2Module } from "./../Module2/module2.module";
7:
8: @NgModule({
9: imports: [BrowserModule, Module2Module],
10: declarations: [AppComponent, Component1Component],
11: bootstrap: [AppComponent],
12: providers: [Service1Service]
13: })
14: export class Module1Module { }
The App Component “AppComponent”
This is the bootstrapping component. Its only role is to aggregate the two other components “Component 1” and “Component 2”.
The typescript code of “AppComponent” is in ClientDev/Module1/Components/app.component.ts:
1: import { Component } from "@angular/core";
2: @Component({
3: selector: "starter-app",
4: templateUrl: "/templates/module1/app.html",
5: styleUrls: ["css/app/components/module1/app.css"]
6: })
7:
8: export class AppComponent {
9: constructor() {
10:
11: }
12: }
The “AppComponent” HTML template is located in ClientDev/HTML/templates/app.html:
1: <starter-comp1>
2: </starter-comp1>
3: <starter-comp2>
4: </starter-comp2>
The Component “Component 1”.
This component belongs to “Module 1”. It is worth to point out that this component uses two services (Service1 and Service2) that come from t The typescript file is located in ClientDev/Module1/Components/component1.component.ts. Notice that the component uses its own css file (line 8) that will be generated by compiling sass files contained in ClientDev/Style/components/module1/component1.sass.
1: import { Component, OnInit } from "@angular/core";
2: import { Service1Service } from "./../Services/service1.service";
3: import { Service2Service } from "./../../Module2/Services/service2.service";
4:
5: @Component({
6: selector: "starter-comp1",
7: templateUrl: "templates/module1/component1.html",
8: styleUrls: ["css/app/components/module1/component1.css"]
9:
10:
11: })
12: export class Component1Component implements OnInit {
13: text: string;
14: constructor(private _s1: Service1Service, private _s2: Service2Service) { }
15:
16: ngOnInit() {
17: this.text = `${this._s1.getText()} ${this._s2.getText()}`;
18: }
19: }
The HTML template file of this component is pretty simple :
<h2>Component From Module 1</h2>
<h2>{{text}}</h2>
The Service “Service1Service”
This is a very simple service that is injectable to components by using the “Injectable” decorator. This service is located in ClientDev/Module1/Services/service1.service.ts.
1: import { Injectable } from "@angular/core";
2:
3: @Injectable()
4: export class Service1Service {
5: getText(): string {
6: return "text from service 1";
7: }
8: }
The Module “Module 2”
This is a separate module that contains its own components and modules. The module declaration file is located in ClientDev/Scripts/Module2/module2.module.ts. By using the “exports” directive (line 11), we declare that the component “Component2Components” can be used by other modules.
1: import { NgModule } from "@angular/core";
2: import { BrowserModule } from "@angular/platform-browser";
3:
4: import { Component2Component } from "./Components/component2.component";
5: import { Service2Service } from "./Services/service2.service";
6:
7:
8: @NgModule({
9: imports: [BrowserModule],
10: declarations: [Component2Component],
11: exports: [Component2Component],
12: providers: [Service2Service]
13: })
14: export class Module2Module { }
The “Component2” Component and “Service2” Service
They are similar to “Component 1” and “Service 2” except they are located in another “Module”.
The Source Code
The source code is available here. Enjoy !
The Bottom Line
The purpose of this article is to propose an ideal structure that handles complex angular projects and takes into account development and production environments. Our structure is based on the modularity principle of A2 and is supported by gulp tasks that handles the most important every-day tasks of a front-end developer.