New to Kendo UI for Angular? Start a free 30-day trial

Consuming Data from Azure Functions

This tutorial demonstrates how to configure Azure Functions to serve data to a Kendo UI Grid for Angular.

Prerequisites

Creating Azure Functions Applications

  1. Follow the Create your first function in the Azure portal > Create a function app quickstart guide.
  2. Provide kendo-grid-functions as a name to the application and set the name of the storage account to kendogridfunctions.
  3. In Azure Portal, go to the newly created kendo-grid-functions application.

Creating HTTP-Triggered Functions for CRUD Operations

To set the read, create, destroy, and update functions, apply the following steps individually to each of them.

  1. On the left-side panel and under the application name, click the + (plus) symbol. When the Functions section is hovered, the symbol appears to the right.

  2. Implement the four actions in the ItemController which will be called by the Grid on performing CRUD operations. Provide names for the actions—for example, KendoRead, KendoCreate, KendoUpdate, and KendoDestroy.

    Image 1: Overview of the application functions Kendo UI for Angular - Cloud Integration - Application functions overview in Azure

  3. If the Get started quickly with a premade function screen appears, click the Custom function link at the bottom.

    Image 2: The Get started quickly with a premade function window Kendo UI for Angular - Cloud Integration - Custom functions in Azure

  4. Click the HTTP trigger option. On the panel that appears to the right, select the language and fill in a meaningful name for each function. As this tutorial will later on use the Read, Create, Update, and Destroy names for the four functions and will implement the Azure Functions in C#, select the C# language.

    Image 3: Configuring a new HTTP trigger function Kendo UI for Angular - Cloud Integration - HTTP trigger function in Azure

Integrating Input for the Read Function

  1. Expand the Read function and, under the function name on the left navigation panel, click the Integrate section.

  2. In the Inputs section, click the New Input button.

  3. Select Azure Table Storage as the input storage that will be integrated. Click Select.

    Image 4: Integrating the new input for the function Kendo UI for Angular - Cloud Integration - Integrating new function input in Azure

  4. Type Product for the partition key of the table.

  5. Choose the maximum number of records to read. In this case, the default value of 50 will be preserved.

  6. In Storage account connection to the right of the field, click the new link.

  7. Select the kendogridfunctions connection that was created during the initial setup of the application.

  8. Change Table name to Products.

  9. Click Save to save the newly integrated input table.

    Image 5: Configuring the new input Kendo UI for Angular - Cloud Integration - New input configuration in Azure

Integrating Output for the Create, Destroy, and Update Functions

To configure an output integration for each of the other three functions (create, destroy, and update):

  1. Click New Output.

  2. Select Azure Table Storage and click Select.

    Image 6: Integrating the new output for the function Kendo UI for Angular - Cloud Integration - Integrate new output for the function in Azure

  3. Select kendogridfunctions_STORAGE for the storage account connection.

  4. Change Table name to Products.

  5. Click Save to save the newly integrated output table.

    Image 7: Configuring the new output Kendo UI for Angular - Cloud Integration - New output configuration in Azure

Implementing the Model

The actual implementation requires you to first create a definition for the Product class:

  1. Select the Read function.

  2. On the right, click View files.

    Image 8: Opening the function files Kendo UI for Angular - Cloud Integration - Open the function files in Azure

  3. Click the Add button and provide the product.csx name to the new file.

    Image 9: Creating a new function file Kendo UI for Angular - Cloud Integration - Create new function file in Azure

  4. Place the following class definition in the file.

    using Microsoft.WindowsAzure.Storage.Table;
    
    public class Product :TableEntity
    {
        public string ProductName { get; set; }
    
        public double UnitPrice { get; set; }
    
        public int UnitsInStock { get; set; }
    
        public bool Discontinued { get; set; }
    
        public Product ToEntity()
        {
            return new Product
            {
                PartitionKey = "Product",
                RowKey = this?.RowKey,
                ProductName = this?.ProductName,
                UnitPrice = this.UnitPrice,
                UnitsInStock = this.UnitsInStock,
                Discontinued = this.Discontinued,
                ETag = "*"
            };
        }
    }

Implementing the Read Function

  1. Under the Read function, open the run.csx file.

  2. Before the initial use, include the following load directive that allows you to use the Model class definition in the actual function.

    #load "product.csx"
  3. Include a reference to the Microsoft.WindowsAzure.Storage and a using configuration for the Table library.

    #r "Microsoft.WindowsAzure.Storage"using Microsoft.WindowsAzure.Storage.Table;
  4. Modify the definition of the Run function method. The newly added inputTable parameter allows you to get and return the products that are stored in the integrated table storage.

    public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IQueryable<Product> inputTable, TraceWriter log)
    {
        if (req.Method == HttpMethod.Get)
        {
            // return the Products table as a list
            return req.CreateResponse(HttpStatusCode.OK, inputTable.ToList(), "application/json");
        }
        else
        {
            return req.CreateResponse(HttpStatusCode.BadRequest, "This route accepts only GET requests.");
        }
    }

Implementing the Create, Destroy, and Update Functions

Now you can proceed with the implementation of the other three functions.

  1. Make all three of them load the Product class and refer the Microsoft.WindowsAzure.Storage and Newtonsoft.Json assemblies.

  2. Add the respective using configurations. As a result, the Run methods for each function differ.

    #r "Newtonsoft.Json"
    #r "Microsoft.WindowsAzure.Storage"
    #load "..\Read\product.csx"
    
    using System.Net;
    using Microsoft.WindowsAzure.Storage.Table;
    using Newtonsoft.Json;

The following example demonstrates the Run method for the Create function.

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, CloudTable outputTable, TraceWriter log)
{
    dynamic body = await req.Content.ReadAsStringAsync();
    Product data = JsonConvert.DeserializeObject<Product>(body as string);
    Product entity = data.ToEntity();
    string newKey = Guid.NewGuid().ToString();

    entity.RowKey = newKey;
    var operation = TableOperation.Insert(entity);
    await outputTable.ExecuteAsync(operation);

    return req.CreateResponse(HttpStatusCode.OK, entity, "application/json");
}

The following example demonstrates the Run method for the Destroy function.

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, CloudTable outputTable, TraceWriter log)
{
    dynamic body = await req.Content.ReadAsStringAsync();
    Product data = JsonConvert.DeserializeObject<Product>(body as string);
    Product entity = data.ToEntity();

    var operation = TableOperation.Delete(entity);
    await outputTable.ExecuteAsync(operation);

    return req.CreateResponse(HttpStatusCode.OK, entity, "application/json");
}

The following example demonstrates the Run method for the Update function.

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, CloudTable outputTable, TraceWriter log)
{
    dynamic body = await req.Content.ReadAsStringAsync();
    Product data = JsonConvert.DeserializeObject<Product>(body as string);
    Product entity = data.ToEntity();

    var operation = TableOperation.Replace(entity);
    await outputTable.ExecuteAsync(operation);

    return req.CreateResponse(HttpStatusCode.OK, entity, "application/json");
}

Configuring the Application

As the implementation is already in place, now you need to add specific configurations to the application and for each of the four functions.

  1. Click the application name and select Platform features.

  2. Under the API section, click CORS.

    Image 10: The platform features of the application Kendo UI for Angular - Cloud Integration - Application platform features in Azure

  3. Add the domain origin of the client-side application that will consume the functions data. Click Save.

  4. Go to the Read function and open the function.json file.

  5. In the bindings / methods section, remove post as an option.

  6. Open the same file for the other three functions. Remove the get method.

Consuming the Implemented CRUD Endpoints on the Client

  1. To get the unique URL for each of the functions, click the URL link of the Get function that is available for each of them.
  2. Implement the client that will consume the data from the functions application.

The following example demonstrates how to implement the Kendo UI Grid and the Angular data service, and place the proper endpoints of the CRUD operations in the service configuration.

import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { catchError, switchMap } from "rxjs/operators";
import { Observable } from "rxjs/Observable";

@Injectable()
export class DataService {
    //
    // change https://your-application-name.azurewebsites.net/ to the base function URL of your application
    //
    private functionUrl = "https://your-application-name.azurewebsites.net/api/";
    private code = "XXXX";

    constructor(private httpClient: HttpClient) { }

    public read(): Observable<any> {
        return this.httpClient
            .get(`${this.functionUrl}READ?code=/${this.code}`);
    }

    public operation(action: string, item: any): Observable<any[]> {
        return this.httpClient
            .post(`${this.functionUrl}${action}?code=/${this.code}`, JSON.stringify(item))
            .pipe(
                switchMap(r => this.read())
            );
    }
}

The following example demonstrates how to bind the Grid. As a result, the Grid will be able to consume and edit the data from the Azure Functions application.

import { Component } from "@angular/core";
import { DataService } from "./data.service";
import { FormGroup, FormControl, Validators } from "@angular/forms";

@Component({
  selector: "app-root",
  template: `
    <kendo-grid
      [kendoGridBinding]="products"
      [pageable]="true"
      [sortable]="true"
      [filterable]="true"
      [pageSize]="10"(edit)="editHandler($event)" (cancel)="cancelHandler($event)"
      (save)="saveHandler($event)" (remove)="removeHandler($event)"
      (add)="addHandler($event)">
      <ng-template kendoGridToolbarTemplate>
        <button kendoGridAddCommand>Add new</button>
      </ng-template>
      <kendo-grid-column field="ProductName" title="Product Name"></kendo-grid-column>
      <kendo-grid-column field="UnitPrice" editor="numeric" title="Unit Price" filter="numeric"></kendo-grid-column>
      <kendo-grid-column field="UnitsInStock" editor="numeric" title="In Stock" filter="numeric"></kendo-grid-column>
      <kendo-grid-column field="Discontinued" editor="boolean" title="Discontinued" filter="boolean"></kendo-grid-column>
      <kendo-grid-command-column>
        <ng-template kendoGridCellTemplate let-isNew="isNew">
          <button kendoGridEditCommand [primary]="true">Edit</button>
          <button kendoGridRemoveCommand>Remove</button>
          <button kendoGridSaveCommand [disabled]="formGroup?.invalid">{{ isNew ? 'Add' : 'Update' }}</button>
          <button kendoGridCancelCommand>{{ isNew ? 'Discard changes' : 'Cancel' }}</button>
        </ng-template>
      </kendo-grid-command-column>
    </kendo-grid>
  `
})
export class AppComponent {
  public products;
  public formGroup: FormGroup;
  private editedRowIndex: number;
  constructor(private service: DataService) {
    service.read().subscribe(res => this.products = res);
  }

  public addHandler({sender}) {
    this.closeEditor(sender);

    this.formGroup = new FormGroup({
        "ProductName": new FormControl("", Validators.required),
        "UnitPrice": new FormControl(0),
        "UnitsInStock": new FormControl(0),
        "Discontinued": new FormControl(false)
    });

    sender.addRow(this.formGroup);
  }

  public editHandler({sender, rowIndex, dataItem}): void {
      this.closeEditor(sender);

      this.formGroup = new FormGroup({
        "ProductName": new FormControl(dataItem.ProductName, Validators.required),
        "UnitPrice": new FormControl(dataItem.UnitPrice),
        "UnitsInStock": new FormControl(dataItem.UnitsInStock),
        "Discontinued": new FormControl(dataItem.Discontinued)
    });

      this.editedRowIndex = rowIndex;

      sender.editRow(rowIndex, this.formGroup);
  }

  public cancelHandler({sender, rowIndex}) {
      this.closeEditor(sender, rowIndex);
  }

  public saveHandler({sender, rowIndex, formGroup, dataItem, isNew}) {
    const item = isNew ? {}: dataItem;
    this.service
      .operation(isNew ? "CREATE": "UPDATE", Object.assign(item, formGroup.value))
      .subscribe(items => this.products = items);
    sender.closeRow(rowIndex);
  }

  public removeHandler({dataItem}) {
    this.service.operation("DESTROY", dataItem).subscribe(items => this.products = items);
  }

  private closeEditor(grid, rowIndex = this.editedRowIndex): void {
      grid.closeRow(rowIndex);
      this.editedRowIndex = undefined;
      this.formGroup = undefined;
  }
}