Click here to Skip to main content
15,878,852 members
Articles / Web Development / HTML
Article

Angular2 + CData API Server: Data-Driven Dynamic Web Apps

10 Feb 2017CPOL9 min read 17.4K   3  
Use the CData Software API Server to create an OData service for your databases and build a dynamic Web app based on the database data.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Angular2 is an updated framework for dynamic Web apps, built upon and expanding principles of Angular JS. The CData API Server lets you generate a REST API for your databases, both on-premises and cloud-based. This article will walk through setting up the CData API Server to create a REST API for a SQLite database and creating a simple single-page application (SPA) that has live access to database data. The SPA will dynamically build and populate an HTML table based on the database data. While the article steps through most of the code, you can download the sample Angular2 project and SQLite database to see the full source code and test the functionality for yourself.

Setting Up the API Server

If you have not already done so, you will need to download the CData API Server. Once you have installed the API Server, you will need to run the application, configure the driver to connect to your data (the instructions in this article are for the included sample database), and then configure the driver to create a REST API for any tables you wish to access in your SPA.

Enable CORS

If the Angular2 Web app and API Server are on different domains, then Angular2 will generate cross-domain requests. This means that CORS (cross-origin resource sharing) must be enabled on any servers queried by Angular2 Web apps. We can enable CORS for the API Server by navigating to the Server tab in of the SETTINGS page of the API Server. You will need to adjust the following settings:

  • Click the checkbox to "Enable cross-origin resource sharing (CORS)".
  • Either click the checkbox to "Allow all domains without '*'" or specify the domain(s) that are allowed to connect in Access-Control-Allow-Origin.
  • Set Access-Control-Allow-Methods to "GET,PUT,POST,OPTIONS".
  • Set Access-Control-Allow-Headers to "authorization".
  • Click Save Changes.

Configure Your Database Connection

To configure the API Server to connect to your database, you will need to navigate to the Connections tab on the SETTINGS page. Once there, click Add Connection. For this article, we will connect to a SQLite database. When you configure the connection, you can name your connection, select SQLite as the database, and fill in the Database field with the full path to your SQLite database (the included database is chinook.db from the SQLite Tutorial).

Image 1

Configure a User

Next, create a user to access your database data through the API Server. You can add and configure users on the Users tab of the SETTINGS page. Since we are only creating a simple SPA for viewing data, we will create a user that has read-only access. Click Add, give the user a name, select GET for the Privileges, and click Save Changes.

Image 2

As you can see in the screenshots, we already had a user configured with read and write access. For this article, we will access the API Server with the read-only user, using the associated authtoken.

Image 3

Accessing Tables

Having created a user, we are ready to enable access to the database tables. To enable tables, click the Add Resources button on the Resources tab of the SETTINGS page. Select the data connection you wish to access and click Next. With the connection selected, you can begin enabling resources by clicking on a table name and clicking Next. You will need to add resources one table at a time. In this example, we enabled all of the tables.

Image 4

Sample URLs for the REST API

Having configured a connection to the database, created a user, and added resources to the API Server, we now have an easily-accessible REST API based on the OData protocol for those resources. Below, you will see a list of tables and the URLs to access them. For information on accessing the tables, you can navigate to the API page for the API Server. For the URLs, you will need the address and port of the API Server. Since we are working with Angular2, we will append the @json parameter to the end of URLs that do not return JSON data by default.

TableURL
Entity (table) Listhttp://address:port/api.rsc/
Metadata for table albumshttp://address:port/api.rsc/albums/$metadata?@json
albums datahttp://address:port/api.rsc/albums

As with standard OData feeds, if you wish to limit the fields returned, you can add a $select parameter to the query, along with other standard URL parameters, such as $filter, $orderby, $skip, and $top.

Building a Single Page Application

With the API Server setup completed, we are ready to build our SPA. We will walk through the source files for the SPA contained in the .zip file, making note of any relevant sections of code as we go along. Several of the source files are based loosely on the Angular2 tutorial from angular.io.

index.html

This is the home page of our SPA and the source code mainly consists of script elements to import the necessary Angular2 libraries.

app/main.ts

This TypeScript file is used to bootstrap the App.

app/rxjs-extensions.ts

This TypeScript file is used to import the necessary Observable extensions and operators.

app/app.module.ts

This TypeScript file is used to create a class that can be used in other files to import the necessary modules to create and run our SPA.

app/app.component.css

This file creates CSS rulesets to modify the h1, h2, th, and td elements in our HTML.

app/app.component.html

This file is the template for our SPA. The template consists of a title, a drop-down to select an available table, a drop-down to (multi) select columns in the table to be displayed, a button to retrieve the data, and a table for the data. Different sections are enabled/disabled based on criteria in *ngIf directives and the menus and table are built dynamically based on the results of calls to the API Server, using the *ngFor directive to loop through the returned data.

All of the calls to the API Server and assignment of values to variables are made in the AppComponent and AppService classes.

HTML
<h1>{{title}}</h1>
<br>
<label>Select a Table</label>
<br>
<select [(ngModel)]="selectedTable" (change)="tableChanged()">
  <option *ngFor="let sel_table of availableTables" [value]="sel_table">{{sel_table}}</option>
</select>
<br>
<br>
<label>Select Columns</label>
<br>
<select *ngIf="selectedTable" [(ngModel)]="selectedColumns" (change)="columnsChanged()" multiple>
  <option *ngFor="let sel_column of availableColumns" [value]="sel_column">{{sel_column}}</option>
</select>
<br>
<br>
<button *ngIf="selectedTable && selectedColumns" (click)="dataButtonClicked()">Get [{{selectedTable}}] Data</button>
<br>
<br>
<table *ngIf="selectedTable && selectedColumns">
  <thead>
  <tr>
    <th *ngFor="let column of selectedColumns">{{ column }}</th>
  </tr>
  </thead>
  <tbody>
  <tr *ngFor="let row of tableData">
    <td *ngFor="let column of selectedColumns">{{ row[column] }}</td>
  </tr>
  </tbody>
</table>

app/app.service.ts

This TypeScript file builds the service for retrieving data from the API Server. In it, we have functions for retrieving the list of tables, retrieving the list of columns for a specific table, and retrieving data from a table. We also have a class that represents the metadata of a table as returned by the API Server.

API_Table

The metadata returned by the API Server for a table includes the table's name, kind, and URL. We only use the name field, but pass the entire object in the event that we need the other information if we decide to build upon our SPA.

JavaScript
export class API_Table {
  name: string;
  kind: string;
  url: string;
}

constructor()

In the constructor, we create a private instance of the Http class and set the Authorization HTTP header based on the user/authtoken credentials for the user we created earlier. We then include this header in our HTTP requests.

JavaScript
constructor(private http: Http) {
  this.headers.append('Authorization', 'Basic ' + btoa(this.userName+":"+this.authToken));
}

getTables()

This function returns a list of the tables. The list is retrieved from the API Server by making an HTTP GET request, including the Authorization header, to the base URL for the API Server: http://localhost:8153/api.rsc

JavaScript
getTables(): Promise<API_Table[]> {
  return this.http.get(this.baseUrl, {headers: this.headers})
    .toPromise()
    .then(response => response.json().value )
    .catch(this.handleError);
}

getColumns()

This function returns a list of columns for the table specified by tableName. Since the $metadata endpoint returns XML formatted data by default, we pass the @json parameter in the URL to ensure that we get JSON data back from the API Server. Once we have the JSON data, we can drill down to retrieve the list of column names.

JavaScript
getColumns(tableName: string): Promise<string[]> {
  return this.http.get(`${this.baseUrl}/${tableName}/$metadata?@json`, {headers: this.headers})
    .toPromise()
    .then(response => response = response.json().items[0]["odata:cname"] )
    .catch(this.handleError);
}

getTableData()

This function returns the rows of data for the specified table and columns. We pass the tableName in the URL and then pass the list of columns (a comma-separated string) as the value of the $select URL parameter.

JavaScript
getTableData(tableName:string, columnList: string): Promise<Object[]> {
  return this.http.get(`${this.baseUrl}/${tableName}/?$select=${columnList}`, {headers: this.headers})
    .toPromise()
    .then(response => response = response.json().value )
    .catch(this.handleError);
}

app/app.component.ts

In this TypeScript file, we have defined the functions that react to the events in the SPA; within these functions, we call the functions from the AppService and use the results to populate the various elements of the SPA. These functions are fairly straightforward, assigning values to the different variables as necessary.

ngOnInit()

In this function, we call the getTables function from our AppService. Since getTables returns the raw data objects from our API Server table query, we need to push only the name field from each result into the array of available tables and not push the entire object.

JavaScript
ngOnInit(): void {
  this.appService
    .getTables()
    .then( tables => {
      for (let tableObj of tables) {
        this.availableTables.push( tableObj.name )
      }
    });
}

tableChanged()

This function is called whenever the user selects a different table from the drop-down menu in the SPA. The function makes a call to the API Server to retrieve the list of columns for the given table, which populates another drop-down menu.

JavaScript
tableChanged(): void {
  this.appService
    .getColumns(this.selectedTable)
    .then( columns => this.availableColumns = columns );

  this.selectedColumns = [];
}

columnsChanged()

This function is called whenever the user changes which columns are selected from the drop-down menu. It simply clears the table data so that we do not display an empty table if the columns selected after the button is clicked are different from those originally selected.

JavaScript
columnsChanged(): void {
  this.tableData = [];
}

dataButtonClicked()

This function serves to join the array of selected columns into a comma-separated string, as required by the $select parameter in an OData query, and pass the table name and list to the getTableData function in the AppService. The resulting data is then used to populate the HTML table.

JavaScript
dataButtonClicked(columnList: string): void {
  columnList = this.selectedColumns.join(',');
  this.appService
    .getTableData( this.selectedTable, columnList )
    .then( data => this.tableData = data );
}

Running the Single Page Application

With our connection to data configured and the source files for the SPA reviewed, we are now ready to run the Single Page Application. You will need to have node.js and npm installed on your machine in order to run the SPA. Included in the sample download is an instance of http-server, which will create a light-weight server on your machine to run the SPA. To start the server, you simply need to run the start.sh script in the root directory of the SPA:

> bash .\start.sh

When the SPA launches, you will see the title and a drop down menu to select a table. The list of tables is retrieved from the API Server and includes all of the tables you added as resources when configuring the API Server.

Image 5

With a table selected, the drop-down, multiselect menu for columns appears, allowing you to select the columns you wish to see in your table. You can see that as you select columns, the table headers appear.

Image 6

Once the table and columns are selected, you can click the Get [table] Data button to retrieve data from your database via the API Server. The HTML table will be populated with data based on the table and columns you selected before clicking on the button.

Image 7

Free Trial & More Information

Now that you have seen a basic example of connecting to your database data in dynamic Web pages, visit our API Server page to read more information about the API Server and download the API Server. Start building dynamic Web pages using live data from your on-premises and cloud-base databases, including SQLite, MySQL, SQL Server, Oracle, and PostgreSQL! As always, our world-class Support Team is ready to answer any questions you may have.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Writer CData Software
United States United States
I'm an educator-turned-technology evangelist, with a short stint as a software developer. In all of the work I've done, data has been critical, and as businesses, industries, and services grow, I can't help but notice the growth in the breadth and depth of data usage. A common interface to data frees enterprises from the burden of connecting to their data and frees them to focus on their own business. By leveraging CData drivers to access common SQL interfaces to more than 100 SaaS, Big Data, and NoSQL sources, developers can build solid, data-driven products and analysts and data scientists can quickly and easily build insights that drive business.

While giving presentations, writing articles, engaging in webinars, and producing tutorial videos I get the opportunity to see first-hand the difference that standard connectivity makes, with regards to both the underlying data sources and the tools and apps consuming the data. Talk to me about partnering with CData to connect to your own organization's data, embedding connectivity into your data-driven solutions or building custom connectors for a new data source.

Comments and Discussions

 
-- There are no messages in this forum --