<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Boosten</title>
    <description>The latest articles on DEV Community by Kevin Boosten (@kevinboosten).</description>
    <link>https://dev.to/kevinboosten</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F278046%2F2ed4362a-2605-4d0a-badd-c46876ed683f.png</url>
      <title>DEV Community: Kevin Boosten</title>
      <link>https://dev.to/kevinboosten</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevinboosten"/>
    <language>en</language>
    <item>
      <title>How I use an OpenAPI spec in my Angular projects</title>
      <dc:creator>Kevin Boosten</dc:creator>
      <pubDate>Thu, 11 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/kevinboosten/how-i-use-an-openapi-spec-in-my-angular-projects-2p7f</link>
      <guid>https://dev.to/kevinboosten/how-i-use-an-openapi-spec-in-my-angular-projects-2p7f</guid>
      <description>&lt;p&gt;If you are working on a project that has an OpenAPI spec, then you can generate your Angular Code and even generate a simulator for development and test purposes. This reduces time and complexity of integrating with a OpenAPI gateway tremendously. Let me show you how I use OpenAPI to boost up my productivity!&lt;/p&gt;

&lt;p&gt;Here is a brief introduction if you’re not familiar with &lt;a href="https://swagger.io/specification/"&gt;OpenAPI&lt;/a&gt; in general:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or maybe you know &lt;a href="https://swagger.io/"&gt;Swagger&lt;/a&gt;, so what’s the difference? Check it out here: &lt;a href="https://swagger.io/blog/api-strategy/difference-between-swagger-and-openapi/"&gt;Differences between Swagger and OpenAPI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenAPI Generator
&lt;/h2&gt;

&lt;p&gt;So your backend colleague gave you the endpoint of their api so you can start integrating your web application with some real data. So what will be your next step? You’re quite a Typescript enthousiast and want to make sure that your web application has some type safety, so you start typing out some TS interfaces that we can use. Okay, check ✅. Next step? Maybe add some abstraction and reusability to your stack? So you create an Angular Service that uses the &lt;a href="https://angular.io/api/common/http/HttpClient"&gt;HttpClient&lt;/a&gt; and so wraps the actual endpoint. Sounds good and eventually this will be a good approach. But it feels a bit repetitive to do this for every project again. Besides that, I think you can spend your time better on building actual features for your application, right?&lt;/p&gt;

&lt;p&gt;So what if we could automate these steps to safe some precious time 🧐? In a few steps we can generate Angular specific code based on our OpenAPI spec. Let’s get started 👨‍💻!&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Angular app
&lt;/h3&gt;

&lt;p&gt;First install the Angular CLI if you dont have this installed already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @angular/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with a new angular app and choose the default options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng new angular-openapi-demo
cd angular-openapi-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the application to verify everything went well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ng serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create an OpenAPI yaml file
&lt;/h3&gt;

&lt;p&gt;A well defined api comes with some documentation. An api built with OpenAPI comes with a yaml, or JSON, spec that describes the actual api. We can build this spec by creating a yaml file in our application. In order to have a real working api, we will use the well known &lt;a href="https://jsonplaceholder.typicode.com/"&gt;JSON Placeholder&lt;/a&gt; public test api.&lt;/p&gt;

&lt;p&gt;Add a file &lt;code&gt;openapi.yaml&lt;/code&gt; to the root of your application and add the following content:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also download the file from the &lt;a href="https://github.com/Boosten/angular-openapi-demo/blob/master/openapi.yaml"&gt;demo repo&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.0.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JSON Placeholder OpenAPI&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Example spec of the well known JSON Placeholder website&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1.9&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://jsonplaceholder.typicode.com&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/posts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Returns a list of Posts.&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Optional extended description in CommonMark or HTML.&lt;/span&gt;
      &lt;span class="na"&gt;operationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GetPosts&lt;/span&gt;
      &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A JSON array of Posts&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;array&lt;/span&gt;
                &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="s"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Post'&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;userId&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;body&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;number&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;record id&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unique user identifier&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;title of this Post&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sunt aut facere repellat provident occaecati excepturi optio reprehenderit&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;description of this post&lt;/span&gt;
          &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think that this kind of documenting is quite self explanatory, but let’s discuss a couple of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Post&lt;/code&gt;: this is a way of defining a model. We can use this model by using the &lt;a href="https://swagger.io/docs/specification/using-ref/"&gt;$ref&lt;/a&gt; keyword.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;servers&lt;/code&gt;: here we define one or more base url’s for our spec.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;example&lt;/code&gt;: give a hint about what value to expect. Later on I will explain you why this is handy when creating a simulator.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip! Make working with OpenApi spec easier in VSCode by installing the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer"&gt;Swagger Viewer extension&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Generate Angular Services
&lt;/h3&gt;

&lt;p&gt;We’re going to generate our Angular code with the CLI of &lt;a href="https://github.com/OpenAPITools/openapi-generator-cli"&gt;openapi-generator&lt;/a&gt;. We can install this via NPM as devDependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i @openapitools/openapi-generator-cli -D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This packages has a lot of &lt;a href="https://openapi-generator.tech/docs/generators"&gt;generators available&lt;/a&gt;, we’re going to use the &lt;a href="https://openapi-generator.tech/docs/generators/typescript-angular"&gt;typescript-angular&lt;/a&gt; one.&lt;/p&gt;

&lt;p&gt;Add a npm script to your &lt;code&gt;package.json&lt;/code&gt; file for more convenient usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "scripts": {
    // other scripts
    "generate:api": "openapi-generator-cli generate -i ./openapi.yaml -g typescript-angular -o src/app/core/api/v1"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the default configuration over here. But you can customize this based on your needs.&lt;/p&gt;

&lt;p&gt;One example could be the option &lt;code&gt;removeOperationIdPrefix&lt;/code&gt; to prevent redundant method names. Take for example the following operationId in your spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;operationId: Posts_GetPosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator will use the operationId to determine the Angular Service name and the method name. If we use the default configuration, our class will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this looks redundant
export class PostsService {
  public postsGetPosts() {}
}

// and when you invoke it, it is redundant and looks weird...
const postsService = new PostsService();
postsService.postsGetPosts();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the arguments &lt;code&gt;-p=removeOperationIdPrefix=true&lt;/code&gt; will remove the &lt;code&gt;Posts_&lt;/code&gt; part of the operationId: &lt;code&gt;Posts_GetPosts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this looks redundant
export class PostsService {
  public getPosts() {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That already looks better to me! As I said, there’re plenty configuration options. And you probably will be using some of them from time to time depending on the spec you receive.&lt;/p&gt;

&lt;p&gt;Next step is to actually generate our code with our custom NPM script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run generate:api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have the following directory structure because we told the generator to output (&lt;code&gt;-o&lt;/code&gt;) to the &lt;code&gt;src/app/core/api/v1&lt;/code&gt; directory:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3ddgJab--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q3ddgJab--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/2.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use generated NgModule and services
&lt;/h3&gt;

&lt;p&gt;The most important parts of the generated code are the following files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;posts.services.ts&lt;/code&gt;: the actual Angular service.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post.ts&lt;/code&gt;: a TS interface that matches the &lt;code&gt;Post&lt;/code&gt; model of our OpenAPI spec.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api.module.ts&lt;/code&gt;: a NgModule that can be imported to your AppModule.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt;: &lt;a href="https://github.com/Boosten/angular-openapi-demo/blob/master/src/app/core/api/v1/README.md"&gt;README&lt;/a&gt; file with usage instructions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add this &lt;code&gt;ApiModule&lt;/code&gt; to your &lt;code&gt;AppModule&lt;/code&gt;. This will use the ‘default’ server endpoint that is available in your openapi.yaml. You can see that in the generated &lt;code&gt;posts.service.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Injectable({
  providedIn: 'root',
})
export class PostsService {
  protected basePath = 'https://jsonplaceholder.typicode.com';
  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// without configuring providers
import { ApiModule } from '';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    ApiModule,
    // make sure to import the HttpClientModule in the AppModule only,
    // see https://github.com/angular/angular/issues/20575
    HttpClientModule,
  ],
  declarations: [AppComponent],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to override or provide a different endpoint, you can do this by passing in a factory function in the &lt;a href="https://angular.io/guide/singleton-services#forRoot"&gt;forRoot&lt;/a&gt; method of the &lt;code&gt;ApiModule&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import {
  ApiModule,
  Configuration,
  ConfigurationParameters,
} from './core/api/v1';

export function apiConfigFactory(): Configuration {
  const params: ConfigurationParameters = {
    basePath: 'https://staging.jsonplaceholder.typicode.com',
  };
  return new Configuration(params);
}

@NgModule({
  imports: [ApiModule.forRoot(apiConfigFactory)],
  declarations: [AppComponent],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can go one step further by moving the basePath to the Angular environment files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// environment.ts
export const environment = {
  production: false,
  basePath: 'https://dev.jsonplaceholder.typicode.com',
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// environment.prod.ts
export const environment = {
  production: true,
  basePath: 'https://jsonplaceholder.typicode.com',
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now we can import the &lt;code&gt;environment.basePath&lt;/code&gt; variable to configure our ApiModule.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are even more variables in the Configuration object that can be set, see &lt;a href="https://github.com/Boosten/angular-openapi-demo/blob/master/src/app/core/api/v1/configuration.ts#L3"&gt;Configuration.ts&lt;/a&gt; on the demo repo.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { NgModule } from '@angular/core';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import {
  ApiModule,
  Configuration,
  ConfigurationParameters,
} from './core/api/v1';

export function apiConfigFactory(): Configuration {
  const params: ConfigurationParameters = {
    basePath: environment.basePath,
  };
  return new Configuration(params);
}

@NgModule({
  imports: [ApiModule.forRoot(apiConfigFactory)],
  declarations: [AppComponent],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage example
&lt;/h3&gt;

&lt;p&gt;We can now start using our generated services in our application! Change your &lt;code&gt;app.component.ts&lt;/code&gt; to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Component } from '@angular/core';
import { PostsService } from './core/api/v1';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  // Create a `cold` observable - we will be subscribing to this observable in the template
  posts$ = this.postService.getPosts();

  // Inject the generated Angular service as a dependency of this class
  constructor(private postService: PostsService) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in your &lt;code&gt;app.component.html&lt;/code&gt; we can use the &lt;code&gt;posts$&lt;/code&gt; variable by subscribing to it with the &lt;a href="https://angular.io/api/common/AsyncPipe"&gt;async&lt;/a&gt; pipe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li *ngFor="let post of posts$ | async as list"&amp;gt;
    &amp;lt;h2&amp;gt; - &amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser should now show you a list of &lt;code&gt;Posts&lt;/code&gt; from JsonPlaceholder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UTxqTmgc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UTxqTmgc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/3.png" alt="Browser example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎉 Done! That was all we needed to do to generate ourselves some Angular services and spare us some time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;In this example I’m generating and putting my code in my project’s repository. That’s fine for most of my projects because we’re using monorepo’s and also using client specific api’s. Another approach could be to publish your generated code as a NPM package that can be installed by others. These steps are also described by the OpenAPI generator itself in the &lt;a href="https://github.com/Boosten/angular-openapi-demo/blob/master/src/app/core/api/v1/README.md"&gt;README&lt;/a&gt;. So it depends on your needs which approach fits better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulator
&lt;/h2&gt;

&lt;p&gt;Now that we've generated our Angular services, let’s have a look how wo can utilize the OpenAPI spec even better in our front-end application stack! What we’re going to use for this is a great package called: &lt;a href="https://github.com/anttiviljami/openapi-backend"&gt;OpenAPI backend&lt;/a&gt; from &lt;a href="https://github.com/anttiviljami"&gt;Viljami Kuosmanen&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As Viljami describes it in one sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;OpenAPI Backend is a Framework-agnostic middleware tool for building beautiful APIs with OpenAPI Specification.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;OpenAPI backend has a couple of useful features, but the feature that we’re going to use is the &lt;a href="https://github.com/anttiviljami/openapi-backend#mocking-api-responses"&gt;auto-mocking responses&lt;/a&gt; behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up simulator project
&lt;/h3&gt;

&lt;p&gt;The simulator project will be a independent project but within your current directory structure and so it will also be part of your git repo. So actually we’re going to create a monorepo: a single repository that contains all our code. I’m an advocate when it comes down to monorepos. If you want to learn more about useful tooling around monorepos, then you certainly should check out the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://lerna.js.org/"&gt;Lerna&lt;/a&gt; - easy to use, quick to set up&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nx.dev/"&gt;NX.dev&lt;/a&gt; - managing monorepos like a pro 😉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m not going to use any of these tools for the sake of this tutorial.&lt;/p&gt;

&lt;p&gt;Let’s get started by creating a directory where our simulator will live and go to the directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir simulator
cd simulator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize a new npm project to generate a &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y # -y will answer the questions with yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i openapi-backend # the actual dependency we need :-)
npm i --save-dev typescript # we will be using Typescript, so transpilation needs to be done
npm i express # To serve our simulated endpoints
npm i --save-dev @types/express # typescript types for express
npm i cors
npm i --save-dev @types/cors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see we’re using Typescript. We need a &lt;code&gt;tsconfig.json&lt;/code&gt; file, you can initialize this with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tsc --init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We used the locally installed typescript compiler by running it via &lt;a href="https://docs.npmjs.com/cli/v7/commands/npx"&gt;npx&lt;/a&gt;. It will run the &lt;code&gt;tsc&lt;/code&gt; bin from your local &lt;code&gt;node_modules&lt;/code&gt; because we installed it as our &lt;code&gt;devDependency&lt;/code&gt;:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Open the generated &lt;code&gt;tsconfig.json&lt;/code&gt; file and configure the output directory. Your file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist", /* Redirect output structure to the directory. */
    "strict": true, /* Enable all strict type-checking options. */
    "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    "skipLibCheck": true, /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re almost there. Update the npm scripts in your &lt;code&gt;package.json&lt;/code&gt; so we can build and run our simulator. Your &lt;code&gt;package.json&lt;/code&gt; should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "simulator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "tsc &amp;amp;&amp;amp; node dist/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "openapi-backend": "^3.9.1"
  },
  "devDependencies": {
    "@types/cors": "^2.8.10",
    "@types/express": "^4.17.11",
    "typescript": "^4.2.3"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the last thing we need to do is to actually create a simulator instance with the openapi-backend package. Do this by adding a file called &lt;code&gt;index.ts&lt;/code&gt; to your &lt;code&gt;simulator&lt;/code&gt; directory and add this content to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import cors from 'cors';
import express from 'express';
import OpenAPIBackend, { Request } from 'openapi-backend';

// Create api with your definition file or object. This points to the openapi yaml spec
const api = new OpenAPIBackend({ definition: '../openapi.yaml' });

// Register your framework specific request handlers here
api.register({
  notFound: (c, req, res) =&amp;gt; res.status(404).json({ err: 'not found' }),
  notImplemented: (c, req, res) =&amp;gt; {
    const { status, mock } = c.api.mockResponseForOperation(
      c.operation.operationId ?? ''
    );
    return res.status(status).json(mock);
  },
});

// Initialize the backend
api.init();

// Initialize the express server that will serve the api backend
const port = 9000;
const app = express();
app.use(express.json());
// Allow cors on all origins - its okay to do this for our simulator
app.use(cors());
app.use((req, res) =&amp;gt; api.handleRequest(req as Request, req, res));
app.listen(port, () =&amp;gt;
  console.info(`api listening at http://localhost:${port}`)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering the &lt;a href="https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#notimplemented-handler"&gt;notImplemented&lt;/a&gt; handler that we will be using to mock the response is the most important part of this configuration. As the documentation states: The notImplemented handler gets called by .handleRequest() if no other Operation Handler has been registered for the matched operation. The &lt;a href="https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#mockresponseforoperationoperationid-opts"&gt;mockResponseForOperation&lt;/a&gt; method will then mock a response based on the available example data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the simulator
&lt;/h3&gt;

&lt;p&gt;Now run &lt;code&gt;npm start&lt;/code&gt; in your &lt;code&gt;simulator&lt;/code&gt; directory and open your browser on &lt;a href="http://localhost:9000/posts"&gt;http://localhost:9000/posts&lt;/a&gt; to see some &lt;code&gt;Posts&lt;/code&gt;🚀! The response should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UnvcYkOn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UnvcYkOn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2021-02-11/4.png" alt="Posts JSON example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So where is this data actually coming from? Do you remember the &lt;code&gt;example&lt;/code&gt; keyword that I mentioned earlier? That’s how we can return some useful response data to our requests. Here you can check out the official documentation about this &lt;a href="https://swagger.io/docs/specification/adding-examples/"&gt;example keyword&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the simulator in our Angular app
&lt;/h3&gt;

&lt;p&gt;Now that we’ve our simulator project configured and running, we can use it in our Angular app. Open the &lt;code&gt;environment.ts&lt;/code&gt; file located at: &lt;code&gt;src/environments/environment.ts&lt;/code&gt; and change the &lt;code&gt;basePath&lt;/code&gt; property to our local running simulator. You file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const environment = {
  production: false,
  basePath: 'http://localhost:9000',
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This is the default environment file that Angular will use when you run the project with &lt;code&gt;npm start&lt;/code&gt; or &lt;code&gt;ng serve&lt;/code&gt; without any configuration parameters. You could add a specific environment configuration. Check out the official docs to learn more: &lt;a href="https://angular.io/guide/build#configure-environment-specific-defaults"&gt;https://angular.io/guide/build#configure-environment-specific-defaults&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you still have your Angular app running, then you should now see a single &lt;code&gt;Post&lt;/code&gt; record being displayed in your browser. Otherwise restart your Angular app by running &lt;code&gt;npm start&lt;/code&gt; in the root of your application. Our Angular application is now running against a local simulator!&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhancing DX (Developer Experience)
&lt;/h3&gt;

&lt;p&gt;Angular applications already utilizing the &lt;code&gt;npm start&lt;/code&gt; command to eventually run &lt;code&gt;ng serve&lt;/code&gt;. We need to make some adjustmenst in order to start our Angular application and simultanously start our simulator. I really appreciate it, and actually expect it, when I only have to run two commands to start the project: &lt;code&gt;npm i &amp;amp;&amp;amp; npm start&lt;/code&gt;. Why? Nobody wants to have some superfluous starter doc that you need to read, handle mentally and do all kinds of set up tasks. To give your project a first good impression it should be ready to use within seconds! I’m not saying you should not have a &lt;code&gt;README.md&lt;/code&gt; file, the opposite! The README file could describe other helpful information that your reader should know (e.g. different configurations).&lt;/p&gt;

&lt;p&gt;So what’s the problem with the repo that we created along this tutorial? As a developer you now need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the Angular app via &lt;code&gt;npm start&lt;/code&gt; in the root of your application&lt;/li&gt;
&lt;li&gt;And start the simulator via &lt;code&gt;npm start&lt;/code&gt; in the subdirectory &lt;code&gt;simulator&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In practice this requires the developer to open two terminal windows/tabs and run the commands. Not a great Developer Experience if you ask me. We can do better!&lt;/p&gt;

&lt;p&gt;So let’s assume that we want to always run against the simulator in the default configuration, so when running the &lt;code&gt;npm start&lt;/code&gt; command. We need to be able to start two tasks in parallel. And maybe you’re already quite familiar with npm, but there’s a package for that called: &lt;a href="https://www.npmjs.com/package/npm-run-all"&gt;npm-run-all&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install it as a &lt;code&gt;devDependency&lt;/code&gt; in the root of our project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install npm-run-all --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open the &lt;code&gt;package.json&lt;/code&gt; of our root project and alter the &lt;code&gt;scripts&lt;/code&gt; section it like this by adding two scripts and changing the &lt;code&gt;start&lt;/code&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"start": "npm-run-all --parallel start:app start:simulator",
"start:app": "ng serve",
"start:simulator": "npm --prefix simulator start",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I guess the scripts do explain themselves, but here’s a brief description:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; will now use the &lt;code&gt;npm-run-all&lt;/code&gt; package to run two other npm scripts. The &lt;code&gt;--parallel&lt;/code&gt; flag will run them in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start:app&lt;/code&gt; will start the Angular application&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start:simulator&lt;/code&gt; will start the simulator. Because its located in a subdirectory, we need to pass the &lt;code&gt;--prefix&lt;/code&gt; argument to npm to point it to the &lt;code&gt;simulator&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running the &lt;code&gt;npm start&lt;/code&gt; command from the root should now start our Angular app + starting the local simulator! Do not forget to update your &lt;code&gt;README.md&lt;/code&gt; file with a sidenote about this behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom handlers
&lt;/h3&gt;

&lt;p&gt;As you might have noticed is that our simulator only returns a single record on an endpoint that could return multiple records. This is fine for some situations, but sometimes you’re developing a new feature that incorporates a list of items, or &lt;code&gt;Posts&lt;/code&gt; in our context. Returning a single &lt;code&gt;Post&lt;/code&gt; will not help you very much if you want to see how your list is working when multiple items are in it. Think about applying staggered animations on lists, filling the available space etc. In this situation the &lt;code&gt;example&lt;/code&gt; data is not sufficient for our use-case. What we can do is providing specific handlers for our Openapi operations. A simple example for our &lt;code&gt;Posts&lt;/code&gt; api is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api.register('GetPosts', function (c, req, res) {
  return res.status(200).json([
    {
      id: 1,
      userId: 1,
      title: 'a',
      body: 'a',
    },
    {
      id: 2,
      userId: 2,
      title: 'b',
      body: 'b',
    },
  ]);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should add this to your &lt;code&gt;simulator/index.ts&lt;/code&gt; file before &lt;code&gt;app.init()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This way we created our very own response for the endpoint that is described in our api spec file! Great for developing and testing purposes if you ask me!&lt;/p&gt;

&lt;p&gt;Check the docs for more info:&lt;a href="https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#registeroperationid-handler"&gt;https://github.com/anttiviljami/openapi-backend/blob/master/DOCS.md#registeroperationid-handler&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you are going to provide or ‘override’ multiple endpoints, make sure that you keep your simulator code maintainable: so splitting up custom handlers in separate files and keeping your index.ts file clean.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The OpenAPI spec is already very helpful to describe your api’s. I showed you how I utilize this spec to generate code in our Angular application and generate a simulator that we can use for development purposes. What I did not describe is how I use this same simulator to act as a api for my integration tests that I run with &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;. Another great use-case for your OpenAPI spec!&lt;/p&gt;

&lt;p&gt;So, what else could you do with it? Let me know!&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>api</category>
      <category>angular</category>
    </item>
    <item>
      <title>How to use Angular environment files in your Azure DevOps multi-stage yml release pipeline</title>
      <dc:creator>Kevin Boosten</dc:creator>
      <pubDate>Sat, 16 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/boostuh1/how-to-use-angular-environment-files-in-your-azure-devops-multi-stage-yml-release-pipeline-37g4</link>
      <guid>https://dev.to/boostuh1/how-to-use-angular-environment-files-in-your-azure-devops-multi-stage-yml-release-pipeline-37g4</guid>
      <description>&lt;p&gt;Angular has the concept of environment files that can be used to configure environment specific values. There are multiple ways to handle environments in your Angular web application.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create an environment specific &lt;code&gt;.ts&lt;/code&gt; file and rebuild the complete application per environment. (e.g. &lt;code&gt;environment.ts&lt;/code&gt;, &lt;code&gt;environment.tst.ts&lt;/code&gt;, &lt;code&gt;environment.stg.ts&lt;/code&gt;, &lt;code&gt;environment.prd.ts&lt;/code&gt;). This works, but has the drawback that we need to rebuild the complete application when we go from staging to production. I don’t think we want that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put your environment settings in a config.json file in your assets directory and request this file at runtime via the HttpClient during the start up. You could use the APP_INITIALIZER to make sure the environment settings are available before your application code is executed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There’re already enough articles about this approach. But it introduces extra code and start up dependencies because we’ve to wait for the config.json file to load over http. Of course this is negligible if we compare it to all the other files, but still it’s another request the browser has to make.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Or….just use the default environment files and make use of replacement tokens!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I’ll show you how to do this in a Azure DevOps multi-stage yml pipeline. So besides a way to handle environments in your Angular app, you’ll also learn something about Azure DevOps multi-stage yml release pipelines!&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with an Angular app
&lt;/h2&gt;

&lt;p&gt;The first thing we need is an minimal Angular app. So let’s start with that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng new azure-angular-release-demo --minimal --routing=false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In your project you will find the default generated &lt;code&gt;environment.ts&lt;/code&gt; and &lt;code&gt;environment.prod.ts&lt;/code&gt; files. Don’t loose them, we’ll need them shortly 😉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u1Cl8LRC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u1Cl8LRC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/1.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Push this repo to your Github or Azure DevOps account so you can use it in Azure DevOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Azure DevOps project
&lt;/h2&gt;

&lt;p&gt;I assume that you’re already in the ecosystem of Microsoft and you’ve got a Organization where you can create a new project. Otherwise consult the &lt;a href="https://docs.microsoft.com/en-us/azure/devops/organizations/projects/create-project?view=azure-devops&amp;amp;tabs=preview-page"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create initial pipeline
&lt;/h2&gt;

&lt;p&gt;Now that we have an Angular app and our project set up, we can configure our pipeline. You can do this via the portal or simply create a file &lt;code&gt;azure-pipelines.yml&lt;/code&gt; in the root of your repository and add the following content to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest"&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, world!&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one-line&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;script"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For this demo I’m starting from the portal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In your DevOps project navigate to Pipelines and select Create Pipeline &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YIDexk-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YIDexk-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/2.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the location where you code is. In this demo I used GitHub. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Jn6RDews--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Jn6RDews--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/3.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your repository &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_RWzlUHO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_RWzlUHO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/4.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the Starter pipeline &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KsjkNRra--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KsjkNRra--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/5.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Eventually we want this in our pipeline to start with &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JtH0fkgq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JtH0fkgq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/6.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save and run your pipeline to see if it works &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SdWzEBey--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SdWzEBey--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/7.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Change to multi-stage pipeline
&lt;/h2&gt;

&lt;p&gt;Multi-stage pipelines are the new way to configure your release via code. We were already using the &lt;code&gt;azure-pipeline.yml&lt;/code&gt; file to define our build steps, but now we can also use it to deploy our application to several environments. The ‘classic’ way to do this in Azure DevOps is by configuring a release via the portal. This configuration will not be saved to your repository.&lt;/p&gt;

&lt;p&gt;Multi-stage pipelines are still in preview, so it’s possible that you have to toggle the preview feature. But at the time of writing I do not see the feature toggle anymore, so maybe it’s not in preview anymore 🎉.&lt;/p&gt;

&lt;p&gt;Check their website for more information about &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/stages?view=azure-devops&amp;amp;tabs=yaml"&gt;multi-stage pipelines&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We are now going to change our starter pipeline to a multi-stage pipeline with three stages. Copy this code to your &lt;code&gt;azure-pipelines.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest"&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, world!&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_dev&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to development&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, world!&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_prod&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, world!&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After committing your changes a new build will start, first thing you will notice are the extra green circles. These are our &lt;em&gt;stages&lt;/em&gt;. We now own three stages, all managed by our yml file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--arpqN1jv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--arpqN1jv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/8.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the detail page of your run you can see some more information to see what is happening per stage. You can even rerun stages if they failed or you want to rerun them with different environment variables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O2abmww9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O2abmww9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/9.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure build stage
&lt;/h2&gt;

&lt;p&gt;We still don’t have anything to deploy other than some echo scripts. &lt;strong&gt;It’s building time 👷🏻‍♂!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the necessary steps to our yml file to build the Angular app. We will install NodeJS, install our project’s npm dependencies and eventually build and publish an optimized production version.&lt;/p&gt;

&lt;p&gt;Change the &lt;em&gt;build&lt;/em&gt; stage of our pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.x"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build -- --prod&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
            &lt;span class="na"&gt;artifact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Commit your changes and let DevOps start a new pipeline run. After the run you can validate the output by checking the produced artifacts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h4acn6o6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h4acn6o6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/10.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AlD-HDZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AlD-HDZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/11.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure deployment stages
&lt;/h2&gt;

&lt;p&gt;Okay, we are now able to set up, configure and build an Angular application via Azure DevOps ✅👏. This is a great moment to grab yourself a coffee or beer, depending on what time it is 😉.Next stop: the deployment stages.&lt;/p&gt;

&lt;p&gt;We are not going to deploy our application to a real server because that goes beyond the scope of this article. Let’s save that for a future article about deploying with ARM templates!&lt;/p&gt;

&lt;p&gt;So, what was the actual topic of this article again🤔? When I started writing this article I wanted to focus primarily on handling environment specific settings in an Angular application with the &lt;code&gt;environment.ts&lt;/code&gt; file. But why not add a real life example to it with some nice additions about Azure multi-stage yml releases too? So you can release your Angular application properly in Azure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change to deployment jobs
&lt;/h3&gt;

&lt;p&gt;We are now going to change the yml file a bit to change our temporary deployment stages to real &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&amp;amp;tabs=schema%2Cparameter-schema#deployment-job"&gt;deployment jobs&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A deployment job is a special type of job. It’s a collection of steps to run sequentially against the environment. In YAML pipelines, we recommend that you put your deployment steps in a deployment job.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_dev&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to development&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployWeb&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, development world!&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_prd&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployWeb&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRD&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, production world!&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Not much changes, just following the documentation from Microsoft. Thing that you will notice is the &lt;em&gt;environment&lt;/em&gt; setting. This is, in our case, just a label to group our deployment and see the history of our deployments. It also gives you Approval configuration. More about that later.Check this &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops"&gt;link&lt;/a&gt; to learn more about environments in DevOps.&lt;/p&gt;

&lt;p&gt;You can now see your environments in the portal too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tDrsC8sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tDrsC8sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/12.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extend environment.ts
&lt;/h3&gt;

&lt;p&gt;So, now we are getting close to our actual end goal: handling environment specific variables in a Angular application via Azure multi-stage pipelines.At this moment our &lt;code&gt;environment.ts&lt;/code&gt; and &lt;code&gt;environment.prod.ts&lt;/code&gt; files don’t contain anything environment specific. Let’s add some dummy environment settings for the sake of this article. We pretend that we have an API that is environment specific and we’re using Auth0 as our authentication provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// environment.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-local-clientid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;local.eu.auth0.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://local.myapi.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// environment.prod.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#{authClientID}#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#{authDomain}#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#{apiEndpoint}#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Okay Kevin, I don’t think &lt;code&gt;#{authClientID}#&lt;/code&gt; is a valid client id. You’re completely right, of course 🙃. These are, so called, replacement tokens. And we’re going to utilize them during our deployment stages to replace the tokens with the actual values.But first, don’t forget to commit and push your changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use environment variables in your code
&lt;/h3&gt;

&lt;p&gt;If we do not use these variables in our code, tree shaking will remove the code from the build. So that would not be very useful for this tutorial.So add this line of code to your &lt;code&gt;app.component.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app.component.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;AppComponent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`is production: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;production&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, auth: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, api: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiEndpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Azure DevOps extension
&lt;/h3&gt;

&lt;p&gt;We now know that we want these tokens to be replaced by the actual values. You could roll your own script to accomplish this, but we’re going to use an extension for this. This extension needs to be installed on organization level, so it could be possible that you need to request this extension to your organization owner. The extension we’re going to install is called &lt;a href="https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens&amp;amp;ssr=false#qna"&gt;Replace Tokens&lt;/a&gt; from &lt;a href="https://twitter.com/grouchon"&gt;Guillaume Rouchon&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add variable groups
&lt;/h3&gt;

&lt;p&gt;With our extension in place, it’s time to add the actual variables that will be used to substitute the tokens.Navigate to the &lt;strong&gt;Library&lt;/strong&gt; section to create two variable groups that contain variables for our two environments: DEV and PRD.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variable groups overview &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--50digPcz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/13.png" alt="img"&gt;
&lt;/li&gt;
&lt;li&gt;Add the variables for DEV &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jsgWx7K3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/14.png" alt="img"&gt;
&lt;/li&gt;
&lt;li&gt;Add the variables for PRD (clone your previous variable group and replace values) &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DQm-OIV6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/15.png" alt="img"&gt;
&lt;/li&gt;
&lt;li&gt;Eventually, you should now have two groups &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tAieB0v9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/16.png" alt="img"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reference your variable groups in your pipeline
&lt;/h3&gt;

&lt;p&gt;We can now use these variables groups in our yml pipeline. Add the &lt;em&gt;variables&lt;/em&gt; property to each deployment stage below the &lt;em&gt;displayname&lt;/em&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_dev&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to development&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_prd&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRD&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="s"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Add Replace token task
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Extensions installed ✅&lt;/li&gt;
&lt;li&gt;Variable groups created ✅&lt;/li&gt;
&lt;li&gt;Variable groups referenced in yml ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now add the replace tokens extension as step to each deployment stage. Make sure you configure the value of &lt;em&gt;targetFiles&lt;/em&gt; properly. What we’re doing here is changing all occurrences of our replacement tokens in the &lt;code&gt;main*.js&lt;/code&gt; files. So that would include the &lt;code&gt;main-es5.*.js&lt;/code&gt; and &lt;code&gt;main-es2015.*.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_dev&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to development&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, development world!&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replacetokens@3&lt;/span&gt;
                &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;targetFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$(Pipeline.Workspace)/dist/**/main*.js"&lt;/span&gt;
                  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto"&lt;/span&gt;
                  &lt;span class="na"&gt;writeBOM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
                  &lt;span class="na"&gt;verbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detailed"&lt;/span&gt;
                  &lt;span class="na"&gt;actionOnMissing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warn"&lt;/span&gt;
                  &lt;span class="na"&gt;keepToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
                  &lt;span class="na"&gt;tokenPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#{"&lt;/span&gt;
                  &lt;span class="na"&gt;tokenSuffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}#"&lt;/span&gt;
                  &lt;span class="na"&gt;useLegacyPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
                  &lt;span class="na"&gt;enableTelemetry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_prd&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRD&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRD&lt;/span&gt;
      &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runOnce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo Hello, production world!&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;replacetokens@3&lt;/span&gt;
                &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;targetFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$(Pipeline.Workspace)/dist/**/main*.js"&lt;/span&gt;
                  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto"&lt;/span&gt;
                  &lt;span class="na"&gt;writeBOM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
                  &lt;span class="na"&gt;verbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detailed"&lt;/span&gt;
                  &lt;span class="na"&gt;actionOnMissing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warn"&lt;/span&gt;
                  &lt;span class="na"&gt;keepToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
                  &lt;span class="na"&gt;tokenPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#{"&lt;/span&gt;
                  &lt;span class="na"&gt;tokenSuffix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}#"&lt;/span&gt;
                  &lt;span class="na"&gt;useLegacyPattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
                  &lt;span class="na"&gt;enableTelemetry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let’s run this pipeline by committing and pushing your changes 🚀!&lt;/p&gt;

&lt;p&gt;Because we’re not deploying our code to a working environment, we can’t verify if the replacement action actually worked…What we can do is check the logging. So open the last run in the DevOps portal and click on the replacetokens job to see: &lt;em&gt;3 tokens replaced out of 3&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DEV &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gUjVOdu_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/17.png" alt="img"&gt;
&lt;/li&gt;
&lt;li&gt;PROD &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--abJ-WhI0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/18.png" alt="img"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bonus: add &lt;em&gt;dependsOn&lt;/em&gt; and &lt;em&gt;conditions&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;As you can see, your pipeline will sequentially run all stages. But you want to make sure that your production deployment will only be executed in certain conditions.We can make use of &lt;code&gt;dependsOn&lt;/code&gt; and &lt;code&gt;conditions&lt;/code&gt; to restrict the execution a bit. Add these properties to your deployment stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;....&lt;/span&gt;
&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to development&lt;/span&gt;
&lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
&lt;span class="s"&gt;....&lt;/span&gt;


&lt;span class="s"&gt;....&lt;/span&gt;
&lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to production&lt;/span&gt;
&lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy_dev&lt;/span&gt;
&lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))&lt;/span&gt;
&lt;span class="s"&gt;....&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our stages do now depend on the &lt;code&gt;build&lt;/code&gt; and/or &lt;code&gt;deploy_dev&lt;/code&gt; stage and so will only run when that stage succeeds. Also we introduced the condition that the git branch should be equal to &lt;code&gt;master&lt;/code&gt;.This gives you a bit more control about your pipeline. You can even introduce manual intervention by configuring an Approval step on the specific environment: &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xOCLphYh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xOCLphYh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-16/19.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building and deploying Angular applications doesn’t need to be very complex if we’re talking about environments. I showed you how to utilize the Angular environment files properly in a Azure DevOps multi-stage yml release pipeline without the need to rebuild the application for every environment. And we did not introduce any extra start up complexity to our application. So no need to load our app configuration via the APP_INITIALIZER and delaying the actual start up of our web app. Just use the already provided environment files properly, &lt;code&gt;environment.ts&lt;/code&gt; for local development and &lt;code&gt;environment.prod.ts&lt;/code&gt; for production, and you’re good to deploy with confidence!&lt;/p&gt;

&lt;p&gt;What approach are you using at this moment?&lt;/p&gt;

</description>
      <category>azuredevops</category>
      <category>microsoft</category>
      <category>angular</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Build and release your Ionic + Capacitor app via Microsoft App Center</title>
      <dc:creator>Kevin Boosten</dc:creator>
      <pubDate>Wed, 06 May 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/boostuh1/build-and-release-your-ionic-capacitor-app-via-microsoft-app-center-i7c</link>
      <guid>https://dev.to/boostuh1/build-and-release-your-ionic-capacitor-app-via-microsoft-app-center-i7c</guid>
      <description>&lt;p&gt;Building and deploying mobile applications can be challenging. I like developing mobile applications for iOS and Android. My framework of choice to do this is &lt;a href="https://ionicframework.com/"&gt;Ionic&lt;/a&gt;. Now Capacitor became the preferred way to access your native SDK’s in your hybrid app, building apps with App Center became a lot easier.&lt;/p&gt;

&lt;p&gt;If you’re building a hybrid application with &lt;a href="https://ionicframework.com/"&gt;Ionic&lt;/a&gt;, you probably are using &lt;a href="https://capacitor.ionicframework.com/"&gt;Capacitor&lt;/a&gt; as your native bridge to get access to API’s like your camera, push notifications and others. If you’re not using it yet then you should definitely give it a try.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Capacitor is a spiritual successor to Apache Cordova and Adobe PhoneGap, with inspiration from other popular cross-platform tools like React Native and Turbolinks, but focused entirely on enabling modern web apps to run on all major platforms with ease. Capacitor has backwards-compatible support for many existing Cordova plugins.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check out the &lt;a href="https://capacitor.ionicframework.com/docs/"&gt;Capacitor docs&lt;/a&gt; for a brief introduction and how to add capacitor to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Center
&lt;/h2&gt;

&lt;p&gt;Visual Studio App Center is a Microsoft product for continuously build, test, release and monitor your apps. In this article we’re only going to focus on the building part of App Center.App Center supports a variety of operating systems: iOS, Android, Windows, MacOS and even tvOS. The platforms, besides the native ones, that are available are: React Native, Cordova (preview), Xamarin and Unity. Enough to cover your needs, I guess!&lt;/p&gt;

&lt;p&gt;Before the introduction of Capacitor, it was not possible to build your Ionic + Cordova app with App Center. There is a Cordova (preview) feature but unfortunately it does not support building. Capacitor has a different philosophy than Cordova. With Capacitor you have full access to your iOS and Android projects and these will also be part of your git repository. Cordova generates these projects on every build, so you can not make changes without making use of the Cordova config.xml file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ionic AppFlow
&lt;/h3&gt;

&lt;p&gt;Of course there are alternatives to build you mobile application. If you’re primarily building Ionic applications then &lt;a href="https://ionicframework.com/appflow"&gt;Ionic AppFlow&lt;/a&gt; will maybe be the better choice for you. They also have got ‘Publishing to Store’ functionality since a couple of months! So no need to manually download your artifacts from AppFlow and upload them to the stores anymore.But if you’re working in a company that also builds Xamarin and React Native apps and is also using Microsoft Azure, then App Center makes more sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a test app
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Skip this section if you already have an Ionic+Capacitor app&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let’s start with creating a Ionic + Capacitor app so we can use that as our app that we’re going to use in App Center.&lt;/p&gt;

&lt;p&gt;You can also fork this demo application from &lt;a href="https://github.com/Boosten/ionic-capacitor-appcenter-demo"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First install the Ionic CLI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;      npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @ionic/cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Create an Angular application with Capacitor integration. At this point we just have a normal Angular web application, nothing special so far.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ionic start myApp tabs &lt;span class="nt"&gt;--capacitor&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;angular
&lt;span class="nb"&gt;cd &lt;/span&gt;myApp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Start the application to verify everything went well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ionic serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Okay, you should now see a tabbed application with some dummy data in it.&lt;/p&gt;

&lt;p&gt;Let’s add the native platforms to it. In order to do this, we should have at least build our application once, so run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ionic build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add the native platforms after that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cap add ios
npx cap add android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Push your changes to a (private) repository and you are good to go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring App Center
&lt;/h2&gt;

&lt;p&gt;We’re now going to create an app in App Center and connect it to our repository.&lt;/p&gt;

&lt;p&gt;Go to App Center to create a new application: &lt;a href="https://appcenter.ms/apps"&gt;https://appcenter.ms/apps&lt;/a&gt; &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ttRCWlJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ttRCWlJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/1.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a name for your application, select iOS en choose a release type. The release type is just a label and it does not have any affect on your build. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hup1rrEH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hup1rrEH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/2.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;strong&gt;Build&lt;/strong&gt; from the side-menu and connect your git repo. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aF6SY1Aq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aF6SY1Aq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/3.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CSU_ppHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CSU_ppHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/4.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now click on the branch that you would like to configure for building (or click on the wrench icon). And click on &lt;strong&gt;Configure build&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6AeIMM7W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6AeIMM7W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/5.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tHgJccP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tHgJccP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/6.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s possible that you see the error &lt;em&gt;You must add a shared scheme&lt;/em&gt;. &lt;br&gt;
Follow the instructions in the link to set your workspace scheme to &lt;br&gt;
“Shared”. Also add the necessary Provisioning Profile and Certificate to build a valid &lt;code&gt;.ipa&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In case you forked the demo repo, you need to change the Team ID and &lt;br&gt;
App ID in Xcode manually and push these changes to your fork. Also &lt;br&gt;
provide a provisioning profile and certificate that matches your app. &lt;br&gt;
You don’t need to have paid developer account to create a valid &lt;br&gt;
provisioning profile, just export the certificate and profile from &lt;br&gt;
your machine that you use in Xcode._Still having trouble? Maybe this &lt;br&gt;
can help you: &lt;a href="https://intercom.help/appcenter/en/articles/1617765-ios-signing-issues-explained"&gt;iOS signing issues explained&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also change other settings like building on each push and auto incrementing your build number.&lt;/p&gt;

&lt;p&gt;Now click on &lt;strong&gt;Save &amp;amp; Build&lt;/strong&gt; to see what happens!&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UV74MdDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UV74MdDi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/7.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❌ Aiiii, that escalated quickly! What happened? Remember we are trying to build a hybrid application? So what did we do to actually build the web part of the app? &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OTVZgbPI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OTVZgbPI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/8.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here comes the ‘trick’💡! App Center supports several build scripts that run at pre-defined stages: &lt;code&gt;post-clone&lt;/code&gt;, &lt;code&gt;pre-build&lt;/code&gt; and &lt;code&gt;post-build&lt;/code&gt;. That’s exactly what we need to build our hybrid application with App Center. We’re going to use the post-clone script in order to perform some steps that are required to build our app. Let’s add a file &lt;code&gt;appcenter-post-clone.sh&lt;/code&gt; to the iOS (&lt;em&gt;ios/App/appcenter-post-clone.sh&lt;/em&gt;) and Android (&lt;em&gt;android/app/appcenter-post-clone.sh&lt;/em&gt;) projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# fail if any command fails&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;span class="c"&gt;# debug log&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

&lt;span class="c"&gt;# Required nodeJS version&lt;/span&gt;
&lt;span class="nv"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.17.0

&lt;span class="c"&gt;# workaround to override the v8 alias&lt;/span&gt;
npm config delete prefix
&lt;span class="nb"&gt;.&lt;/span&gt; ~/.bashrc
nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NODE_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
nvm &lt;span class="nb"&gt;alias &lt;/span&gt;node10 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NODE_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# go to root of project&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../..

&lt;span class="c"&gt;# install dependencies&lt;/span&gt;
npm i

&lt;span class="c"&gt;# run optimized production build&lt;/span&gt;
npm run build &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--prod&lt;/span&gt;

&lt;span class="c"&gt;# copy the web assets to the native projects and updates the native plugins and dependencies based in package.json&lt;/span&gt;
npx cap &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first couple of lines are just to override the default NodeJS version that App Center is using. After that, we’re installing our npm dependencies, running an production build and finally invoking a Capacitor specific command to sync our web part of the application to the native projects.&lt;/p&gt;

&lt;p&gt;Go back to the configuration and be sure to click on “Save &amp;amp; Build” button to let App Center know that new build specific files need to be indexed.&lt;/p&gt;

&lt;p&gt;Your build should now succeed after waiting for a couple of minutes 🎉.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9o9FQOBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9o9FQOBp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.kevinboosten.dev/assets/images/posts/2020-05-06/9.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, long story short: add the &lt;code&gt;appcenter-post-clone.sh&lt;/code&gt; script to both native projects in your Ionic + Capacitor project, and you will be able to use App Center as your CI/CD platform.&lt;/p&gt;

&lt;p&gt;You obviously can extend this script with all the things you need. For example: you could change the dev/prod mode based on an &lt;code&gt;environment variable&lt;/code&gt; that you can set in your App Center build configuration. Definitely take a look at the &lt;a href="https://github.com/microsoft/appcenter/tree/master/sample-build-scripts"&gt;Microsoft sample-build-scripts repo&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Extend your build configuration to your needs&lt;/li&gt;
&lt;li&gt;Distribute builds to distribution groups&lt;/li&gt;
&lt;li&gt;Publish your application to the public stores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my next blog I will describe how to properly distribute your app for testing purposes and how to add new test devices to your apps provisioning profile for your iOS distribution.&lt;/p&gt;

&lt;p&gt;Stay tuned and happy building 🏗👷🏻‍♂️👷🏻‍♀️!&lt;/p&gt;

</description>
      <category>appcenter</category>
      <category>microsoft</category>
      <category>ionic</category>
      <category>capacitor</category>
    </item>
  </channel>
</rss>
