In this section, you will learn how to add support for TypeScript to your Razor Pages application. Similar to Sass a couple of sections ago, TypeScript is not essential for working with client-side code, but it is a great productivity assistant. And, like Sass, TypeScript code is not used by your application directly. It is compiled to JavaScript. Your application will work with the resulting .js files that are generated by the TypeScript compiler.
So why use TypeScript instead of writing JavaScript directly?
TypeScript is a superset of JavaScript that adds static typing to the language. This means that TypeScript code can be checked for type errors at compile time, which can help to prevent runtime errors. TypeScript also provides other features that can make browser-based JavaScript development more reliable and efficient, such as:
- Interfaces: Interfaces can be used to define the shape of data structures, which can help to prevent errors when passing data between different parts of an application.
- Generics: Generics can be used to create reusable code that can work with different types of data.
- Modules: Modules can be used to organize code into logical units, which can make it easier to understand and maintain large codebases.
If you are new to TypeScript, I recommend you visit the official site for more guidance.
In this section, you will configure TypeScript in your application, and then use it to generate a small amount of JavaScript code that will calculate the total price of an order prior to the user submitting the form.
To begin, you need to install TypeScript itself if you haven't already done so at some point. This can be done via the Node Package Manager (npm) which is enabled by default in VS Code.
Open a terminal and execute the following command, which will install the latest version of TypeScript globally:
npm install -g typescript
Next, you need to configure the TypeScript compiler's behaviour within your application. Configuration is specified in a tsconfig.json file, which you create by executing the following command:
tsc --init
The resulting file contains a default configuration which is sufficient for your needs for this exercise. If you want to take advantage of modules that use import
and export
, you should change the target
and module
options to "ES2020"
at least. To support the latest features regardless of version, you can change these options to "ESNext"
.
Now you will modify the Order page to include a hidden field for the unit price of the item being ordered, and some elements to act as containers for the result of the calculation. Add the highlighted lines just before the exising button:
<form class="col-9" method="post">
<div class="form-group mb-3">
<label asp-for="Quantity" class="form-label"></label>
<input asp-for="Quantity" class="form-control" />
<span asp-validation-for="Quantity"></span>
</div>
<div class="form-group mb-3">
<label asp-for="OrderEmail" class="form-label"></label>
<input asp-for="OrderEmail" class="form-control " />
<span asp-validation-for="OrderEmail"></span>
</div>
<div class="form-group mb-3">
<label asp-for="ShippingAddress" class="form-label"></label>
<textarea asp-for="ShippingAddress" class="form-control"></textarea>
<span asp-validation-for="ShippingAddress"></span>
</div>
<input type="hidden" asp-for="UnitPrice" value="@Model.Product.Price"/>
<div class="mb-3"> Total cost: <span id="orderTotal">@Model.Product.Price.ToString("c")</span></div>
<button type="submit" class="btn bn-sm btn-primary">Place order</button>
</form>
Next, add a new file to the wwwrooot/js folder named order.ts with the following content, which uses standard DOM APIs to reference the quantity and unit prices along with the span
you just added for the order total. It listens for the change event of the Quantity input and calculates the total price for the order based on the quantity specified, outputting the result ot the span:
const unitPriceElement = document.querySelector('#UnitPrice') as HTMLInputElement;
const quantityElement = document.querySelector('#Quantity') as HTMLInputElement;
const orderTotalElement = document.querySelector('#orderTotal') as HTMLSpanElement;
if(quantityElement && unitPriceElement && orderTotalElement){
quantityElement.addEventListener('change', _ => {
const unitPrice = Number(unitPriceElement.value);
const quantity = Number(quantityElement.value);
const orderTotal = unitPrice * quantity;
orderTotalElement.textContent = orderTotal.toLocaleString('en-GB', {
style: 'currency',
currency: 'GBP',
});
});
}
You may want to change the locale and currency settings passed to the Number.toLocaleString
function to support your culture.
Note
The DOM elements are cast to their specific type using the as
keyword, so that the value
property can be accessed. Without this casting, the types returned from the querySelector
method would be Element
, which only exposes those properties and methods that are common to all document elements.
Rather than manually compiling your TypeScript files every time you change one, you can initiate a TypeScript compiler process that continuously monitors files for changes. Whenever a file is modified, added, or deleted within the application, the TypeScript compiler recompiles the modified files and any dependent files. This process is initiated by executing the following command:
tsc --watch
Having executed this command, you should find that a new file has been added to the wwwroot/js folder - order.js. This is the output generated by the TypeScript compiler from the file you just authored. In this example, you should notice that the content of the generated JavaScript file is almost identical to the TypeScript version, with the type assertions removed.
Reference the file in the scripts
section of the Order page as follows, using the script taghelper with the append-version
attribute set to true
which will ensure that the latest version will always be downloaded to the client:
@section scripts{
<partial name="_ValidationScriptsPartial" />
<script src="~/js/order.js" asp-append-version="true"></script>
}
Then change the quantity value and check that the order total is updated:
Summary
In this section, you learned how to configure TypeScript in the application to manage your JavaScript files, then you added some logic to calcuate the total cost of the order interactively. At the moment, the user can only order one item at a time. In the next section, you will add a cookie-enabled shopping basket facility that tracks multiple orders for the user.
Next: Working with cookies in Razor Pages
Previous: Working With Forms