Поделиться через


Angular 2.0 and the Microsoft Graph

Yesterday, the AngularJS Team announced the official beta release of Angular 2. The beta milestone is a significant achievement that should motivate developers to give Angular 2 a serious consideration for new web/mobile projects. Besides getting ready for primetime, Angular 2 offers some significant improvements over Angular 1 (just read here). In this post, I’ll describe the steps of building an Angular 2 application that authenticates to Azure Active Directory (using an implicit OAuth2 flow) and calls into the Microsoft Graph API. In addition to the completed solution on GitHub, I’ve also provided a step-by-step video of building the Angular 2/Office 365 app from scratch:

[View:https://www.youtube.com/watch?v=QoTKK2_-dC0]

Angular 2 and TypeScript

The AngularJS Team worked closely with Microsoft to take advantage of TypeScript in Angular 2. If you are new to TypeScript, it is a strongly-typed JavaScript superset that compiles down to JavaScript. Because it is class-based and type-strict, it is favored by many developers for client-side development (especially those with object oriented backgrounds). TypeScript introduces a few interesting challenges to a web project.

TypeScript must be compiled to JavaScript before run in a browser (some browsers can compile TypeScript, but this is slow). Luckily, the TypeScript compiler can be run in a watch mode that will automatically re-compile when a TypeScript file has been saved. Notice the start script we have configured below in the package.json (used by the Node Package Manager). This script will concurrently start the TypeScript complier in watch mode (tsc –w) and start the live-server web hosts (which also listens for files changes to provide automatic refreshes).

package.json with start script

 { "name": "Angular2Files",    "version": "1.0.0", "scripts": {        "start": "concurrent \"tsc -w\" \"live-server\""    },  "dependencies": {       "jquery": "*",      "bootstrap": "*",       "angular2": "2.0.0-beta.0",     "systemjs": "0.19.6",       "es6-promise": "^3.0.2",        "es6-shim": "0.33.0",       "reflect-metadata": "0.1.2",        "rxjs": "5.0.0-beta.0",     "zone.js": "0.5.10" },  "devDependencies": {        "concurrently": "^1.0.0",       "live-server": "^0.9.0",        "typescript": "1.7.3"   }}

 

The class-based implementation of TypeScript promotes the separation of different classes into different script files. This can make script references messy in HTML. To overcome this, the project (and samples on angular.io) use SystemJS to reference all the generated .js files as a package. Below shows the System.config that tells SystemJS to reference all the .js files found under the “src” folder.

System.config to dynamically load .js scripts

 <script type="text/javascript"> System.config({     packages: {         "src": {                format: "register",             defaultExtension: "js"          }       }   }); System.import("src/app/app");</script>

 

OAuth2 with Azure AD

The Azure AD Team created an Angular module for integrating the Azure AD Authentication Library (ADAL) into Angular 1 projects. For this project, I decided to leverage a manual/raw OAuth2 flow instead of using a library (which I think is valuable for all developers to understand). However, I’m starting to investigate the ngUpgrade and ngForward goodness the AngularJS Teams are offering for mixing Angular 1 and 2 in one project…perhaps my next post

For an implicit OAuth flow with Azure AD, we will first redirect the user through a sign-in and consent flow to get an id_token. Once we have an id_token, we know the user is signed in and we should be able to get an access_token using a different redirect. These two redirects are depicted in the AuthHelper.ts file as the login and getAccessToken functions. Note that both the id_token and access_token will be passed back from Azure AD as URL parameters. The constructor handles this parameter check.

authHelper for manage Azure AD authentication

 import { Injectable } from "angular2/core";import { SvcConsts } from "../svcConsts/svcConsts";@Injectable()export class AuthHelper {  //function to parse the url query string    private parseQueryString = function(url) {      var params = {}, queryString = url.substring(1),        regex = /([^&=]+)=([^&]*)/g, m;     while (m = regex.exec(queryString)) {           params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);        }       return params;  }   private params = this.parseQueryString(location.hash);  public access_token:string = null;      constructor() {     //check for id_token or access_token in url     if (this.params["id_token"] != null)            this.getAccessToken();      else if (this.params["access_token"] != null)           this.access_token = this.params["access_token"];    }       login() {       //redirect to get id_token      window.location.href = "https://login.microsoftonline.com/" + SvcConsts.TENTANT_ID +            "/oauth2/authorize?response_type=id_token&client_id=" + SvcConsts.CLIENT_ID +           "&redirect_uri=" + encodeURIComponent(window.location.href) +           "&state=SomeState&nonce=SomeNonce"; }       private getAccessToken() {      //redirect to get access_token      window.location.href = "https://login.microsoftonline.com/" + SvcConsts.TENTANT_ID +            "/oauth2/authorize?response_type=token&client_id=" + SvcConsts.CLIENT_ID +          "&resource=" + SvcConsts.GRAPH_RESOURCE +           "&redirect_uri=" + encodeURIComponent(window.location.href) +           "&prompt=none&state=SomeState&nonce=SomeNonce"; }}

 

Angular 2 Routes

Many single page applications in Angular 1 make use of Angular routing (or Angular UI Router) to dynamically load partial views without reloading the entire page. Implementing Angular 1 routing required an additional script reference and dependency to the routing module (ex: ngRoute). Angular 2 routing has many similarities to its predecessor…references an additional script reference, added as dependency on the root module, contains route config, offers object/functions to navigate between views, etc. However, these look much different when implemented in TypeScript. Below is the routing implementation for my project where I’ve highlighted routing specific code.

Note: the ADAL module for Angular 1 provided a route extension for determining if a view required authentication or not (via requireADLogin flag). Given my simple project contains only two views (login and files), I simply perform a check in the constructor of the App to navigate between the two based on the existence of an access token.

Route configuration in Angular 2

 import { Component, provide } from "angular2/core";import { bootstrap } from "angular2/platform/browser";import { Router, RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy } from "angular2/router";import { HTTP_PROVIDERS } from "angular2/http";import { Login } from "../login/login";import { Files } from "../files/files";import { AuthHelper } from "../authHelper/authHelper";@Component({   selector: "files-app",  template: "<router-outlet></router-outlet>",    directives: [ROUTER_DIRECTIVES],    providers: [HTTP_PROVIDERS]})// Configure the routes for the app@RouteConfig([ { name: "Login", component: Login, path: "/login" }, { name: "Files", component: Files, path: "/files" } ])export class App {    constructor(router:Router, auth:AuthHelper) {       // Route the user to a view based on presence of access token       if (auth.access_token !== null) {           // access token exists...display the users files            router.navigate(["/Files"]);        }       else {          // access token doesn't exist, so the user needs to login           router.navigate(["/Login"]);        }   }}bootstrap(App, [AuthHelper, ROUTER_PROVIDERS, provide(LocationStrategy, { useClass: HashLocationStrategy })]);

 

Calling the Microsoft Graph

In Angular 1, the $http object was commonly used for performing REST calls into the Microsoft Graph. Angular 2 offers an Http object that performs the same operations. This requires an additional script reference and import as seen below. Also notice the addition of the Authorization Bearer token included in the header of the Microsoft Graph request.

Calling Microsoft Graph

 import { Component, View } from "angular2/core";import { Http, Headers } from "angular2/http";import { AuthHelper } from "../authHelper/authHelper"@Component({   selector: "files"})@View({  templateUrl: "src/files/view-files.html"})export class Files {  private files = []; constructor(http:Http, authHelper:AuthHelper) {     // Perform REST call into Microsoft Graph for files on OneDrive for Business        http.get("https://graph.microsoft.com/v1.0/me/drive/root/children", {           headers: new Headers({ "Authorization": "Bearer " + authHelper.access_token })      })      .subscribe(res => {          // Check the response status before trying to display files         if (res.status === 200)             this.files = res.json().value;          else                alert("An error occurred calling the Microsoft Graph: " + res.status);      }); }}

 

Conclusions

I’m really excited the see Angular 2 reach Beta and anxious to see the creative ways the Microsoft community leverages is in their solutions. You can download the Angular 2 Files project from GitHub: https://github.com/OfficeDev/O365-Angular2-Microsoft-Graph-MyFiles

Comments

  • Anonymous
    December 16, 2015
    Hey Rich Any examples you've done where the angular assets are hosted inside sharepoint and one is able to reuse the authentication done by sharepoint rather than use adal.  When building custom experiences hosted in sharepoint really dont want the user to need to go through a login flow and would love to streamline that as much as possible Thanks for all the great samples! dave

  • Anonymous
    December 17, 2015
    Really good video, learned a tonne of new stuff. Angular 2 looks amazing, from the point of view of how it works, but it's massive (~512KB minified) and also has dependencies (rxjs and system.js, as well as the es6-shim and pollyfiller libraries). You are talking close to 1MB before you even write any code. Compare that to Angular 1 which is around ~140KB when minified, with no dependencies. Any plans to address that?

  • Anonymous
    December 17, 2015
    The comment has been removed

  • Anonymous
    December 21, 2015
    @John why 1mb is a big problem, js files are cached on the browser and they are also cached in many CDNs around the world, I have never had any problem and I think I have had bigger js files to be loaded than 1MB.

  • Anonymous
    December 23, 2015
    The app authorization tool is not working. It redirects to dev.office.com/app-registration?error_msg=At+this+time%2c+we+can%27t+sign+in+and+register+the+app+with+a+Microsoft+account+(we%27re+working+on+enabling+that).+Instead+please+sign+in+with+an+Office+365+account.

  • Anonymous
    February 04, 2016
    Dave Feldman - that is EXACTLY what I am doing right now. Have the exact same question on you. Any kind of revelations regarding this? twitter @jonaseriksson84