Develop Office Add-ins with Angular
This article provides guidance for using Angular 2+ to create an Office Add-in as a single page application.
Note
Do you have something to contribute based on your experience using Angular to create Office Add-ins? You can contribute to this article in GitHub or provide your feedback by submitting an issue in the repo.
For an Office Add-ins sample that's built using the Angular framework, see Word Style Checking Add-in Built on Angular.
Install the TypeScript type definitions
Open a Node.js window and enter the following at the command line.
npm install --save-dev @types/office-js
Bootstrapping must be inside Office.initialize
On any page that calls the Office JavaScript APIs, your code must first assign a function to Office.initialize
. Office calls this function immediately after it has initialized the Office JavaScript libraries. If you have no initialization code, the function body can just be empty "{}
" symbols, but you must not leave the Office.initialize
function undefined. For details, see Initialize your Office Add-in.
Your Angular bootstrapping code must be called inside the function that you assign to Office.initialize
. This ensures that the Office JavaScript libraries initialize first. The following is a simple example that shows how to do this. This code should be in the main.ts file of the project.
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
Office.initialize = function () {
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);
};
Use the Office dialog API with Angular
The Office Add-in dialog API enables your add-in to open a page in a nonmodal dialog box that exchanges information with the main page, which is typically in a task pane.
The displayDialogAsync method takes a parameter that specifies the URL of the page that should open in the dialog box. Your add-in can have a separate HTML page (different from the base page) to pass to this parameter, or you can pass the URL of a route in your Angular application.
It's important to remember, if you pass a route, that the dialog box creates a new window with its own execution context. Your base page and all its initialization and bootstrapping code run again in this new context, and any variables are set to their initial values in the dialog box. This technique launches a second instance of your single page application in the dialog box. Code that changes variables in the dialog box doesn't change the task pane version of the same variables. Similarly, the dialog box has its own session storage (the Window.sessionStorage property), which isn't accessible from code in the task pane.
Trigger the UI update
In an Angular app, the UI sometimes doesn't update. This is because that part of the code runs out of the Angular zone. The solution is to put the code in the zone, as shown in the following example.
import { NgZone } from '@angular/core';
export class MyComponent {
constructor(private zone: NgZone) { }
myFunction() {
this.zone.run(() => {
// The codes that need update the UI.
});
}
}
Use Observable
Angular uses RxJS (Reactive Extensions for JavaScript), and RxJS introduces Observable
and Observer
objects to implement asynchronous processing. This section provides a brief introduction to using Observables
. For more detailed information, see the official RxJS documentation.
An Observable
is like a Promise
object in some ways - it is returned immediately from an asynchronous call, but it might not resolve until some time later. However, while a Promise
is a single value (which can be an array object), an Observable
is an array of objects (possibly with only a single member). This enables code to call array methods, such as concat
, map
, and filter
, on Observable
objects.
Push instead of pull
Your code "pulls" Promise
objects by assigning them to variables, but Observable
objects "push" their values to objects that subscribe to the Observable
. The subscribers are Observer
objects. The benefit of the push architecture is that new members can be added to the Observable
array over time. When a new member is added, all the Observer
objects that subscribe to the Observable
receive a notification.
The Observer
is configured to process each new object (called the "next" object) with a function. (It is also configured to respond to an error and a completion notification. See the next section for an example.) For this reason, Observable
objects can be used in a wider range of scenarios than Promise
objects. For example, in addition to returning an Observable
from an AJAX call, the way you can return a Promise
, an Observable
can be returned from an event handler, such as the "changed" event handler for a text box. Each time a user enters text in the box, all the subscribed Observer
objects react immediately using the latest text or the current state of the application as input.
Wait until all asynchronous calls have completed
When you want to ensure that a callback only runs when every member of a set of Promise
objects has resolved, use the Promise.all()
method.
myPromise.all([x, y, z]).then(
// TODO: Callback logic goes here.
)
To do the same thing with an Observable
object, you use the Observable.forkJoin() method.
const source = Observable.forkJoin([x, y, z]);
const subscription = source.subscribe(
x => {
// TODO: Callback logic goes here.
},
err => console.log('Error: ' + err),
() => console.log('Completed')
);
Compile the Angular application using the Ahead-of-Time (AOT) compiler
Application performance is one of the most important aspects of user experience. An Angular application can be optimized by using the Angular Ahead-of-Time (AOT) compiler to compile the app at build time. It converts all source code (HTML templates and TypeScript) into efficient JavaScript code. If you compile your app with the AOT compiler, no additional compilation will occur at runtime, which results in faster rendering and faster asynchronous requests for HTML templates. Additionally, the overall application size will be reduced, because the Angular compiler won't need to be included in the application distributable.
To use the AOT compiler, add --aot
to the ng build
or ng serve
command:
ng build --aot
ng serve --aot
Note
To learn more about the Angular Ahead-of-Time (AOT) compiler, see the official guide.
Support the Trident webview control
Older Office clients use the Trident webview control provided by Internet Explorer 11, as described in Browsers and webview controls used by Office Add-ins. There are a couple Angular-specific considerations to make if your add-in needs to support these Office versions.
Angular depends on a few window.history
APIs. These APIs don't work in the Trident webview. When these APIs don't work, your add-in may not work properly, for example, it may load a blank task pane. To mitigate this, Office.js nullifies those APIs. However, if you're dynamically loading Office.js, AngularJS may load before Office.js. In that case, you should disable the window.history
APIs by adding the following code to your add-in's index.html page.
<script type="text/javascript">window.history.replaceState=null;window.history.pushState=null;</script>
If your add-in supports Trident-based browser controls, you'll need to use hash location strategy instead of the default path location strategy. The path location strategy requires HTML5 support which Trident doesn't provide.
Office Add-ins