Monday, June 22, 2020

Real world angular reactive form

For the last couple of years, I've created a few CRM and back-office applications using angular. The one thing you can say about those type of apps is they heavily rely on forms. While there are quite a few angular 2+ tutorials about this topic available, It was quite difficult to find a good real-world example for using angular reactive forms with nested component, arrays, custom inputs and etc...

This post is targeted for programmers with some basic angular concepts understanding. If you you're new to angular I highly suggest this Udemy course before moving to more advanced topics:


In this tutorial, we are going to build a simple pizza booking form using angular reactive forms. Here is our simple app layout:

Component layout of the app 

Reactive form approach

Angular provides us with two ways for handling forms:

We won't go deep into the difference between both approaches but I will just tell that angular's reactive form is a powerful tool for managing complex form states. It's heavily based on RxJs which makes it easy to react and observe form changes.

Getting Started

Final product design

I won't cover each part of the final product in this tutorial, but you can find the entire source code on my GitHub repo: https://github.com/scopsy/angular-forms-example

Here are the topics we going to discuss:

Delegating business logic to component scoped services

In order to manage the pizza form logic, we will use multiple services and initialize them in the app-pizza-form-container component, to ensure a new instance of the services will be initialized each time the root component will be created. I have seen a lot of junior developers use services only for global services. Moving away our business logic to the service will result in a smaller component file and easier to test components.

Here is an example of how we defined our services at the component level:

@Component({
  selector: 'app-pizza-form-container',
  templateUrl: './pizza-form-container.component.html',
  styleUrls: ['./pizza-form-container.component.scss'],
  providers: [
    PizzaFormService,
    PizzaFormValidatorsService,
    PizzaLoaderService
  ]
})
export class PizzaFormContainerComponent implements OnInit {
    // ... code
}

When providing a service in the component a new instance of the service will be created when the component bootstraps and the service will be destructed when the component destroyed. This is very helpful since we want that every time a customer will enter the order page, a new order will be created as opposed to providing the service in our AppModule where a single instance of the service will be created for the entire lifecycle of our app.

Here is how our PizzaFormService looks:


@Injectable()
export class PizzaFormService {
  public availableToppings = [...Object.values(PizzaToppingsEnum)];
  public form: FormGroup;

  constructor(
    private pizzaValidatorsService: PizzaFormValidatorsService,
    private fb: FormBuilder
  ) {
    this.form = this.fb.group({
      selectedPizza: null,
      pizzas: this.fb.array([]),
      customerDetails: this.fb.group({
        firstName: [null, Validators.required],
        lastName: [null, Validators.required],
        phoneNumber: [null, Validators.required],
        address: this.fb.group({
          street: [null, Validators.required],
          houseNum: [null, Validators.required],
          city: [null, Validators.required],
          floor: [null, Validators.required],
        })
      })
    }, {
      validator: this.pizzaValidatorsService.formValidator()
    });
  }

  get pizzasArray(): FormArray {
    return this.form.get('pizzas') as FormArray;
  }

  get isValid(): boolean {
    if (!this.form.valid) {
      this.pizzaValidatorsService.validateAllFormFields(this.form);
      return false;
    }

    return true;
  }

  selectPizzaForEdit(index: number) {
    this.form.get('selectedPizza').setValue(index);
  }

  addPizza(): FormGroup {
    const pizzaGroup = this.getPizzaFormGroup();
    this.pizzasArray.push(pizzaGroup);

    this.form.markAsDirty();

    return pizzaGroup;
  }

  deletePizza(index: number): void {
    this.pizzasArray.removeAt(index);
    this.form.markAsDirty();
  }

  getPizzaFormGroup(size: PizzaSizeEnum = PizzaSizeEnum.MEDIUM): FormGroup {
    return this.fb.group({
      size: [size],
      toppings: this.mapToCheckboxArrayGroup(this.availableToppings)
    }, {
      validator: this.pizzaValidatorsService.pizzaItemValidator()
    });
  }

  createPizzaOrderDto(data: IPizzaFormInterface): IPizzaFormInterface {
    const order = {
      customerDetails: data.customerDetails,
      pizzas: data.pizzas
    };

    for (const pizza of order.pizzas) {
      pizza.toppings = this.getSelectedToppings(pizza.toppings as IToppingItem[])
        .map((i) => {
          return i.name;
        });
    }

    return order;
  }

  getSelectedToppings(toppings: IToppingItem[]): IToppingItem[] {
    return toppings.filter(i => i.selected);
  }

  private mapToCheckboxArrayGroup(data: string[]): FormArray {
    return this.fb.array(data.map((i) => {
      return this.fb.group({
        name: i,
        selected: false
      });
    }));
  }
}

The service is responsible and manage the form object built by the FormBuilder service and for handling the interaction with the form including adding and removing pizzas. We will take a closer look at this class down the road.

Validation based on multiple control values

Notice how we delegated the form validators to a different service in order keep our form service file in a reasonable size. In our pizza app we created some basic validations for the form fields with the built-in validators:

this.form = this.fb.group({
  selectedPizza: null,
  pizzas: this.fb.array([]),
  customerDetails: this.fb.group({
    firstName: [null, Validators.required],
    lastName: [null, Validators.required],
    phoneNumber: [null, Validators.required],
    address: this.fb.group({
      street: [null, Validators.required],
      houseNum: [null, Validators.required],
      city: [null, Validators.required],
      floor: [null, Validators.required],
    })
  })
}, {
  validator: this.pizzaValidatorsService.formValidator()
});

The built-in validators are great for single control validations and they include the following validators:

  • Required
  • min
  • max
  • email
  • pattern (for a regex match)

To combine multiple validators we use the Validators.compose function that accepts an array of built-in and custom validators.

Single control validations are great, but usually, we need to create more complex validations based on multiple values in our forms. We can provide a validator function to each formGroup in our form, simply by passing the options parameter after the group content object  

this.form = this.fb.group({
 // formGroup definition code goes here...
}, {
  validator: this.pizzaValidatorsService.formValidator()
});

Let's take a look at the PizzaValidatorsService:

@Injectable()
export class PizzaFormValidatorsService {

  constructor() { }

  formValidator(): ValidatorFn {
    return (control: FormGroup): ValidationErrors | null => {
      const errors: ValidationErrors = {};

      if (!(control.get('pizzas') as FormArray).length) {
        errors.noPizzas = {
          message: 'You must select at least one pizza to order'
        };
      }

      return Object.keys(errors).length ? errors : null;
    };
  }

  pizzaItemValidator(): ValidatorFn {
    return (control: FormGroup): ValidationErrors | null => {
      const errors: ValidationErrors = {};

      const pizzaSize: PizzaSizeEnum = control.get('size').value;
      const pizzaToppings: IToppingItem[] = control.get('toppings').value.filter(i => i.selected);

      if (pizzaSize !== PizzaSizeEnum.LARGE && pizzaToppings.length > 4) {
        errors.toppingPizzaSize = {
          message: 'To use more then 4 toppings you must selected large pizza'
        };
      }

      return Object.keys(errors).length ? errors : null;
    };
  }
  
  validateAllFormFields(formGroup: FormGroup) {
    Object.keys(formGroup.controls).forEach(field => {
      const control = formGroup.get(field);

      if (control instanceof FormControl) {
        control.markAsTouched({ onlySelf: true });
      } else if (control instanceof FormGroup) {
        this.validateAllFormFields(control);
      }
    });
  }
}

We defined 2 validators, one for the entire form and one for each pizza formGroup item.

formValidator(): ValidatorFn {
    return (control: FormGroup): ValidationErrors | null => {
      const errors: ValidationErrors = {};

      if (!(control.get('pizzas') as FormArray).length) {
        errors.noPizzas = {
          message: 'You must select at least one pizza to order'
        };
      }

      return Object.keys(errors).length ? errors : null;
    };
}

The form validator is just a class method that returns a ValidatorFn function, this function will receive the control object that will be our form definition. We can then perform a check of the pizzas FormArray and if its empty add a noPizzas error. Notice how the noPizzas property is an object, we define it like this to provide a meaningful error message to the user.

This is how we can then display the error message in our HTML file:

<div *ngIf="form.errors?.noPizzas && form.dirty" class="ValidationErrorLabel mg-bottom-15">
   {{form.errors?.noPizzas.message}}
</div>

First, we check if the noPizzas error exist on the form.errors object, if it does we check if the form is dirty, since we don't want to show the error before the user has touched the form (it will be awkward). We then display the noPizzas.message error text defined in the validator. Tadda.

Reusable form components

It is often a good practice to separate our application into small and reusable components. Our customer details area is a great fit for an app-wide reusable component:

We will create a customer details component that will receive only the formGroup related to the customer details, this will allow as to reuse the customerDetails with other forms in our application as long as the customer details component stays "dumb" and isolated from the parent form structure. Here is how the component file looks:


@Component({
  selector: 'app-customer-details',
  templateUrl: './customer-details.component.html',
  styleUrls: ['./customer-details.component.scss']
})
export class CustomerDetailsComponent implements OnInit {
  @Input() group: FormGroup;
  constructor() { }

  ngOnInit() {
  }
}

It has a single @Input named group of type FormGroup. In the component HTML file we will wrap the parent div with the [formGroup] attribute:

<div class="CustomerDetails" [formGroup]="group">
  <div class="row">
    <div class="col-md-12">
      <h3>
        Customer Details
      </h3>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="First Name" formControlName="firstName" />
      </mat-form-field>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="Last Name" formControlName="lastName" />
      </mat-form-field>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="Phone Number" formControlName="phoneNumber" />
      </mat-form-field>
    </div>
  </div>

  <div class="row" [formGroup]="group.get('address')">
    <div class="col-md-12">
      <h3>
        Delivery Address
      </h3>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="Street" formControlName="street" />
      </mat-form-field>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="Apt. Number" formControlName="houseNum" />
      </mat-form-field>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="City" formControlName="city" />
      </mat-form-field>
    </div>
    <div class="col-md-3">
      <mat-form-field class="full-width-input">
        <input matInput placeholder="Floor" formControlName="floor" />
      </mat-form-field>
    </div>
  </div>
</div>

Now, inside our app-form-container component we use the new component like this:

<div class="row">
    <div class="col-md-12">
      <app-customer-details [group]="form.get('customerDetails')"></app-customer-details>
    </div>
</div>

Notice how we only pass the customerDetails form group of our form object. This ensures that our component is only aware of the customerDetails context and not about its host. We then can easily reuse the app-customer-details with other forms that have a similar formGroup:

customerDetails: this.fb.group({
    firstName: [null, Validators.required],
    lastName: [null, Validators.required],
    phoneNumber: [null, Validators.required],
    address: this.fb.group({
      street: [null, Validators.required],
      houseNum: [null, Validators.required],
      city: [null, Validators.required],
      floor: [null, Validators.required],
    })
})

(It is often recommended to extract the customerDetails form group to external service in order to ensure the formGroup structure will be the same across forms and to avoid repetition.)

Custom form control

Working with formControl is easy when working with the regular inputs: selectinputtextarea and etc... But often its needed to create a custom component we want to integrate with our reactive form. Let's look at the PizzaSizeSelector component:

Our designer came up with a sophisticated widget and we need to implement it. First of all, we need to create a new component and declare it inside our main component:

<div class="col-md-6 offset-md-3">
  <app-pizza-size-picker formControlName="size"></app-pizza-size-picker>
</div>

Notice the formControlName attribute, when the page reloaded we will get the following error:

ERROR Error: No value accessor for form control with name: 'size'

This is all because we have to make a few modifications to our new component, let's take a look at what we need to do:

@Component({
  selector: 'app-pizza-size-picker',
  templateUrl: './pizza-size-picker.component.html',
  styleUrls: ['./pizza-size-picker.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PizzaSizePickerComponent),
      multi: true
    }
  ]
})

The first thing to notice is that we added a new provider named NG_VALUE_ACCESSOR. Angular at startup uses this token to register the element with the respected model. After registering our component like a real pro the error goes away. However we have not finished our job here.

export class PizzaSizePickerComponent implements ControlValueAccessor {
  pizzaSize: PizzaSizeEnum;
  PizzaSizeEnum = PizzaSizeEnum;

  constructor() { }

  changeSize(size: PizzaSizeEnum) {
    this.pizzaSize = size;
    this.propagateChange(size);
  }

  writeValue(value: PizzaSizeEnum) {
    this.pizzaSize = value;
  }

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() {}
  propagateChange = (value: PizzaSizeEnum) => {};
}

We need to implement the ControlValueAccessor interface. This will ensure we implemented the correct hooks of the formControl component.

writeValue

Is used to write a new value to the controller, for example when we will use our form to set a value:

this.form.get('size').setValue(1);

the writeValue function will get called and will allow us to write the new value the local pizzaSize property.

registerOnChange

Is called when the component is bootstrapped in the form context, it will pass a function we will use later to notify the host form about changes inside our custom component.

registerOnTouched

Behaves like registerOnChange but is used to pass the touched event to the form, we won't implement it in our simple example.

After we finished hooking up everything in our component file let's take a look at the HTML implementation:


<div class="row">
  <div class="col-md-12">
    <div class="PizzaSizePicker">
      <div class="PizzaSizePicker__item"
           (click)="changeSize(PizzaSizeEnum.SMALL)"
           [ngClass]="{'PizzaSizePicker__item--active': pizzaSize === PizzaSizeEnum.SMALL}">
        SMALL
      </div>
      <div class="PizzaSizePicker__item"
           (click)="changeSize(PizzaSizeEnum.MEDIUM)"
           [ngClass]="{'PizzaSizePicker__item--active': pizzaSize === PizzaSizeEnum.MEDIUM}">
        MEDIUM
      </div>
      <div class="PizzaSizePicker__item"
           (click)="changeSize(PizzaSizeEnum.LARGE)"
           [ngClass]="{'PizzaSizePicker__item--active': pizzaSize === PizzaSizeEnum.LARGE}">
        LARGE
      </div>
    </div>
  </div>
</div>

We define our custom buttons and attached a click function to execute the changeSize function declared before:

changeSize(size: PizzaSizeEnum) {
    this.pizzaSize = size;
    this.propagateChange(size);
}

We change our local component state and call the propagateChange function to notify the parent formGroup of our change.

Working with checkbox arrays in ReactiveForms

I found that working with checkbox arrays in ReactiveForms can get quite tricky, some of the solutions suggest creating a boolean FormArray representation of the checkbox state. In our example we have the Toppings picker represented with multiple checkboxes:

The usual suggestion would be to convert all the toppings with something like this:

const TOPPINGS = ['Pepperoni', 'Olives', 'Pineapple']; // Yes it says pineapple.

this.form = this.fb.group({
    // ... other code
    toppings: this.fb.array(TOPPINGS.map(() => false))
})

Personally, I find this approach not very convenient since the final form value will be:

toppings: [false, false, true, false]

We will then have to map this weird array to more meaningful value based on the index of the toppings array.

So how can we do better?

We can map the toppings data array to the following interface

interface ICheckBoxItem {
    id?: string; 
    selected: boolean;
    name: string; 
}
  • id - is an optional key that can be used for the internal enum value of the topping item.
  • selected - will hold the checkbox checked state
  • name - is the friendly text will be displayed to the user.

In our example, we will use the same value for the display and for the internal ID. We can create a small helper function to map our data to the ICheckBoxItem interface:

private mapToCheckboxArrayGroup(data: string[]): FormArray {
    return this.fb.array(data.map((i) => {
      return this.fb.group({
        name: i,
        selected: false
      });
    }));
}

We receive the options array and map it to a new FormArray. The toppings array formGroup creation will look like this:

  getPizzaFormGroup(size: PizzaSizeEnum = PizzaSizeEnum.MEDIUM): FormGroup {
    return this.fb.group({
      size: [size],
      toppings: this.mapToCheckboxArrayGroup(this.availableToppings)
    }, {
      validator: this.pizzaValidatorsService.pizzaItemValidator()
    });
  }

this.availableToppings represents the toppings array described above).

Then, our HTML file will iterate over the toppings form array and generate the checkbox inputs:

<div class="col-md-12 mg-top-15">
  <h5>Toppings</h5>
  <div class="ToppingsSelector" formArrayName="toppings">
    <div class="ToppingsSelector__item" *ngFor="let topping of toppingsArray.controls" [formGroup]="topping" >
      <mat-checkbox [formControl]="topping.get('selected')">
        {{topping.get('name').value}}
      </mat-checkbox>
    </div>
  </div>
</div>

Later when we need to get all the selected toppings we can simply filter all the formControls where selected === true.

Restoring the form state from the server

Personally, this is my favorite part of ReactiveForms. Where all the hard work we did before really pays off. So we finished our beautiful form and everyone is happy. But pretty soon our boss comes and introduce us to a new requirement: The user will be able to modify his order after it was created.

Luckily, it's super easy with our form. First, in our container component, we will detect if we are in edit mode or in creation mode. I usually pass the id param to the router and if it exists it means we are editing an existing form

ngOnInit() {
    if (this.editMode) {
      // ...fetch data from server based on the id 
      this.pizzaLoaderService.loadPizzaForEdit(DEMO_PIZZA);
    }
}

Let's take a look at the PizzaLoaderService:

@Injectable()
export class PizzaLoaderService {

  constructor(
    private pizzaFormService: PizzaFormService
  ) {

  }

  loadPizzaForEdit(data: IPizzaFormInterface): void {
    this.pizzaFormService.form.patchValue({
      customerDetails: {
        ...data.customerDetails
      }
    });

    for (const pizza of data.pizzas) {
      const group = this.pizzaFormService.addPizza();
      group.patchValue({
        size: pizza.size,
        toppings: this.prefillToppingsSelection(group.get('toppings').value, pizza.toppings as PizzaToppingsEnum[])
      });
    }
  }

  prefillToppingsSelection(toppings: IToppingItem[], selectedToppings: PizzaToppingsEnum[]): IToppingItem[] {
    return toppings.map((i) => {
      if (selectedToppings.includes(i.name)) {
        i.selected = true;
      }

      return i;
    });
  }
}

We inject the PizzaFormService to gain access to the form object and provide a loadPizzaForEdit function. It will get the order object as received from the server ( for simplicity sake I used the same interface for the form, but usually you will have to map the server model to the form interface. This logic will happen here.)

It patches the values of the customerDetails form group and then adds a pizza group for each pizza object received from the server. Only after the form group is added we can patch it's value with the data from the server.

Mapping the checkbox values

Remember that our toppings are a formArray with the ICheckBoxItem interface? Well, the server responded with ['Ham', 'Pineapple']. Where are the other toppings? And wait, that's not how our formArray looks like! To map the server response to the correct structure we created a small function named prefillToppingsSelection:

prefillToppingsSelection(toppings: IToppingItem[], selectedToppings: PizzaToppingsEnum[]): IToppingItem[] {
    return toppings.map((i) => {
      if (selectedToppings.includes(i.name)) {
        i.selected = true;
      }

      return i;
    });
}

It receives the entire form values from the toppings formGroup created from the addPizza method and iterates over them, if the individual topping exists in the selectedToppings array it marks it as selected = true. Voila.

Resetting a form

A week goes by and yet another feature request from our boss John: "A customer must be able to reset the form". Happy me responds with: "Hold my beer for a sec.".

Let's create a new method in our PizzaFormService:

resetForm() {
    while (this.pizzasArray.length) {
      this.pizzasArray.removeAt(0);
    }

    this.form.reset();
}

Reactive forms have a built-in  reset function. However, it won't remove our added pizza formGroups from the FormArray. We will need to iterate over them and remove them manually.

Now our form will completely reset when the customer hits that button. Great success.

Wrapping up

Handling complex forms is often pretty hard. Angular provides us with a powerful tool to manage the state of those forms easily. I highly recommend to take a look at the full project source code available here.

Have any tips worth knowing about? Share them in the comments!

Angular Reactive Forms: Tips and Tricks

With every article I publish, my goal is to help you become a better and more confident coder.

Here are a couple of pointers to help shed some light on Angular Forms:

Control Validation

By default, whenever a value of a FormControl changes, Angular runs the control validation process.

For example, if you have an input that is bound to a form control, Angular performs the control validation process for every keystroke.

Let’s see this in action:

Now, imagine a form with complex validation requirements — updating such a form on every keystroke could become too costly. In addition to that, I find it very annoying to display an error message to the user when he hasn’t completed the action of entering the form data.

The way we get around this is by using the updateOn property. We can tell Angular that we only want to run the validation function upon submit or blur. As we saw, the default option is upon change.

updateOn: ‘blur’

We can also apply this option to a formGroup or a formArray.

Ok, that’s great. But let’s say we still want to run our validators whenever form control’s data changes, how can we optimize them?

In such a case we don’t have to use the control validator mechanism, we can create an alternative which we can fully control ourselves.

Luckily, every control exposes a valueChanges observable that we can take advantage of, and use RxJS to do some more powerful stuff. For example, we can add a debounce to our control.

And this is only one simple example. We can use more powerful operators to perform advanced cross-control validations like mergecombineLatest, etc., so keep that in mind.

I also wanted to mention that I’m against premature optimization— you should always prefer readability and reusability, and only apply optimizations when you think you may need them.

There might be cases when we don’t want to invalidate the form just because a single control is invalid. I haven’t come across such a case, but maybe you have.

In such a case the message you’re trying to convey to the user is: “this value isn’t valid, but it doesn’t mean the form can’t be submitted”.

In this instance we can set the onlySelf property to true, which means that each change only affects the control itself alone, and not its parents.

Preventing Infinite Loops

In the process of setting up our form controls, we might inadvertently cause infinite loops.

For example, if on the one hand we listen to the store’s changes and update the form accordingly, and on the other hand we listen to the form’s value changes and update the store.

In order to avoid the above situation, we can tell Angular not to emit the valueChanges event whenever we update the form.

And now we’re good to go. It is worth noting that most of a control’s methods support this option, such as setValue()reset(), updateValueAndValidity(), etc.

Control Disabling

As you may know, when we need to disable or enable a control we can call the control.disable() or control.enable() methods.

That’s helpful, but sometimes we want to tell Angular that this control is disabled on the initialization face. We can achieve this by passing an object to our FormControl constructor.

Pay attention that we must also pass the value property; otherwise Angular assumes that we want the control’s value to be { disabled: true }.

My next tip is something I think many people haven’t noticed yet. Whenever we call a control’s disable() or enable() methods, Angular triggers the valueChanges event.

Yes, it may surprise you because the “value” didn’t change, but this is how it works.

There are times when we want to prevent this behavior from occurring.

Let’s say we need to update the store upon form’s value changes. This behavior would cause redundant updates, since we’d also update the store whenever the control disability status changed.

Luckily as we mentioned before there is an easy fix, we can add { emitEvent false } to the current method:

When we want to get our FormGroup value we usually call the value property. For example:

But there’s one catch there. The value property will omit controls that are disabled. So, in the above example, we’ll only get the email control value.

In most cases, this is not the desired behavior. In order to get the complete form’s values, we can use the FormGroup.getRawValue() method.

Control Getters and Setters

I want to talk about a common mistake that I see in people’s code examples, especially on Stackoverflow. When they need to add a new control, they assign it directly to the parent’s controls property:

Please don’t do this. Instead, use the addControl() method, since it performs multiple required tasks for us under the hood:

Angular source code

Another thing I want to talk about is obtaining a reference to a form control. This is more of a personal stylistic preference, but I don’t like to get it from the group’s controls property:

Angular provides the group with a get() method, and I find it to be a more preferable option.

Another reason to prefer this — the Angular team might opt to change the FormGroup structure in the future, and if we use the controls property directly, it might lead to a breaking change, whereas the get() method could adjust to reflect the new structure.

This might be why Angular provides us with the get() method, but hey, maybe it’s just me.

Using FormBuilder

There are times when we need to create a large form structure. In this case, the code can rapidly become repetitive and full of boilerplate. For example:

In reality, it might be even larger than that, but you get the point. In order to make our life easier, Angular provides us with the FormBuilder service, which frees us from adding boilerplate. I think it’s a better option, as it enables much cleaner code.

That’s it 😀


Free hosting web sites and features -2024

  Interesting  summary about hosting and their offers. I still host my web site https://talash.azurewebsites.net with zero cost on Azure as ...