MEET HILTI
Hilti is known as an innovator in the building and construction space, but is less known for the digital innovations driving the industry forward.
Along with measuring and IoT, Hilti is creating innovative digital solutions that dramatically improve users’ and customer’s productivity.
Visit HILTI Hilti’s new Digital Platforms team co-located in Paris, France and Plano, Texas is proof of the investments being made in Hilti’s digital and technological future.
Get more about us! -Visit https://hilti.com, -Don't miss out the "Carrers" page: https://hilti.com/careers -Read the interview with Marc Dassler on the next page
INTERVIEW Marc Dassler Head of Hilti Digital Platforms
Marc Dassler, Head of Hilti Digital Platforms discusses how his team of software developers and product owners are architecting the future of digital success in the construction space.
I’m a tech guy from the start-up world. My background is in software development and robotics. But I started early on with my own businesses in eCommerce and studied in parallel. Fun fact, I joined a researcher group at the university for robots and I ended up of being two-time Robo Cup World Champion, which means that I trained robots to play football.
6
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
INTERVIEW Marc Dassler
How did a tech guy from the start-world – and the Robo Cup Work Champion - end up at a 75-year-old construction company? Well, it does seem a strange match at rst glance, but it says something about the direction Hilti is taking. The company doesn’t rest its laurels on their hardware innovations; they are really building a culture that is focused on both physical and digital solutions to customers’ barriers to productivity. The creation of the Digital Platforms team, which is a collective of software developers and architects, is all about technological innovation. And we are doing this more in a startup way than in a typical corporate approach. So, you’ve said Hilti is investing in digital, but why are you at the Angular Conference? Like I’ve mentioned, our Digital Platforms team is focused on innovative solutions, which is why most of our teams’ choice the Angular framework. It provides the structure our developers and architects need to iteratively and adaptively innovate. We also want to make sure we are supporting the communities that support us. Our team members are constantly learning from peers at developer conferences which strengthens our team and grows our community. Is it strange for a coder to work for what is largely known as a hardware and construction company? Actually, no. When you step into our of ces in Paris or Plano and see our scrum teams working you would be surprised to learn you were not in a technology company. Our culture, both within the Digital Platforms team and the rest of Hilti, has the innovative feel that is synonymous with tech.
ngPoland 2019 Special Edition | BEHIND THE CODE MAGAZINE
7
ARTICLE
Everything you should know about union types in TypeScript by Miłosz Piechocki Union types are the keystone of TypeScript’s type system. When compared to other mainstream languages such as Java or C#, TypeScript support for union types is astonishing. What makes them so important? Historically, union types were added to the language (in version 1.4) to support a common JavaScript pattern where a function could accept values of different types. For example, size function from lodash library accepts either an array, an object or a string as its parameter. However, the importance of union types grew much larger than that. They allow for much more precise types, they facilitate writing functional code, and they make you favour composition over inheritance. This article explores the topic of union types in depth. After reading it, you will learn multiple useful techniques and best practices related to unions that will vastly increase your productivity as a TypeScript programmer.
Fundamentals Let’s start with a practical explanation of what unit types are.
Basic example I’ve used many charting libraries throughout my career. One of the most popular ones is Highcharts. The API of this library is a good example of a pattern that is common amongst JavaScript libraries. 8
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
When you want to draw a chart with Highcharts, you need to call Highcharts.chart function which accepts an object with options as the second argument. Options objects let you specify various properties of the chart. For example pane.size property lets you set the size of the chart. If you look at the API reference (https://api.highcharts.com/highcharts/pane.size), it says that pane.size property can be either a string or a number. When you pass a number, it is interpreted as the number of pixels. When you pass a string, you can, for example, specify the size as the percentage of the size of the container (e.g. '80%'). Such API is not uncommon in the JavaScript world. It takes advantage of the dynamic nature of the language to provide a convenient and exible developer experience. How could such API be expressed in a statically typed language such as TypeScript? The answer is… union types. A union of two types is a type that accepts values from these two types. In case of pane.size, its type is number | string (as in De nitelyTyped). It means that both numbers and strings can be assigned to pane.size.
Composing types As we just saw, the necessity of introducing union types into TypeScript follows directly from the fact that JavaScript is a dynamically typed language. In other languages (such as Java or C#) such API would be much less convenient to use and would require a lot more code (for example, by creating a small type hierarchy). The beauty of union types is that their actual usefulness vastly exceeds the scenario mentioned above. Union types are one of the easiest ways to compose types. What does it mean? The composition is the act of creating big things from multiple smaller things. Union types let you create types from other types. Let’s look at another Highcharts example. The library lets you set the alignment of chart legends by specifying the legend.align property. As you can read in the API reference: Valid values are left, center and right. Of course legend.align could simply be typed as a string. However, with we can do much better with union types. De nitelyTyped types legend.align as follows:
align?: "left" | "center" | "right"; ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
9
Miłosz Piechocki
Here, the type of align is a union of three string literal types: "left", "center" and "right". A string literal type is a type that can only hold one possible value. String literal types are mostly useless on their own. However, they come very handy when composed with other types. In this example, union types combined with string literal types allow us to neatly express the fact that align is not just any string. It can only be one of the three strings - nothing more, nothing less. Such information is extremely valuable for the consumer of the library - they don’t even need to read the API reference any more. They can rely on TypeScript to make sure that you don’t pass invalid string.
Types as Sets Let’s now take a step back and learn some theory behind union types. This knowledge will give you some proper intuition about union types.
Relationship between types and sets
Computer science and mathematics overlap in many places. One of such places is type systems. A type, when looked at from a mathematical perspective, is a set of all possible values of that type. For example the string type is a set of all possible strings: {'a', 'b', 'ab', ...}. Of course, it’s an in nite set. Similarly, number type is a set of all possible numbers: {1, 2, 3, 4, ...}. Type unde ned is a set that only contains a single value: { unde ned }. What about object types (such as interfaces)? Type Foo is a set of all object that contain foo and xyz properties.
interface Foo { foo: string; xyz: string; } interface Bar { bar: string; xyz: string; } 10
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
Union type in terms of set theory Armed with this knowledge, you’re now ready to understand the meaning of union types. Union type A | B represents a set that is a union of the set of values associated with type A and the set of values associated with type B. Therefore, Foo | Bar represents a union of the set of objects having foo and xyz properties and the set of objects having bar and xyz. Objects belonging to such set all have xyz property. Some of them have foo property and the others have bar property.
Intersection types Understanding union types in the context of set theory makes it much easier to grasp intersection types. Intersection type A & B represents a set that is an intersection of the set of values associated with type A and the set of values associated with type B. Foo & Bar represents an intersection of the set of objects having foo and xyz properties and the set of objects having bar and xyz. In other words, the set contains objects that belong to sets represented by both Foo and Bar. Only objects that have all three properties (foo, bar and xyz) belong to the intersection.
Optionality One of the key applications of union types are optional properties and optional function parameters. Optionality is a very important concept as it lets you distinguish a situation when some value is required from when it can be skipped. Unintended optionality is a source of many errors in JavaScript (without types).
Optional properties
Optional properties in TypeScript are denoted by ?.
interface Person { name: string; spouse?: Person; } ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
11
Miłosz Piechocki
Person type has an optional property spouse and a required property name. An optional property can be skipped when creating an object marked as an instance of Person interface.
const person: Person = { name: "John" }; Optional function parameters Optional function parameters are also marked with ? character.
function sayHello(person: Person, greeting?: string) { console.log(`${greeting || 'Hello'}, ${person.name}`); } Such function can either be called with one or two parameters.
sayHello(person, "Good morning"); sayHello(person); Strict null checks What does it have to do with union types? Optional properties and parameters get way more useful when you enable the strictNullChecks compiler ag. When the ag is disabled, the type of Person.spouse is Person. This is a bit misleading. You know that spouse is optional, so it can be unde ned. However, nothing stops you from using in in an expression such as person.spouse.name. Such code may result in a runtime error! Fortunately, with the ag enabled, the type of Person.spouse is Person | unde ned. Guess what, a union type!
12
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
Let’s now take a look at the de nition of strictNullChecks in the documentation: In strict null checking mode, the null and unde ned values are not in the domain of every type and are only assignable to themselves and any (the one exception being that unde ned is also assignable to void). By default (when strictNullChecks is disabled) null and unde ned values are part of every type. It’s like every type is automatically unionized with null and unde ned types. Therefore, marking a property (or a parameter) optional doesn’t change its type! In terms of set theory, enabling strictNullChecks will stop null and unde ned from being automatically included in a set of possible values for every type. However, when the ag is enabled, this is no longer the case. Therefore, marking a property optional can be re ected by a change to its type. If the spouse were not optional, it’s type would be Person and it wouldn’t be possible to assign unde ned to it (or skip it). The type of optional spouse is, therefore, Person | unde ned.
Optional property vs explicit union with undefined You might wonder, is there any difference between marking a property as optional and explicitly typing it as a union of some type and unde ned?
interface Person { name: string; spouse: Person | unde ned; } In such de nition spouse property is not optional. You cannot skip it when creating a Person instance. However, you can still provide unde ned as a value.
// ۑProperty 'spouse' is missing in type '{ name: string; }' // but required in type 'Person'.ts(2741) const person: Person = { name: "John" };
ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
13
Miłosz Piechocki
// ܅No errors! const person: Person = { name: "John", spouse: unde ned }; Does it every make sense to use ... | unde ned instead of optional property? It does! The meaning of both versions is slightly different. Marking a property optional means that unde ned is a good default value for this property. This is not always desirable. Sometimes, not providing the property might have some serious consequences. In such cases, it would be better if the consumer of the type were forced to pass unde ned explicitly. Imagine that you’re implementing a function that fetches data from the backend. The function accepts a con guration object RequestSettings.
interface RequestSettings { url: string; method: 'GET' | 'POST'; authentication?: AuthSettings; } With such design, authentication is optional. If the property is skipped, no authentication method will be used when making the request. It might be a good idea to change the type of authentication to AuthSettings | unde ned. Such type would make it harder to skip the authentication property. If the user of this type wanted to skip authentication, they’d need to be 100% explicit about this and provide unde ned as the value of authentication.
Playing with optionality There are some useful built-in types that interact with the concept of optionality.
Partial Partial<T> returns a type that has the same properties as T but all of them are optional. Partial works on a single level - it doesn’t affect nested objects. A common use case for Partial is when you need to type a function that lets you override default values of properties of some object.
14
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
MiĹ&#x201A;osz Piechocki
const defaultSettings: Settings = { /* ... */ }; function getSettings(custom: Partial<Settings>): Partial<Settings> { return { ...defaultSettings, ...custom }; } Required Required<T> removes optionality from Tâ&#x20AC;&#x2122;s properties. Similarly to Required, Partial works on the top level only. The example is somehow symmetrical to the previous one. Here, we accept an object that has some optional properties. Then, we apply default values when a property is not present. The result is an object with no optional properties Required<Settings>.
function applySettings(settings: Settings) { const actualSettings: Required<Settings> = { width: settings.width || 100, height: settings.height || 200, title: settings.title || '', } // do something... } NonNullable NonNullable<T> removes null and unde ned from the set of possible values of T. It is mostly useful when working with strictNullChecks and optional properties and arguments. It has no effect on a type that is already not nullable.
type Foo = NonNullable<string | null | unde ned>; // string
ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
15
Miłosz Piechocki
Discriminated Union Types Let’s now take a look at a very interesting class of union types Discriminated Union Types (or tagged unions). This is one of the most powerful features of TypeScript. They let you create very precise types (hence, catch more bugs) and leverage static analysis to reduce type assertions in your code.
Issues with enum types
Let’s start by looking at an example of a problem that can be solved with discriminated unions. You’re working on an application which deals with management of customers. There are two kinds of customers: individual and institutional. For each customer kind, you store different details: individual customers have a rst and last name and a social security number. Companies have a company name and a tax identi er. You could model the above situation with the following types:
enum CustomerType { Individual, Institution } interface Customer { acquisitionDate: Date; type: CustomerType; rstName?: string; lastName?: string; socialSecurityNumber?: string; companyName?: string; companyTaxId?: number; } Unfortunately, you have to make most of the elds optional. If you didn’t, you would have to ll in all of the elds when creating an instance of Customer. However, you don’t want to ll companyTaxId when creating an Individual customer. The problem with this solution is that it’s now possible to create instances that don’t make any sense in terms of business domain. For example, you can create an object with too little info:
16
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
const customer1: Customer = { acquisitionDate: new Date(2016, 1, 1), type: CustomerType.Individual }; …or one that has too much data provided:
const customer2: Customer = { acquisitionDate: new Date(2016, 1, 1), type: CustomerType.Individual, rstName: "John", lastName: "Green", companyName: "Acme", companyTaxId: 9243546 }; Wouldn’t it be nice if the type system could help us prevent such situations? Actually, this is what TypeScript is supposed to do, right?
Discriminated unions to the rescue With discriminated unions, you can model your domain with more precision. They are kind of like enum types but can hold additional data as well. Therefore, you can enforce that a speci c customer type must have an exact set of elds. Let’s see it in action.
interface IndividualCustomerType { kind: "individual"; rstName: string; lastName: string; socialSecurityNumber: number; } interface InstitutionCustomerType { kind: "institutional"; companyName: string; companyTaxId: number; } ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
17
Miłosz Piechocki
type CustomerType = | IndividualCustomerType | InstitutionCustomerType; interface Customer { acquisitionDate: Date; type: CustomerType; } We’ve de ned two interfaces. Both of them have a kind property which is a literal type. Variable of literal type can only hold a single, speci c value. Each interface contains only elds that are relevant to the given type of customer. Finally, we’ve de ned CustomerType as a union of these two interfaces. Because they both have the kind eld TypeScript recognizes them as discriminated union types and makes working with them easier. The biggest gain is that it’s now impossible to create illegal instances of Customer. For example, both of the following objects are ne:
const customer1: Customer = { acquisitionDate: new Date(2016, 1, 1), type: { kind: "individual", rstName: "John", lastName: "Green", socialSecurityNumber: 423435 } }; const customer2: Customer = { acquisitionDate: new Date(2016, 1, 1), type: { kind: "institutional", companyName: "Acme", companyTaxId: 124345454 } };
18
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
…but TypeScript would fail to compile this one:
// ۑFails to compile const customer3: Customer = { acquisitionDate: new Date(2016, 1, 1), type: { kind: "institutional", companyName: "Acme", companyTaxId: 124345454, rstName: "John" } };
Working with discriminated unions Let’s now see how to implement a function that takes a Customer object and prints the customer’s name based on their type.
function printName(customer: Customer) { switch (customer.type.kind) { case "individual": return `${customer.type. rstName} ${customer.type.lastName}`; case "institutional": return customer.type.companyName; } } As we can see, TypeScript is clever enough to know that inside case "individual" branch of the switch statement customer.type is actually an instance of IndividualCustomerType. For example, trying to access companyName eld inside this branch would result in a compilation error. We would get the same behavior inside an if statement branch. There is one more interesting mechanism called exhaustiveness checking. TypeScript is able to gure out that we have not covered all of the possible customer types! Of course, it would seem much more useful if we had tens of them and not just two.
ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
19
Miłosz Piechocki
// fails to compile function printName(customer: Customer) { switch (customer.type.kind) { case "individual": return `${customer.type. rstName} ${customer.type.lastName}`; // case "institutional": return customer.type.companyName; default: const exhaustiveCheck: never = customer.type; } } This solution makes use of the never type. Since case "institutional" is not de ned, control falls through to the default branch in which customer.type is inferred to be of type InstitutionCustomerType while being assigned to never type which of course results in an error.
Recent improvements to union types Union types saw some exciting improvements in a recent TypeScript release (version 3.5). Let’s take a deeper look at them.
Improved excess property checks in union types First, let’s understand what excess property checks are. Normally in TypeScript, you can assign an object of a type that has some properties to a variable of a type that has a subset of those properties.
const employee: Employee = { name: "John", company: "ACME" } const person: Person = employee; // ܅ However, there is an exception to this rule. If you try to assign an object literal directly to person, TypeScript will complain:
// ۑObject literal may only specify known properties, and 'company' does not exist in type 'Person'. const person: Person = { name: "John", company: "ACME" }; This mechanism is called excess property checking. It has been introduced to help identify typos when passing an object with optional properties. Additionally, it’s sometimes desirable that an object doesn’t have any extra properties then those that are declared in the interface (for example, when you spread such object). 20
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
Excess property checking doesn’t work great with union types. Before version 3.5, the following code would not result in an error.
interface Person { name: string; } interface System { id: number; } declare function sendMessage(author: Person | System, message: string): void; sendMessage({ name: "John" }, "hello"); // ܅ sendMessage({ id: 123 }, "hello"); // ܅ sendMessage({ name: "John", id: "456" }, "hello"); // ܅ When checking the third invocation of sendMessage, TypeScript is not able to gure out that id is an excess property. Version 3.5 slightly improves the situation. Every property now has to be present in some union type member (and have the same type).
// ۑTypes of property 'id' are incompatible. sendMessage({ name: "John", id: "456" }, "hello"); Unfortunately, the following code is still valid as both properties (name and id) are present in some union member.
sendMessage({ name: "John", id: 456 }, "hello"); // ܅ It’s worth noting that excess property checking works much better with discriminated unions. In fact, it would be much better for sendMessage to accept a discriminated union anyway as it’s implementation should somehow discriminate between Person and System and behave differently in both cases.
ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
21
Miłosz Piechocki
Smarter union type checking This (rather subtle) improvement is related to discriminated unions. Are you familiar with ES6 iterators and generators? Starting from TypeScript version 3.6, generators are now strongly typed. One of the predecessors for this change was making IteratorResult interface (returned from Iterator.next method) a discriminated union.
type IteratorResult<T, TReturn = T> = // Returned when iterated sequence is not yet nished. | { done: false, value: T } // Returned when iterated sequence is nished. // The type of the last element (TReturn) can differ from // the type of other elements (T). | { done: true, value: TReturn }; Prior to TypeScript 3.5, the following code would fail with an error:
class NumberIterator { next() { return { done: false, value: 1 } } } // Built-in type, here a simpli ed version. interface Iterator<T, TReturn = T> { next(): IteratorResult<T, TReturn>; } // ۑerror TS2322: Type 'NumberIterator' is not assignable to type //'IteratorResult<number>'. // Type '{ done: boolean, value: number }' is not assignable to type '{ //done: true, value: number }'. let x: Iterator<number> = new NumberIterator(); The reason of the failure is that the return type of NumberIterator.next is inferred to { done: boolean, value: number } which is not assignable to IteratorResult.
22
BEHIND THE CODE MAGAZINE
|
ngPoland 2019 Special Edition
ARTICLE
Miłosz Piechocki
TypeScript 3.5 makes union type checking smarter in such a way that the type of the discriminating property (boolean in our case) will be broken down into its constituents and each one will be checked in isolation. Therefore, { done: boolean, value: number } will be viewed as { done: true, value: number } and { done: false, value: number } and will therefore match IteratorResult.
Summary Congratulations for getting this far! Wrapping up, you’ve learned about: • union types basics • type composition with union types • union types in terms of set theory • implementing optionality with union types • strict null checks • discriminated unions • recent changes to union types I hope that now you appreciate how powerful union types are in TypeScript. In fact, much more could be written about them… If you liked this article, feel free to check out my blog "Code with style" (https://codewithstyle.info/) and my short ebook about common TypeScript mistakes (https://typescriptmasterclass.com/). See you at the conference!
I've been writing (and reading) TypeScript code for almost 5 years. I compiled this document based on the hundreds of pull requests that I've reviewed, as well as on my own mistakes. I hope you like it! Miłosz Piechocki
ngPoland 2019 Special Edition
|
BEHIND THE CODE MAGAZINE
23
PREVIOUS PUBLICATIONS Check out the online archives of Behind the Code Magazine published by NG Poland:
Behind The Code Magazine #3 ngIndia edition 2019
Behind The Code Magazine #2 ngPoland 2018
See more: https://issuu.com/ngpoland