Skip to content

Static Angular App on Azure Blob Storage

This post demonstrates how to set up and deploy a static Angular app to Azure Blob Storage. We will use a package called ng-deployazure to automate the deployment. The starter Angular app will be connected to a back-end API, and the Leaflet library will be used to visualize the data on a map.

Prerequisites

You will need these installed before we begin:

  • Node.js (12.16.2 in this tutorial)
  • Angular CLI (10.0.7 in this tutorial)
  • Azure Account
  • Azure Storage Account

Create an Example Angular App

Follow the basic instructions to generate an app with the angular cli:

ng new example-angular-app

When prompted if you want routing, choose yes.

This process generates all of the boilerplate needed for a relatively production-ready angular site.

Generating a Data Service With Angular CLI

To start, let’s create some mock GeoJSON data to represent the result of our API call:

export const DATA = {
  "type":"FeatureCollection","features":
  [
    {
      "type":"Feature",
      "id":"48",
      "properties":
        {
          "name":"Texas",
          "density":98.07
        },
      "geometry":
        {
          "type":"Polygon",
          "coordinates": [[
              [-103.0517578125,31.98944183792288],[-106.61132812499999,31.98944183792288],
              [-106.5673828125,31.765537409484374],[-104.69970703125,30.278044377800153],
              [-104.21630859375,29.439597566602902],[-103.07373046875,28.9600886880068],
              [-102.7001953125,29.783449456820605],[-101.40380859375,29.783449456820605],
              [-99.51416015625,27.605670826465445],[-99.052734375,26.41155054662258],
              [-97.119140625,25.997549919572112],[-97.3828125,27.15692045688088],
              [-96.85546875,28.188243641850313],[-93.8671875,29.726222319395504],
              [-93.55957031249999,31.071755902820133],[-94.02099609375,31.98944183792288],
              [-94.04296874999999,33.578014746143985],[-96.6796875,33.8339199536547],
              [-99.97558593749999,34.57895241036948],[-99.99755859375,36.474306755095235],
              [-103.0517578125,36.474306755095235],[-103.0517578125,31.98944183792288]]]
        }
    },
  ]
}

We will use angular cli to scaffold a data service to communicate with our back-end

ng generate service data

Two files will be created in the src/app/ directory: data.service.spec.ts and data.service.ts.

Update data.service.ts to return an Observable of our mock data:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { DATA } from './mock-data';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor() { }

  getData(): Observable<Object> {
    return of(DATA);
  }
}

Make sure to update app.module.ts to register our new data service as a provider:

@NgModule({
  declarations: [
    AppComponent,
    MainComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule
  ],
  providers: [DataService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Generate a Component to Visualize the Data

ng generate component main

The CLI generates a bunch of new files:

Inject our newly created data service into our new component’s constructor and add a stub subscription to it:

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {

  constructor(private dataService: DataService) {
  }

  ngOnInit(){
    this.dataService.getData().subscribe(data => {
      console.log("Data from service and in scope within main component: ", data);
    });
  }
}

Update app-routing.module.ts with a new route to point to our main component:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MainComponent } from './main/main.component';

const routes: Routes = [
  {path: '', component: MainComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Build and run the app, testing that our print statements from the MainComponent are returning the data from our data-service:

ng serve

Hooking up Leaflet to Visualize the Data

We will use my favorite open-source library for map stuff called Leaflet. I normally start the process of integrating a new angular component by searching for an example on Stackblitz. Here is a good example of that for Leaflet.

Here are the steps to integrate a simple leaflet display in our app:

Install Leaflet

npm install leaflet
npm install @asymmetrik/ngx-leaflet
npm install --save-dev @types/leaflet

Update your app.module.ts to hook-up our two new modules

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { LeafletModule } from '@asymmetrik/ngx-leaflet';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DataService } from './data.service';
import { MainComponent } from './main/main.component';

@NgModule({
  declarations: [
    AppComponent,
    MainComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    LeafletModule.forRoot()
  ],
  providers: [DataService],
  bootstrap: [AppComponent]
})
export class AppModule { }

And add Leaflet’s CSS to our global styles defined in src/styles.css

/* You can add global styles to this file, and also import other style files */
@import "~leaflet/dist/leaflet.css";
/* You can add global styles to this file, and also import other style files */
@import "~leaflet/dist/leaflet.css";

And add some CSS to our main component to define the map style in main.component.css:

#leaflet {
    height: 100%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
}

With Leaflet integrated into our app, let’s add the appropriate imports and variables to the top of our MainComponent to hold the map state and config:

import { Component, OnInit } from '@angular/core';
import * as L from 'leaflet';
import { DataService } from '../data.service';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {

  // leaflet map's state and config
  map;
  options = {
    layers: [L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')],
    zoom: 6,
    center: L.latLng(31.392185,-99.170506)
  };

Add our map component to the template for MainComponent (main.component.html):

<div id="leaflet"
leaflet 
[leafletOptions]="options"
(leafletMapReady)="onMapReady($event)">
</div>

Add an implementation of onMapReady() to initialize the map when it has loaded. We will use this hook to request our data from our data service, which will in turn invoke a new onDataLoaded() function.

onMapReady() implementation:

  // called when the Leaflet map is loaded
  onMapReady(map){

    //create the map
    this.map = map;

    console.log("Map Ready!");
    this.dataService.getData().subscribe(data => {this.onDataLoaded(data)})
  }

onDataLoaded() implementation:

  // called when the data has been loaded
  onDataLoaded(data){
    this.feature = L.geoJSON(data, {
      style: {weight: 1, color: 'blue', fillOpacity: 0.5},
    }).addTo(this.map);
  }

Leaflet should be ready to test out. Run the development server and look in the browser:

ng serve
This image has an empty alt attribute; its file name is image-17-1024x728.png

If you’re seeing the great state of Texas, everything is working!

Hooking Up a Production Data Service

We are now ready to hook this thing up to a real geoJSON data-service!

I have made another post describing the steps to set up a Basic Node.js Data Service on Azure. I have deployed an example service following that tutorial which serves geojson for all states in the USA.

We need to do a few more things to retrieve data from this service and display it on our leaflet map.

First, we need to set the development environment to point to localhost:8080:

environment.ts

export const environment = {
  production: false,
  geojson_service_url: "https://localhost:8080"
};

To communicate with a back-end data-service, we need to utilize HttpClientModule, which must be added to our app.module.ts:

... 
 imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    LeafletModule.forRoot()
  ]
...

Next, update src/app/data.service.ts to inject HttpClient and use it to make a request to our data service:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(
    private http: HttpClient
  ) { }

  getData(): Observable<Object> {
    return this.http.get<Object>(environment.geojson_service_url + "/states/");
  }
}

Running everything locally for development (Optional):

In a previous post, we built an example data service to serve the US States in GeoJson format. We will build this project from its Github repository:

git clone https://github.com/PaulFenton/example-data-service.git
cd example-data-service
npm install
npm run dev

With the back-end running locally in development mode, we can navigate back to our angular app and start our Angular development server:

ng serve

Navigating to /localhost:4200 on our browser:


Deploy our App to Azure

To run the back-end on Azure, follow the steps outlined in my previous post. Otherwise, you can use the endpoint I created until it is shut down. To connect to the production endpoint, add the following environment file called environment.prod.ts:

export const environment = {
  production: true,
  geojson_service_url: "https://example-data-service.azurewebsites.net"
};

The configurations inside angular.json at the root of your project will tell angular to use this environment file for the production build and deployment.

Utilizing the ng-deploy-azure Package

We will use an open-sourced package backed by Microsoft called ng-deploy-azure to do the heavy lifting of using the azure cli to provision storage and configure it for static hosting. Before you start you will need an azure account.

Run the following steps from their Quick-start:

ng add @azure/ng-deploy

If prompted, authenticate into Azure with the link & code provided in the output.

ng run example-angular-app:deploy

Click the link and there we have it:

You can see the version I deployed here.


You can find the code for the finished product here on Github.

This concludes the starter tutorial for angular. We can use this project as a building block in future tutorials!