Develop SharePoint Hosted Add-in with Angular 2 and WebPack

In this post, I will provide step by step instructions on how to create a SharePoint hosted Add-in with latest Angular 2 (i.e @angular/.. ~4.2.0).  I did came across many articles showing how to create a SPA in SharePoint using Angular JS and deprecated version of Angular 2 (i.e. angular 2) and @angular/.. ~2.0.0 but those are now old approaches and doesn’t work anymore with the latest Angular 2. So, fasten your seat belt, grab a cup of coffee and just follow along – end result would be a much happier version of you.

NoteIn  future if Angular team comes up with the newer version of Angular 2 and you want to add those latest Angular 2 dependencies to this project then look at the “Special Note” at the end of this blog.

1) Create a SharePoint Hosted Add-in Project

Open Visual Studio 2015 and Create a SharePoint Hosted Add-in project.

2) Add ‘app’ module

Right click on SharePoint project and add a new item. Choose ‘module’ from the item type and name it ‘app’.
image1

3) Remove Unnecessary files

a) Move ‘Default.aspx’ from ‘Pages’ to ‘app’ and delete the ‘Pages’ module. This would automatically change the ‘Start Page’ url to the current location of ‘Default.aspx’ in ‘AppManifest.xml’. Default.aspx should look like this –
image10
b) Delete ‘Content’ module.
c) Move ‘AppIcon.png’ from ‘Images’ to ‘app\Images’ and delete the ‘Images’ module.
d) Remove ‘Scripts’ module too.
It should now look like this –
image2

4) Add ‘config’ and ‘src’ folders

Add ‘config’ and ‘src’ folders at the root level. ‘config’ folder will have all the webpack related config files and ‘src’ folder will have the typescript files with the app source code.
image3

5) Add tsconfig.json and typings.json TypeScript related files

At the top level, add tsconfig.json and typings.json files with the code shown below –

tsconfig.json

{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules",
"typings/main",
"typings/main.d.ts"
]
}

typings.json

{
"globalDependencies": {
"core-js": "registry:dt/core-js#0.0.0+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160505161446",
"node": "registry:dt/node#4.0.0+20160509154515"
}
}

image4

6) Add ‘package.json’

At the project root, add package.json with this code –

{
"name": "angular2-webpack",
"version": "1.0.0",
"description": "A webpack starter for Angular",
"scripts": {
"start": "webpack-dev-server --inline --progress --port 8080",
"test": "karma start",
"build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"
},
"license": "MIT",
"dependencies": {
"@angular/common": "~4.2.0",
"@angular/compiler": "~4.2.0",
"@angular/core": "~4.2.0",
"@angular/forms": "~4.2.0",
"@angular/http": "~4.2.0",
"@angular/platform-browser": "~4.2.0",
"@angular/platform-browser-dynamic": "~4.2.0",
"@angular/router": "~4.2.0",
"core-js": "^2.4.1",
"rxjs": "5.0.1",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@types/node": "^6.0.45",
"@types/jasmine": "2.5.36",
"angular2-template-loader": "^0.6.0",
"awesome-typescript-loader": "^3.0.4",
"css-loader": "^0.26.1",
"extract-text-webpack-plugin": "2.0.0-beta.5",
"file-loader": "^0.9.0",
"html-loader": "^0.4.3",
"html-webpack-plugin": "^2.16.1",
"jasmine-core": "^2.4.1",
"karma": "^1.2.0",
"karma-chrome-launcher": "^2.0.0",
"karma-jasmine": "^1.0.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.1",
"null-loader": "^0.1.1",
"raw-loader": "^0.5.1",
"rimraf": "^2.5.2",
"style-loader": "^0.13.1",
"typescript": "~2.3.1",
"webpack": "2.2.1",
"webpack-dev-server": "2.4.1",
"webpack-merge": "^3.0.0"
}
}

7) Install Packages

Assuming that you already have Node package manager installed, run the command below from NodeJS command prompt as an administrator to install all the packages that are listed in ‘package.json’.
npm install

8) Add ‘styles.css’

Add ‘styles.css’ under ‘app/css’. Create a foldr ‘css’ under ‘app’ module.
body {
background: #0147A7;
color: #fff;
}

image5

9) Add App module and Component and its related files under ‘src/app’

app.component.ts
import { Component, ViewEncapsulation } from '@angular/core';
import '../../app/css/styles.css';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})

export class AppComponent {

ngOnInit() {
console.log(“App Initialized”);
}
}

app.component.html
< !DOCTYPE html>
<body><main>
<h1>Hello from Angular 2 App with Webpack</h1>
<img src="../../app/images/AppIcon.png" />
</main></body>

app.component.css
main {
padding: 1em;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
margin-top: 50px;
display: block;
}

app.module.ts
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component';

@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }

image6

10) Add main.ts under ‘src’

main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { enableProdMode } from '@angular/core';
import { AppModule } from './app/app.module';
if (process.env.ENV === 'production') {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

11) Add polyfill and vendor files under ‘src’

polyfills.ts
import 'core-js/es6';
import 'core-js/es7/reflect';
require('zone.js/dist/zone');

if (process.env.ENV === ‘production’) {
// Production
} else {
// Development
Error[‘stackTraceLimit’] = Infinity;
require(‘zone.js/dist/long-stack-trace-zone’);
}
vendor.ts
// Angular 2
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';

// RxJS
import ‘rxjs’;

image7

12) Add WebPack related config files

All the webpack related config files are grabbed from here Webpack for Angular 2 and then modified a little.
config/helpers.js
var path = require('path');
var _root = path.resolve(__dirname, '..');
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [_root].concat(args));
}
exports.root = root;

config/webpack.common.js
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');

module.exports = {
entry: {
‘polyfills’: ‘./src/polyfills.ts’,
‘vendor’: ‘./src/vendor.ts’,
‘app’: ‘./src/main.ts’
},

resolve: {
extensions: [‘.ts’, ‘.js’]
},

module: {
rules: [
{
test: /\.ts$/,
loaders: [
{
loader: ‘awesome-typescript-loader’,
options: { configFileName: helpers.root(‘src’, ‘tsconfig.json’) }
}, ‘angular2-template-loader’
]
},
{
test: /\.html$/,
loader: ‘html-loader’
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: ‘file-loader?name=/assets/[name].[hash].[ext]’
},
{
test: /\.css$/,
exclude: helpers.root(‘src’, ‘app’),
loader: ExtractTextPlugin.extract({ fallbackLoader: ‘style-loader’, loader: ‘css-loader?sourceMap’ })
},
{
test: /\.css$/,
include: helpers.root(‘src’, ‘app’),
loader: ‘raw-loader’
}
]
},

plugins: [

// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root(‘./src’), // location of your src
{} // a map of your routes
),

new webpack.optimize.CommonsChunkPlugin({
name: [‘app’, ‘vendor’, ‘polyfills’]
})

]

};
config/webpack.dev.js
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

module.exports = webpackMerge(commonConfig, {
devtool: ‘cheap-module-eval-source-map’,

output: {
path: helpers.root(‘app’),
publicPath: ‘/sites/demo/Angular2SPAddin/app’,
filename: ‘[name].js’,
chunkFilename: ‘[id].chunk.js’
},

plugins: [
new ExtractTextPlugin(‘[name].css’)
],
});
webpack.config.js
module.exports = require('./config/webpack.dev.js');
image8

13) Install WebPack

Install WebPack globally using the command below. We will use WebPack to generate the bundles.
npm install -g webpack

14) Generate bundles using WebPack

Run the command below to generate the bundles which will generate the bundles in ‘app’ module.
webpack –-config .\webpack.config.js –-progress –-colors
This will generate the following files in the ‘app’ modules –
/assets/AppIcon.xxxx.png
vendor.js
app.js
polyfills.js
app.css
Ignore the warnings in the nodejs command prompt.

15) Include bundles in the ‘app’ module

Click on “show all files” icon in the project ribbon which will also show the files that are in the project directory but not included in the project. Right click the files as shown in the image below and include in project.
image9

16) Run the Addin

Press F5 in the Visual Studio to run the add-in.
image11

Special note – In the future if Angular team comes up with the newer version of Angular 2 and you want to add those latest Angular 2 dependencies to this project then you may need to change the following files in this project –
package.json
webpack.common.js
webpack.dev.js
helpers.js
To change the above files, if needed, follow the link below to the guide that explains how you can bundle your Angular 2 applications using webpack. Angular team update this guide whenever they release new versions of Angular 2 and need some changes in the webpack config files to work with the latest version of Angular 2.
Webpack guide

Leave a Comment

Your email address will not be published. Required fields are marked *