Skip to content
Frederik Prijck

AUTHOR

Frederik Prijck

Full Stack Engineer

Senior Software Engineer, This Dot 9 years of professional software development experience Open source enthusiast

Select...
Select...
Blog placeholder image

Continuously Deploying Angular to Azure Storage with Azure DevOps

Introduction In a previous article, we saw how to use Azure DevOps to deploy an Angular application to an Azure App Service running .NET Core. Even though an Angular application works perfectly on an Azure App Service, we don't need a .NET runtime (or any other runtime such as nodejs, Ruby, PHP, ...) to host our Angular application if it's not using Server Side Rendering. If you're using Server Side Rendering, you do need a runtime such as nodejs to be able to run a process that's responsible for rendering, and serving, the Angular application. In this article, we're deploying an application that doesn't use Server Side Rendering, so we have no need for such a runtime. An Azure Storage Account is mostly used for storing blobs, and files, but it also contains a queue, and a NoSQL database with which you can integrate. Apart from the above features, Azure Storage Account provides a way to host a static (HTML, CSS, JavaScript, and other assets) site, such as an Angular application. The hosting is free. You're only being billed for the blob storage taken by the files of your application. You can find more details on Azure Storage Blob pricing at https://azure.microsoft.com/en-us/pricing/details/storage/blobs/. Creating a Storage Account We can create a Storage Account through the Azure Portal; ensure you have an active subscription to which we can add the Storage Account. You can create one (here if you don't have one yet. Make sure you choose a Free Trial subscription in order to not be charged. A Free Trial shouldn't be used for production). Inside the Azure Portal, navigate to All Services, and search for Storage accounts. Selecting Storage accounts from the search results will bring you to the Storage accounts overview. Click "add" to create a new Storage Account, and fill in the required information in the Basics tab: Select the subscription and resource group in which we want to create the storage account, and provide it with a name (the name has to be unique across all storage accounts in Azure). The other options can be left untouched. You can find more information about these configuration settings at https://docs.microsoft.com/en-us/azure/storage/common/storage-introduction#types-of-storage-accounts. We don't need to change anything in the other tabs (Networking, Advanced and Tags). Go ahead and create the Storage Account. Azure should navigate you to the deployment details, showing the progress of the deployment. Once you have deployed, you should be able to click "Go to resource" in order to go to the created Storage Account. As mentioned before, there are different ways we can make use of an Azure Storage. We're not going to cover the details of any of its features, other than hosting a static website. Configuring a Static Website You can access the configuration regarding static websites from the Storage Account's side-menu (Settings ⇒ Static website) As static website hosting is disabled by default, we need to enable it in order to be able to host a static website inside a Storage Account. Once enabled, we need to provide an index document name and, optionally, an error document path. The error document path should point to an HTML file that will be rendered when a request to Azure Storage returns a 404. Currently, we don't have such a file, so let's keep it empty for this article. Once saved, you should see a primary and secondary endpoint being listed. These are the URLs on which the application, once deployed, will be available. Apart from the URL, you'll also see that a container, named $web, has been created to host the static website. As we haven't deployed any files yet, trying to open any of the endpoint URL's in your favorite browser will show the default 404 message (this is the message that would be replaced with an HTML file if we had configured an Error document path). Configure a Release Pipeline for a Storage Account We'll be creating a release pipeline that will use the artifacts published as part of the build pipeline that was configured in https://dev.to/thisdotmedia/continuously-integrating-angular-with-azure-devops-2k9l. This article assumes that the Angular application that was used in the above article has been updated to include environmental configuration as described in https://dev.to/thisdotmedia/runtime-environment-configuration-with-angular-4f5j. When inside an Azure DevOps project, create a new Release Pipeline by selecting Pipeline ⇒ Releases ⇒ New ⇒ New Release Pipeline We're not going to use a template for our release pipeline, so you can choose to start with an empty job instead of selecting a preconfigured template. As Azure DevOps supports multiple stages, you can provide a name for the stage that's created by default, or you can keep Stage 1 as its name. Adding build artifacts Let's start by adding the artifacts that we want to deploy to Azure Storage. Click Add an Artifact, select the Build source type (as the artifacts are stored within the build pipeline), and select the appropriate build pipeline from the source dropdown. You can keep the default values for both the version to deploy, as we always want to deploy the latest version, and the source alias. The source alias is the folder that will be used to download the artifacts. We will be using this value throughout the next steps when manipulating, and uploading the artifacts. Adding deployment tasks As we have started with an empty job, there are no tasks defined yet. We'll need to add a task to deploy the artifact's files to an Azure Storage Account. To do so, we can make use of the integrated Azure file copy task by going to the tasks section, clicking the '+' on the Agent job, searching for Azure file copy, and clicking add. When adding an Azure file copy task, you will need to provide the required information in order for Azure DevOps to know what files have to be deployed to which destination. This includes both the Azure Subscription, and the Storage Account information. As we're going to deploy the build artifacts, which are configured to be available inside the release definition, we'll need to provide the path to the source that we want to deploy. You can navigate the artifacts, and select the appropriate folder, by clicking the ... button. Once we have configured the files that should be deployed, select your Azure Subscription, select Azure Blob as the Destination Type, choose the appropriate Storage Account that you want to use, and provide $web for the container name (this is the default container name used by Azure DevOps when enabling static websites inside an Azure Storage Account). Environment specific configuration As mentioned in the previous article (https://dev.to/thisdotmedia/runtime-environment-configuration-with-angular-4f5j), we're not making use of the built-in Angular environment system in order for our application to make use of environment-specific configuration. As we want to avoid rebuilding the application in order for it to use environment-specific configuration, we're using a JSON file that's served as part of the assets directory, and is retrieved in our Angular application using an Http Request. You can find a detailed overview on how to configure this in the article mentioned above. We will need a File transform task to modify the contents of the config.json file as part of our release pipeline's stage (in case of multiple stages, every stage can have its own step, and can replace the contents with different values before deploying). Add the task, and configure it to target the assets/config.json file inside our web-app artifact. Ensure this task is executed before deploying the files to Azure Storage. In order for Azure DevOps to know what names and values it has to use while transforming the config file, we will need to create release pipeline variables. The file transform task will use all the variables, and update values based on its name. All we need for now is for an apiUrl to be configurable for each environment, so we'll need an apiUrl pipeline variable (I went with localhost locally and https://www.thisdot.co for the environment): Continuous Deployment Trigger Even though we could save the configuration, and create manual releases at this point, in order to continuously deploy our artifacts, we'll need to set up a trigger. Click on the lightning strike symbol that's showing up on your artifact in the pipeline section, enable the Continuous deployment trigger, and add a branch filter for the branch you want to deploy (which is master in this case). We don't need to enable the Pull request trigger for this article. You can save the release pipeline, and create a new release, either by running a new build, and making use of the continuous deployment trigger, or by manually creating a new release. Once the deployment is finished, navigating to one of the Azure Storage endpoint URL's should show the Angular application, including the correct environment configuration being logged to the console. Conclusion Using an Azure Storage Account to host a static web application doesn't make deploying any different from deploying to an Azure App Service. However, when we're not making use of any server-side rendering technology, using a runtime such as .NET/PHP/Java is unnecessary, so we have no need to use an App Service. Making use of Azure Storage lowers the costs since hosting itself is free, and you're only billed for the blob storage taken by the application's files. Even though App Services has free variants, once you start adding things such as custom domains, you'll need to use one of the paid variants. The cost of these are generally higher than that of an Azure Storage Account if all you're hosting is a static website....

Blog placeholder image

Runtime environment configuration with Angular

When planning to deploy an Angular application, you might have the need for some configuration that can be different for every environment that's hosting the application. Even when you are only deploying to a single environment, the configuration can be different from the one used for local development. Often, a frontend application needs to communicate with a backend, whose URL can be different for each environment. In this article, we'll modify an Angular application to make use of a configurable API URL in such a way that it can differ for each environment to which you're deploying. Angular environments Angular has a built-in environment system that allows you to specify configuration for multiple environments. When building the application with a given target environment, Angular CLI will replace the environment.ts file with the content of the environment-specific environment file (e.g. environment.qa.ts, environment.prod.ts). Even though this works quite well, the downside of this is that we need to recompile and reupload the artifacts for each environment to which we are planning to deploy. As the idea behind an Azure DevOps release pipeline is to use a single build artifact, and deploy them to, theoretically, an endless amount of environments, this approach doesn't work well with the way we're setting up Continuous Deployment. Runtime environment configuration As we need to be able to deploy our application to multiple environments using a different configuration without recompiling the application for a specific environment, we will need to have some kind of runtime configuration. This means we'll need to swap out the environment configuration after the artifacts are built. One way we can do this in an Angular application is by including a config.json file in the assets directory that contains the configuration settings. Including the JSON file in the assets directory ensures it's being copied to the dist folder when running ng build without the need to make any changes to the angular.json file. Go ahead and create the config.json file using a single apiUrl property: ` We can load the config file as part of an APP_INITIALIZER, ensuring the application isn't started before the config file is loaded. Before we can include an actual APP_INITIALIZER, we will create a service that's responsible for fetching the config.json file using Angular's HttpClient. ` Once we added the above service to an Angular application, we can hook it into Angular's APP_INITIALIZER: ` Wherever we need access to the environment-specific configuration, we can inject the ConfigService, and access the config property. ` In the above component, we're injecting the ConfigService in order to log the entire config object to the console. This should allow us to inspect whether or not the configuration has been set correctly for any environment. In a real application, you probably need to inject the config in the services that are responsible for calling that environment-specific API, and use the API URL to build the endpoint URL. Replacing the configuration file Now that we have everything in place to include configuration at runtime using a JSON file, all that's left to do is to replace that file with the environment-specific configuration, and it will be picked up when running the application. One way could be to include multiple environment configuration files in source control, (config.qa.json, config.prod.json, ...) and swap them during deployment. However, I prefer not to add a separate config file for each environment to which we're deploying. Instead, I think it's a good idea to use a single file, and update its content as part of an automated release pipeline. This allows for a separation between the code-base, and the different number of environments to which it's being deployed....