Angular2

TypeScript & RxJS & VS Code

by @sergey-tihon

Prerequisites

node

Event-driven I/O server-side JavaScript environment https://nodejs.org/en/download/

npm

Package manager

npm install npm -g

TypeScript

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open Source.

www.typescriptlang.org

npm install -g typescript

Conclusion = Adopted

Use it in all new projects instead of JavaScript.

HappySherlock

TypeScript comes to you with modularity, correctness, refactoring, intellisense, support of future EcmaScripts

Types, Lambdas and Type Inference

JavaScript

1: 
2: 
3: 
let hello2 = function(name) {
    return "Hello " + name;
}

TypeScript

1: 
2: 
let hello = (name:string) => "Hello, " + name;
// string => string

Interfaces

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
interface SPSearchRequest {
    SourceId?: string;
    EnableSorting : boolean;
    Querytext: string;
    RefinementFilters: SPResultsWrapperObject;
    HiddenConstraints: string;
    RowLimit: number;
    SelectProperties: SPResultsWrapperObject;
    HitHighlightedProperties: SPResultsWrapperObject;
    SummaryLength: number;
    EnableQueryRules: boolean;
    StartRow?: number;
    Refiners?: string;
    SortList?: {results:SortByDirection[]} // <--
    TrimDuplicatesIncludeId?: string;
}

Generic Types

1: 
2: 
3: 
4: 
5: 
6: 
class Parent<T> {
    children: T[];
    constructor() {
        this.children = [];
    }
}

Structural Typing (Duck typing)

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
interface Named {
    name: string;
}

class Person {
    name: string;
}

var p: Named;
// OK, because of structural typing
p = new Person();

Visual Studio Code

Code Editing. Redefined.

Happy

VSCode

VS Code includes support for debugging, embedded Git control, syntax highlighting, intelligent code completion, snippets, and code refactoring. It is also customizable, so users can change the editor's theme, keyboard shortcuts, and preferences.

VSCode

IntelliSense

intellisense

Go to Definition

ctrlhover

Peek Definition

references

Reference information

referenceinfo

Rename refactoring

rename

Debugging

vscode_debugging

Git version control

vscode_git

Configurable

The .vscode/settings.json file allows to configure:

  • Editor Configuration - font, wrapping, tab size, line numbers, ...
  • Files Configuration - exclude filters, encoding, trailing whitespace
  • HTTP Configuration - Proxy settings
  • File Explorer Configuration - Working Files behavior
  • Search Configuration - file exclude filters
  • CSS Configuration - CSS linting configuration
  • JavaScript Configuration - Language specific settings
  • JSON Configuration - Schemas associated with certain JSON files
  • Less/Sass Configuration - Control linting for Less/Sass
  • TypeScript Configuration - Language specific settings

Angular 2

5min QuickStart

angular.io

Configure TypeScript

tsconfig.json

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
{
"compilerOptions": {
    "target": "es5",
    "module": "system",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
},
"exclude": [
    "node_modules",
    "typings/main",
    "typings/main.d.ts"
]
}

TypeScript Typings

typings.json

1: 
2: 
3: 
4: 
5: 
6: 
{
  "ambientDependencies": {
    "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/
es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2"
  }
}

tsconfig.json

1: 
2: 
3: 
4: 
5: 
{
  "scripts": {
    "postinstall": "npm run typings install"
  }
}

Dependencies package.json

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
{
...
"dependencies": {
    "angular2": "2.0.0-beta.7", // Released 23.02.2016
    "systemjs": "0.19.22",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "zone.js": "0.5.15"
},
"devDependencies": {
    "concurrently": "^2.0.0",
    "lite-server": "^2.1.0",
    "typescript": "^1.7.5",
    "typings":"^0.6.8"
}
}

npm install

Scripts package.json

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
{
"name": "angular2-quickstart",
"version": "1.0.0",
"scripts": {
    "postinstall": "npm run typings install",
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "lite": "lite-server",
    "start": "concurrent \"npm run tsc:w\" \"npm run lite\" ",
    "typings" : "typings"
},
"license": "ISC",
...
}

npm start

First component

app/app.component.ts

1: 
2: 
3: 
4: 
5: 
6: 
7: 
import {Component} from 'angular2/core';

@Component({
    selector: 'my-app',
    template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

Bootstrap it

app/main.ts

1: 
2: 
3: 
4: 
import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'

bootstrap(AppComponent);

Platform specific bootstrap Browser, Apache Cordova, NativeScript or server-side rendering.

index.html

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
<html> <head>
    <title>Angular 2 QuickStart</title>
    <!-- 1. Load libraries -->
    <!-- 2. Configure SystemJS -->
    <script>
    System.config({
        packages: {        
        app: {
            format: 'register',
            defaultExtension: 'js'
        }}
    });
    System.import('app/main')
            .then(null, console.error.bind(console));
    </script>
</head>
<!-- 3. Display the application -->
<body>
    <my-app>Loading...</my-app>
</body>
</html>

Angular 2

Cool stuff

RxJS : Reactive Extensions for JavaScript

Http.get returns Observable<Response>

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
interface QuerySuggestion {
    Query:string;
}
@Injectable()
export class SPSuggestionService {
    constructor(private _http:Http) { }
    public getSuggestedQueries(queryText:string, count:number = 5) 
      : Rx.Observable<QuerySuggestion[]> 
    {
        var headers = new Headers();
        headers.append('Accept', 'application/json;odata=verbose');
        return this._http.get(
                "https://contoso.com/_api/search/suggest"
                + "?fprequerysuggestions=true"
                + "&inumberofquerysuggestions="+count
                + "&querytext='"+queryText.replace("'","''")+"'",
                { headers: headers })
            .map(res => 
              <QuerySuggestion[]> res.json().d.suggest.Queries.results);
    };
}
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
import * as Rx from 'rxjs/Rx';

export class SearchComponent implements OnInit {
    queryText = new Control("");
    querySuggestions$: Rx.Observable<QuerySuggestion>;
    
    ngOnInit() {
        this.querySuggestions$ =
            this.queryText.valueChanges
                .debounceTime(400)
                .distinctUntilChanged()
                .filter((value:string) => value.length >= MinQueryLength)
                .switchMap((value:string) =>
                     this._suggestionService.getSuggestedQueries(value));
    }

Binding

1: 
2: 
3: 
<li *ngFor="#item of querySuggestions$ | async">
    {{item.Query}}
</li>

Performance

Ang2 Perf

Immutable.js

Controlling Change Detection

1: 
2: 
3: 
4: 
5: 
6: 
@Component({
  selector: 'person',
  template: `{{person.name}}`,
  changeDetection: ChangeDetectionStrategy.OnPush // ⇐===
})
class DisplayPerson {

Hierarchical injectors

Arch

Routing components

Routing

Component Router In-Depth

TypeScript Extensibility is coming

TS Ext

When release ?

ng-conf

SharePoint 2013 - Integration Challenges

Same Origin Policy & Authentication - CORS

http://www.silver-it.com/node/152

CORS recap

CORS

Solution 1

Adding the necessary HTTP response headers Access-Control-Allow-Headers, Access-Control-Allow-Methods and Access-Control-Allow-Origin at IIS level.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
<system.webServer>
  <httpProtocol>
     <customHeaders>
         <add name="Access-Control-Allow-Origin"  value="http://localhost:3000" />
         <add name="Access-Control-Allow-Headers" 
              value="Content-Type, Accept, X-Requested-With, X-File-Name"/>
         <add name="Access-Control-Allow-Methods"     value="GET, POST"/>
         <add name="Access-Control-Allow-Credentials" value="true"/>
     </customHeaders>

In a SharePoint context, you can add those headers for a given web app using the IIS console

Develop a HTTP module to work around the authentication problem regarding the preflight requests

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
public class CORSPreflightModule : IHttpModule
{
  private const string OPTIONSHEADER = "OPTIONS";
  private const string ORIGINHEADER = "ORIGIN";
  private const string ALLOWEDORIGIN = "http://localhost:3000";
  void IHttpModule.Dispose() { }

  void IHttpModule.Init(HttpApplication context) {
    context.PreSendRequestHeaders += (sender, e) => {
      var response = context.Response;

      if (context.Request.HttpMethod.ToUpperInvariant() 
            == OPTIONSHEADER.ToUpperInvariant() &&
          context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
        { response.StatusCode = (int)HttpStatusCode.OK; }
    };
  }
}

Solution 2

Reverse Proxy

Fiddler

  • Open Fiddler, Rules, Customize Rules
  • Find the OnBeforeResponse function and add the following
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
static function SetCORSResponseHeaders(oSession: Session,origin: String)  {
  oSession.oResponse.headers.Add("Access-Control-Allow-Headers",
                                 "Content-Type,Authorization,X-RequestDigest");
  oSession.oResponse.headers.Add("Access-Control-Allow-Methods",
                                 "GET,POST,PUT,DELETE,MERGE, Options");
  oSession.oResponse.headers.Add("Access-Control-Allow-Origin",origin);
  oSession.oResponse.headers.Add("Access-Control-Allow-Credentials",true);   
}
static function OnBeforeResponse(oSession: Session) {
  var origin:String="http://localhost:3000"; //do not include a ending / character
  //in this case, we know we're in a CORS context since the origin header was sent
  if(oSession.oRequest.headers["Origin"] != '') {
     SetCORSResponseHeaders(oSession,origin);
     //In case we receive a preflight request
     if(oSession.oRequest.headers.HTTPMethod=='OPTIONS'   
        && oSession.oRequest.headers["Origin"]==origin) {    
       oSession.oResponse.headers.HTTPResponseCode  = 200;
       oSession.oResponse.headers.HTTPResponseStatus = "200 OK"; 
     }
  }

Questions?

Thanks

@sergey-tihon