Создание одностраничных приложений Angular с помощью Microsoft Graph
В этом руководстве рассказывается о создании Angular,которое использует microsoft Graph для получения сведений о календаре для пользователя.
Совет
Если вы предпочитаете просто скачать завершенный учебник, вы можете скачать или клонировать GitHub репозиторий.
Предварительные требования
Перед началом этого руководства необходимо установить Node.jsна компьютере разработки. Если у вас нет Node.js, посетите предыдущую ссылку для параметров загрузки.
Вы также должны иметь личную учетную запись Майкрософт с почтовым ящиком на Outlook.com или учетную запись Microsoft work или school. Если у вас нет учетной записи Майкрософт, существует несколько вариантов получения бесплатной учетной записи:
- Вы можете зарегистрироваться на новую личную учетную запись Майкрософт.
- Вы можете зарегистрироваться в программе Microsoft 365 разработчика, чтобы получить бесплатную Office 365 подписку.
Примечание
Этот учебник был написан с версией Node 14.15.0. Действия в этом руководстве могут работать с другими версиями, но они не были проверены.
Отзывы
Обратите внимание на этот учебник в репозитории GitHub.
Создание одностраничного приложения Angular
В этом разделе вы создадим новый проект Angular.
Откройте интерфейс командной строки (CLI), перейдите в каталог, в котором у вас есть права на создание файлов, и запустите следующие команды, чтобы установить Angular CLI и создать новое Angular приложение.
npm install -g @angular/cli ng new graph-tutorial
Центр Angular запросит дополнительные сведения. Ответьте на запросы следующим образом.
? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? CSS
После завершения команды измените
graph-tutorial
каталог в CLI и запустите следующую команду, чтобы запустить локальный веб-сервер.ng serve --open
Браузер по умолчанию открывается на https://localhost:4200/ странице Angular по умолчанию. Если браузер не открыт, https://localhost:4200/ откройте его и просмотрите, чтобы убедиться, что новое приложение работает.
Добавление пакетов node
Прежде чем двигаться дальше, установите дополнительные пакеты, которые вы будете использовать позже:
- ng-bootstrap для использования компонентов Bootstrap из Angular.
- время форматирования дат и времени.
- Windows-iana
- msal-angular для проверки подлинности для Azure Active Directory и получения маркеров доступа.
- microsoft-graph-client для звонков в Microsoft Graph.
Запустите следующие команды в CLI.
ng add @ng-bootstrap/ng-bootstrap npm install @azure/msal-browser@2.16.1 @azure/msal-angular@2.0.2 npm install date-fns@2.23.0 date-fns-tz@1.1.6 windows-iana@5.0.2 npm install @microsoft/microsoft-graph-client@3.0.0 npm install @microsoft/microsoft-graph-types --save-dev
Проектирование приложения
В этом разделе вы создадим пользовательский интерфейс для приложения.
Откройте ./src/styles.css и добавьте следующие строки.
@import "~bootstrap/dist/css/bootstrap.css"; /* Add padding for the nav bar */ body { padding-top: 4.5rem; } /* Style debug info in alerts */ .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; } /* Add padding to user avatar link */ .avatar-link { padding-top: .25em; padding-bottom: .25em; }
Создайте новый файл в папке ./src/app с именем user.ts и добавьте следующий код.
export class User { displayName!: string; email!: string; avatar!: string; timeZone!: string; }
Создание компонента Angular для верхней навигации на странице. В CLI запустите следующую команду.
ng generate component nav-bar
После завершения команды откройте ./src/app/nav-bar/nav-bar.component.ts и замените содержимое на следующее.
import { Component, OnInit } from '@angular/core'; import { User } from '../user'; @Component({ selector: 'app-nav-bar', templateUrl: './nav-bar.component.html', styleUrls: ['./nav-bar.component.css'] }) export class NavBarComponent implements OnInit { // Should the collapsed nav show? showNav: boolean = false; // Is a user logged in? authenticated: boolean = false; // The user user?: User = undefined; constructor() { } ngOnInit() { } // Used by the Bootstrap navbar-toggler button to hide/show // the nav in a collapsed state toggleNavBar(): void { this.showNav = !this.showNav; } signIn(): void { // Temporary this.authenticated = true; this.user = { displayName: 'Adele Vance', email: 'AdeleV@contoso.com', avatar: '/assets/no-profile-photo.png', timeZone: '' }; } signOut(): void { // Temporary this.authenticated = false; this.user = undefined; } }
Откройте ./src/app/nav-bar/nav-bar.component.htmlи замените его содержимое следующим.
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <div class="container"> <a routerLink="/" class="navbar-brand">Angular Graph Tutorial</a> <button class="navbar-toggler" type="button" (click)="toggleNavBar()" [attr.aria-expanded]="showNav" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" [class.show]="showNav" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a routerLink="/" class="nav-link" routerLinkActive="active">Home</a> </li> <li *ngIf="authenticated" class="nav-item"> <a routerLink="/calendar" class="nav-link" routerLinkActive="active">Calendar</a> </li> </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> <a class="nav-link" href="https://docs.microsoft.com/graph/overview" target="_blank">Docs</a> </li> <li *ngIf="authenticated" ngbDropdown placement="bottom-right" class="nav-item"> <a ngbDropdownToggle id="userMenu" class="nav-link avatar-link" role="button" aria-haspopup="true" aria-expanded="false"> <img src="{{user != null ? user.avatar : ''}}" class="rounded-circle align-self-center mr-2" style="width: 32px;"> </a> <div ngbDropdownMenu aria-labelledby="userMenu" class="dropdown-menu-right"> <h5 class="dropdown-item-text mb-0">{{user != null ? user.displayName : ''}}</h5> <p class="dropdown-item-text text-muted mb-0">{{user != null ? user.email : ''}}</p> <div class="dropdown-divider"></div> <button class="dropdown-item" role="button" (click)="signOut()">Sign Out</button> </div> </li> <li *ngIf="!authenticated" class="nav-item"> <button class="btn btn-link nav-link border-0" role="button" (click)="signIn()">Sign In</button> </li> </ul> </div> </div> </nav>
Создание домашней страницы для приложения. Запустите следующую команду в CLI.
ng generate component home
После завершения команды откройте ./src/app/home.component.ts и замените содержимое на следующее.
import { Component, OnInit } from '@angular/core'; import { User } from '../user'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { // Is a user logged in? authenticated: boolean = false; // The user user?: User = undefined; constructor() { } ngOnInit() { } signIn(): void { // Temporary this.authenticated = true; this.user = { displayName: 'Adele Vance', email: 'AdeleV@contoso.com', avatar: '', timeZone: '' }; } }
Откройте ./src/app/home/home.component.htmlи замените его содержимое следующим.
<div class="jumbotron"> <h1>Angular Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API from Angular</p> <div *ngIf="authenticated; then welcomeUser else signInPrompt"></div> <ng-template #welcomeUser> <h4>Welcome {{ user != null ? user.displayName : '' }}!</h4> <p>Use the navigation bar at the top of the page to get started.</p> </ng-template> <ng-template #signInPrompt> <button class="btn btn-primary btn-large" role="button" (click)="signIn()">Click here to sign in</button> </ng-template> </div>
Создайте простой
Alert
класс. Создайте новый файл в каталоге ./src/app с именем alert.ts и добавьте следующий код.export class Alert { type!: string; message!: string; debug!: string; }
Создайте службу оповещения, которую приложение может использовать для отображения сообщений пользователю. В CLI запустите следующую команду.
ng generate service alerts
Откройте ./src/app/alerts.service.ts и замените содержимое на следующее.
import { Injectable } from '@angular/core'; import { Alert } from './alert'; @Injectable({ providedIn: 'root' }) export class AlertsService { alerts: Alert[] = []; addError(message: string, debug?: string) { this.alerts.push({message: message, debug: debug ?? '', type: 'danger'}); } addSuccess(message: string, debug?: string) { this.alerts.push({message: message, debug: debug ?? '', type: 'success'}); } remove(alert: Alert) { this.alerts.splice(this.alerts.indexOf(alert), 1); } }
Создание компонента оповещений для отображения оповещений. В CLI запустите следующую команду.
ng generate component alerts
После завершения команды откройте ./src/app/alerts/alerts.component.ts и замените содержимое на следующее.
import { Component, OnInit } from '@angular/core'; import { AlertsService } from '../alerts.service'; import { Alert } from '../alert'; @Component({ selector: 'app-alerts', templateUrl: './alerts.component.html', styleUrls: ['./alerts.component.css'] }) export class AlertsComponent implements OnInit { constructor(public alertsService: AlertsService) { } ngOnInit() { } close(alert: Alert) { this.alertsService.remove(alert); } }
Откройте ./src/app/alerts/alerts.component.htmlи замените его содержимое следующим.
<div *ngFor="let alert of alertsService.alerts"> <ngb-alert type="{{alert.type}}" (close)="close(alert)"> {{alert.message}} <pre *ngIf="alert.debug" class="alert-pre border bg-light p-2 mt-2"><code>{{alert.debug}}</code></pre> </ngb-alert> </div>
Откройте ./src/app/app-routing.module.ts
const routes: Routes = [];
и замените строку следующим кодом.import { HomeComponent } from './home/home.component'; const routes: Routes = [ { path: '', component: HomeComponent }, ];
Откройте ./src/app/app.component.htmlи замените все содержимое на следующее.
<app-nav-bar></app-nav-bar> <main role="main" class="container"> <app-alerts></app-alerts> <router-outlet></router-outlet> </main>
Добавьте файл изображений выбора с именем no-profile-photo.png в каталоге ./src/assets . Это изображение будет использоваться в качестве фотографии пользователя, если у пользователя нет фотографии в Microsoft Graph.
Сохраните все изменения и обновите страницу. Теперь приложение должно выглядеть совсем по-другому.
Регистрация приложения на портале
В этом упражнении будет создаваться новая регистрация веб-приложений Azure AD с помощью центра администрирования Azure Active Directory администратора.
Откройте браузер и перейдите в Центр администрирования Azure Active Directory. Войдите с помощью личной учетной записи (т.е. учетной записи Microsoft) или рабочей (учебной) учетной записи.
Выберите Azure Active Directory на панели навигации слева, затем выберите Регистрация приложений в разделе Управление.
Выберите Новая регистрация. На странице Зарегистрировать приложение задайте необходимые значения следующим образом.
- Введите имя
Angular Graph Tutorial
. - Введите поддерживаемые типы учетных записей для учетных записей в любом каталоге организаций и личных учетных записей Microsoft.
- В разделе URI адрес перенаправления введите значение в первом раскрывающемся списке
Single-page application (SPA)
и задайте значениеhttp://localhost:4200
.
- Введите имя
Нажмите Зарегистрировать. На странице Angular Graph учебника скопируйте значение ID приложения (клиента) и сохраните его, оно потребуется на следующем шаге.
Добавление проверки подлинности с помощью Azure AD
В этом упражнении вы расширит приложение от предыдущего упражнения для поддержки проверки подлинности с помощью Azure AD. Это необходимо для получения необходимого маркера доступа OAuth для вызова microsoft Graph. На этом шаге вы интегрируете библиотеку проверки подлинности Майкрософт для Angular в приложение.
Создайте новый файл в каталоге ./src с именем oauth.ts и добавьте следующий код.
export const OAuthSettings = { appId: 'YOUR_APP_ID_HERE', redirectUri: 'http://localhost:4200', scopes: [ "user.read", "mailboxsettings.read", "calendars.readwrite" ] };
Замените
YOUR_APP_ID_HERE
с помощью ID приложения с портала регистрации приложений.Важно!
Если вы используете источник управления, например git, то сейчас самое время исключить файл oauth.ts из-под контроля источника, чтобы избежать случайной утечки вашего ID приложения.
Откройте ./src/app/app.module.ts
import
и добавьте следующие утверждения в верхнюю часть файла.import { FormsModule } from '@angular/forms'; import { IPublicClientApplication, PublicClientApplication, BrowserCacheLocation } from '@azure/msal-browser'; import { MsalModule, MsalService, MSAL_INSTANCE } from '@azure/msal-angular'; import { OAuthSettings } from '../oauth';
Добавьте следующую функцию под утверждениями
import
.let msalInstance: IPublicClientApplication | undefined = undefined; export function MSALInstanceFactory(): IPublicClientApplication { msalInstance = msalInstance ?? new PublicClientApplication({ auth: { clientId: OAuthSettings.appId, redirectUri: OAuthSettings.redirectUri, postLogoutRedirectUri: OAuthSettings.redirectUri }, cache: { cacheLocation: BrowserCacheLocation.LocalStorage, } }); return msalInstance; }
Добавьте
MsalModule
иFormsModule
в массивimports
внутри декларации@NgModule
.imports: [ BrowserModule, FormsModule, AppRoutingModule, NgbModule, MsalModule ],
Добавьте массив
MSALInstanceFactory
MsalService
и вproviders
массив внутри объявления@NgModule
.providers: [ { provide: MSAL_INSTANCE, useFactory: MSALInstanceFactory }, MsalService ],
Реализация входа в систему
В этом разделе вы создадим службу проверки подлинности и внедрим вход и вход.
Запустите следующую команду в CLI.
ng generate service auth
Создав для этого службу, вы можете легко ввести ее в любые компоненты, которые нуждаются в доступе к методам проверки подлинности.
После завершения команды откройте ./src/app/auth.service.ts и замените содержимое следующим кодом.
import { Injectable } from '@angular/core'; import { MsalService } from '@azure/msal-angular'; import { InteractionType, PublicClientApplication } from '@azure/msal-browser'; import { AlertsService } from './alerts.service'; import { OAuthSettings } from '../oauth'; import { User } from './user'; @Injectable({ providedIn: 'root' }) export class AuthService { public authenticated: boolean; public user?: User; constructor( private msalService: MsalService, private alertsService: AlertsService) { this.authenticated = false; this.user = undefined; } // Prompt the user to sign in and // grant consent to the requested permission scopes async signIn(): Promise<void> { const result = await this.msalService .loginPopup(OAuthSettings) .toPromise() .catch((reason) => { this.alertsService.addError('Login failed', JSON.stringify(reason, null, 2)); }); if (result) { this.msalService.instance.setActiveAccount(result.account); this.authenticated = true; // Temporary placeholder this.user = new User(); this.user.displayName = 'Adele Vance'; this.user.email = 'AdeleV@contoso.com'; this.user.avatar = '/assets/no-profile-photo.png'; // Temporary to display token in an error box this.alertsService.addSuccess('Token acquired', result.accessToken); } } // Sign out async signOut(): Promise<void> { await this.msalService.logout().toPromise(); this.user = undefined; this.authenticated = false; } }
Откройте ./src/app/nav-bar/nav-bar.component.ts и замените его содержимое следующим.
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { User } from '../user'; @Component({ selector: 'app-nav-bar', templateUrl: './nav-bar.component.html', styleUrls: ['./nav-bar.component.css'] }) export class NavBarComponent implements OnInit { // Should the collapsed nav show? showNav: boolean = false; // Is a user logged in? get authenticated(): boolean { return this.authService.authenticated; } // The user get user(): User | undefined { return this.authService.user; } constructor(private authService: AuthService) { } ngOnInit() { } // Used by the Bootstrap navbar-toggler button to hide/show // the nav in a collapsed state toggleNavBar(): void { this.showNav = !this.showNav; } async signIn(): Promise<void> { await this.authService.signIn(); } signOut(): void { this.authService.signOut(); } }
Откройте ./src/app/home/home.component.ts и замените его содержимое следующим.
Сохраните изменения и обновите браузер. Нажмите кнопку Нажмите здесь, чтобы войти в кнопку, и вы должны быть перенаправлены на https://login.microsoftonline.com
. Вход с учетной записью Майкрософт и согласие на запрашиваемую разрешения. Страница приложения должна обновиться, показывая маркер.
Получение сведений о пользователе
Сейчас служба проверки подлинности задает постоянные значения для имени и адреса электронной почты пользователя. Теперь, когда у вас есть маркер доступа, вы можете получить сведения о пользователях из Microsoft Graph, чтобы эти значения соответствовали текущему пользователю.
Откройте ./src/app/auth.service.ts
import
и добавьте следующие утверждения в верхнюю часть файла.import { Client } from '@microsoft/microsoft-graph-client'; import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
Добавьте свойство в класс
AuthService
с изваной .graphClient
public graphClient?: Client;
Добавьте к классу
AuthService
новую функцию с именемgetUser
.private async getUser(): Promise<User | undefined> { if (!this.authenticated) return undefined; const graphClient = Client.init({ // Initialize the Graph client with an auth // provider that requests the token from the // auth service authProvider: async(done) => { const token = await this.getAccessToken() .catch((reason) => { done(reason, null); }); if (token) { done(null, token); } else { done("Could not get an access token", null); } } }); // Get the user from Graph (GET /me) const graphUser: MicrosoftGraph.User = await graphClient .api('/me') .select('displayName,mail,mailboxSettings,userPrincipalName') .get(); const user = new User(); user.displayName = graphUser.displayName ?? ''; // Prefer the mail property, but fall back to userPrincipalName user.email = graphUser.mail ?? graphUser.userPrincipalName ?? ''; user.timeZone = graphUser.mailboxSettings?.timeZone ?? 'UTC'; // Use default avatar user.avatar = '/assets/no-profile-photo.png'; return user; }
Найдите и удалите следующий код из
signIn
метода.// Temporary placeholder this.user = new User(); this.user.displayName = "Adele Vance"; this.user.email = "AdeleV@contoso.com"; this.user.avatar = '/assets/no-profile-photo.png'; // Temporary to display token in an error box this.alertsService.addSuccess('Token acquired', result);
На его месте добавьте следующий код.
this.user = await this.getUser();
Новый код использует SDK microsoft Graph для получения сведений пользователя,
User
а затем создает объект с использованием значений, возвращаемого вызовом API.Измените
constructor
класс,AuthService
чтобы проверить, вошел ли пользователь, и загрузив его данные, если это так. Замените существующееconstructor
следующим.constructor( private msalService: MsalService, private alertsService: AlertsService) { this.authenticated = this.msalService.instance .getAllAccounts().length > 0; this.getUser().then((user) => {this.user = user}); }
Теперь, если вы сохраните изменения и запустите приложение, после регистрации вы должны вернуться на домашную страницу, но пользовательский интерфейс должен измениться, чтобы указать, что вы подписаны.
Щелкните аватар пользователя в правом верхнем углу, чтобы получить доступ к ссылке Sign Out . Щелкнув кнопку "Выйти", вы сбросит сеанс и возвращает вас на домашнюю страницу.
Хранение и обновление маркеров
На этом этапе у приложения есть маркер доступа, который отправляется Authorization
в заголовке вызовов API. Это маркер, который позволяет приложению получать доступ к microsoft Graph от имени пользователя.
Однако этот маркер недолговечен. Срок действия маркера истекает через час после его выпуска. Поскольку приложение использует библиотеку MSAL, не нужно внедрять логику хранения маркеров или обновления. Кэширования MsalService
маркера в хранилище браузера. Метод acquireTokenSilent
сначала проверяет кэш-маркер, и если срок его действия не истек, он возвращает его. Если срок действия истек, он делает молчаливый запрос на получение нового.
Просмотр календаря
В этом упражнении вы будете включать Graph Microsoft в приложение. Для этого приложения вы будете использовать библиотеку microsoft-graph-client для звонков в Microsoft Graph.
Получение событий календаря из Outlook
Добавьте новую службу для удержания всех Graph вызовов. Запустите следующую команду в CLI.
ng generate service graph
Так же, как и в созданной ранее службе проверки подлинности, создание службы позволяет вводить ее в любые компоненты, которые нуждаются в доступе к microsoft Graph.
После завершения команды откройте ./src/app/graph.service.ts и замените содержимое следующим.
import { Injectable } from '@angular/core'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import { AuthService } from './auth.service'; import { AlertsService } from './alerts.service'; @Injectable({ providedIn: 'root' }) export class GraphService { constructor( private authService: AuthService, private alertsService: AlertsService) {} async getCalendarView(start: string, end: string, timeZone: string): Promise<MicrosoftGraph.Event[] | undefined> { if (!this.authService.graphClient) { this.alertsService.addError('Graph client is not initialized.'); return undefined; } try { // GET /me/calendarview?startDateTime=''&endDateTime='' // &$select=subject,organizer,start,end // &$orderby=start/dateTime // &$top=50 const result = await this.authService.graphClient .api('/me/calendarview') .header('Prefer', `outlook.timezone="${timeZone}"`) .query({ startDateTime: start, endDateTime: end }) .select('subject,organizer,start,end') .orderby('start/dateTime') .top(50) .get(); return result.value; } catch (error) { this.alertsService.addError('Could not get events', JSON.stringify(error, null, 2)); } return undefined; } }
Давайте посмотрим, что делает этот код.
- Он инициализирует Graph клиента в конструкторе для службы.
- Он реализует функцию
getCalendarView
, использующую Graph клиента следующим образом:- Вызывается URL-адрес
/me/calendarview
. - Метод
header
включает заглавнуюPrefer: outlook.timezone
, из-за чего время начала и окончания возвращаемых событий должно быть в предпочтительном часовом поясе пользователя. - Метод
query
добавляет параметрыstartDateTime
endDateTime
и добавляет окно времени для представления календаря. - Метод
select
ограничивает поля, возвращенные для каждого события, только теми, которые будут фактически использовать представление. - Метод
orderby
сортировать результаты по времени начала.
- Вызывается URL-адрес
Создайте Angular для вызова этого нового метода и отображения результатов вызова. Запустите следующую команду в CLI.
ng generate component calendar
После завершения команды
routes
добавьте компонент в массив в ./src/app/app-routing.module.ts.import { CalendarComponent } from './calendar/calendar.component'; const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'calendar', component: CalendarComponent }, ];
Откройте ./src/app/calendar/calendar.component.ts и замените содержимое на следующее.
import { Component, OnInit } from '@angular/core'; import { parseISO } from 'date-fns'; import { endOfWeek, startOfWeek } from 'date-fns/esm'; import { zonedTimeToUtc } from 'date-fns-tz'; import { findIana } from 'windows-iana'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import { AuthService } from '../auth.service'; import { GraphService } from '../graph.service'; import { AlertsService } from '../alerts.service'; @Component({ selector: 'app-calendar', templateUrl: './calendar.component.html', styleUrls: ['./calendar.component.css'] }) export class CalendarComponent implements OnInit { public events?: MicrosoftGraph.Event[]; constructor( private authService: AuthService, private graphService: GraphService, private alertsService: AlertsService) { } async ngOnInit() { // Convert the user's timezone to IANA format const ianaName = findIana(this.authService.user?.timeZone ?? 'UTC'); const timeZone = ianaName![0].valueOf() || this.authService.user?.timeZone || 'UTC'; // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z const now = new Date(); const weekStart = zonedTimeToUtc(startOfWeek(now), timeZone); const weekEnd = zonedTimeToUtc(endOfWeek(now), timeZone); this.events = await this.graphService.getCalendarView( weekStart.toISOString(), weekEnd.toISOString(), this.authService.user?.timeZone ?? 'UTC'); // Temporary to display raw results this.alertsService.addSuccess('Events from Graph', JSON.stringify(events, null, 2)); } }
Пока это только отрисовка массива событий в JSON на странице. Сохраните изменения и перезапустите приложение. Войдите и щелкните ссылку Календарь в панели nav. Если все работает надлежащим образом, в календаре пользователя должен появиться дамп событий в формате JSON.
Отображение результатов
Теперь вы можете обновить компонент CalendarComponent
, чтобы отображать события более удобным для пользователя образом.
Удалите временный код, который добавляет оповещение из функции
ngOnInit
. Обновленная функция должна выглядеть так.ngOnInit() { // Convert the user's timezone to IANA format const ianaName = findIana(this.authService.user?.timeZone ?? 'UTC'); const timeZone = ianaName![0].valueOf() || this.authService.user?.timeZone || 'UTC'; // Get midnight on the start of the current week in the user's timezone, // but in UTC. For example, for Pacific Standard Time, the time value would be // 07:00:00Z var startOfWeek = moment.tz(timeZone).startOf('week').utc(); var endOfWeek = moment(startOfWeek).add(7, 'day'); this.graphService.getCalendarView( startOfWeek.format(), endOfWeek.format(), this.authService.user?.timeZone ?? 'UTC') .then((events) => { this.events = events; }); }
Добавьте функцию в
CalendarComponent
класс для формата объектаDateTimeTimeZone
в строку ISO.formatDateTimeTimeZone(dateTime: MicrosoftGraph.DateTimeTimeZone | undefined | null): string { if (dateTime == undefined || dateTime == null) { return ''; } try { // Pass UTC for the time zone because the value // is already adjusted to the user's time zone return moment.tz(dateTime.dateTime, 'UTC').format(); } catch(error) { this.alertsService.addError('DateTimeTimeZone conversion error', JSON.stringify(error)); return ''; } }
Откройте ./src/app/calendar/calendar.component.htmlи замените его содержимое следующим.
<h1>Calendar</h1> <a class="btn btn-light btn-sm mb-3" routerLink="/newevent">New event</a> <table class="table"> <thead> <th scope="col">Organizer</th> <th scope="col">Subject</th> <th scope="col">Start</th> <th scope="col">End</th> </thead> <tbody> <tr *ngFor="let event of events"> <td>{{event.organizer?.emailAddress?.name}}</td> <td>{{event.subject}}</td> <!-- Use 'UTC' in the pipe to date so Angular will not convert the already converted time to local time. See https://angular.io/api/common/DatePipe --> <td>{{formatDateTimeTimeZone(event.start) | date:'short' : 'UTC' }}</td> <td>{{formatDateTimeTimeZone(event.end) | date: 'short' : 'UTC' }}</td> </tr> </tbody> </table>
Это циклы через коллекцию событий и добавляет строку таблицы для каждого из них. Сохраните изменения и перезапустите приложение. Щелкните ссылку Календарь , и теперь приложение должно отрисовки таблицы событий.
Создание нового события
В этом разделе вы добавим возможность создания событий в календаре пользователя.
Откройте ./src/app/graph.service.ts и добавьте в класс следующую
GraphService
функцию.async addEventToCalendar(newEvent: MicrosoftGraph.Event): Promise<void> { try { // POST /me/events await this.graphClient .api('/me/events') .post(newEvent); } catch (error) { throw Error(JSON.stringify(error, null, 2)); } }
Создание новой формы события
Создайте компонент Angular для отображения формы и вызова этой новой функции. Запустите следующую команду в CLI.
ng generate component new-event
После завершения команды
routes
добавьте компонент в массив в ./src/app/app-routing.module.ts.import { NewEventComponent } from './new-event/new-event.component'; const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'calendar', component: CalendarComponent }, { path: 'newevent', component: NewEventComponent }, ];
Создайте новый файл в каталоге ./src/app/new-event с именем new-event.ts и добавьте следующий код.
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; // Model for the new event form export class NewEvent { subject?: string; attendees?: string; start?: string; end?: string; body?: string; // Generate a MicrosoftGraph.Event from the model getGraphEvent(timeZone: string): MicrosoftGraph.Event { const graphEvent: MicrosoftGraph.Event = { subject: this.subject, start: { dateTime: this.start, timeZone: timeZone }, end: { dateTime: this.end, timeZone: timeZone } }; // If there are attendees, convert to array // and add them if (this.attendees && this.attendees.length > 0) { graphEvent.attendees = []; const emails = this.attendees.split(';'); emails.forEach(email => { graphEvent.attendees?.push({ type: 'required', emailAddress: { address: email } }); }); } // If there is a body, add it as plain text if (this.body && this.body.length > 0) { graphEvent.body = { contentType: 'text', content: this.body }; } return graphEvent; } }
Этот класс будет служить моделью для новой формы событий.
Откройте ./src/app/new-event/new-event.component.ts и замените содержимое на следующий код.
import { Component, OnInit } from '@angular/core'; import { AuthService } from '../auth.service'; import { GraphService } from '../graph.service'; import { AlertsService } from '../alerts.service'; import { NewEvent } from './new-event'; @Component({ selector: 'app-new-event', templateUrl: './new-event.component.html', styleUrls: ['./new-event.component.css'] }) export class NewEventComponent implements OnInit { model = new NewEvent(); constructor( private authService: AuthService, private graphService: GraphService, private alertsService: AlertsService) { } ngOnInit(): void { } onSubmit(): void { const timeZone = this.authService.user?.timeZone ?? 'UTC'; const graphEvent = this.model.getGraphEvent(timeZone); this.graphService.addEventToCalendar(graphEvent) .then(() => { this.alertsService.addSuccess('Event created.'); }).catch(error => { this.alertsService.addError('Error creating event.', error.message); }); } }
Откройте ./src/app/new-event/new-event.component.htmlи замените содержимое следующим кодом.
<h1>New event</h1> <form (ngSubmit)="onSubmit()" #newEventForm="ngForm"> <div class="form-group"> <label for="subject">Subject</label> <input type="text" class="form-control" id="subject" required [(ngModel)]="model.subject" name="subject" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger">Subject is required</div> </div> <div class="form-group"> <label for="attendees">Attendees</label> <input type="text" class="form-control" id="attendees" placeholder="Enter one or more email addresses (separated with a ;) to add attendees" [(ngModel)]="model.attendees" name="attendees" #name="ngModel"> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label for="start">Start</label> <input type="datetime-local" class="form-control" id="start" required [(ngModel)]="model.start" name="start" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger">Start is required</div> </div> </div> <div class="col"> <div class="form-group"> <label for="end">End</label> <input type="datetime-local" class="form-control" id="end" required [(ngModel)]="model.end" name="end" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="alert alert-danger">End is required</div> </div> </div> </div> <div class="form-group"> <label for="body">Body</label> <textarea class="form-control" id="body" rows="4" [(ngModel)]="model.body" name="body" #name="ngModel"></textarea> </div> <button type="submit" class="btn btn-primary mr-2" [disabled]="!newEventForm.form.valid">Create</button> <a class="btn btn-secondary" routerLink="/calendar">Cancel</a> </form>
Сохраните изменения и обновите приложение. Выберите кнопку New event на странице календаря, а затем используйте форму для создания события в календаре пользователя.
Поздравляем!
Вы завершили учебный Angular Microsoft Graph. Теперь, когда у вас есть рабочее приложение, которое вызывает Microsoft Graph, вы можете экспериментировать и добавлять новые функции. В обзоре microsoft Graph все данные, к которые можно получить доступ с помощью Microsoft Graph.
Отзывы
Обратите внимание на этот учебник в репозитории GitHub.
Возникла проблема с этим разделом? Если это так, отправьте нам отзыв, чтобы мы исправили этот раздел.