PageSourceSearch

https://johnnyreilly.com/assets/js/2ff1ed0f.6bb9b79e.js

js johnnyreilly.com collected 2026-09-24 19:32:11 UTC 15,951 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkjohnnyreilly_com=self.webpackChunkjohnnyreilly_com||[]).push([["2538"],{18588(e,t,n){n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>a,default:()=>d,frontMatter:()=>s,metadata:()=>i,toc:()=>c});var i=n(31195),r=n(74848),o=n(28453);let s={slug:"typescript-using-functions-with-union-types",title:"TypeScript: In Praise of Union Types",authors:"johnnyreilly",date:new Date("2015-01-20T00:00:00.000Z"),tags:["typescript"],hide_table_of_contents:!1,description:"TypeScript 1.4s Union Types offer a way to specify a value that is of one of many different types and results in a much terser definition file.",atUri:"at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.document/3mr5bsxtimw2g"},a,l={authorsImageUrls:[void 0]},c=[{value:"(&amp; How to Express Functions in UTs)",id:"-how-to-express-functions-in-uts",level:2},{value:"A little history",id:"a-little-history",level:2},{value:"That&#39;s right - the days before Union Types are now &quot;history&quot; :-)",id:"thats-right---the-days-before-union-types-are-now-history--",level:3},{value:"A new dawn",id:"a-new-dawn",level:2},{value:"State of the Union",id:"state-of-the-union",level:2},{value:"Bonfire of the Overloads",id:"bonfire-of-the-overloads",level:2}];function h(e){let t={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h2,{id:"-how-to-express-functions-in-uts",children:"(& How to Express Functions in UTs)"}),"\n",(0,r.jsxs)(t.p,{children:["Have you heard the good news my friend? I refer, of course, to the shipping of TypeScript 1.4 and my ",(0,r.jsx)(t.em,{children:"favourite"})," language feature since generics.... Union Types."]}),"\n",(0,r.jsxs)(t.p,{children:["In the ",(0,r.jsx)(t.a,{href:"https://blogs.msdn.com/b/typescript/archive/2015/01/16/announcing-typescript-1-4.aspx",children:"1.4 announcement"})," Jonathan Turner described Union Types thusly:"]}),"\n",(0,r.jsxs)(t.blockquote,{children:["\n",(0,r.jsx)(t.p,{children:"JavaScript functions may take a number of possible argument types. Up to now, we\u2019ve supported this using function overloads. Starting with TypeScript 1.4, we\u2019ve generalized this capability and now allow you to specify that that a value is one of a number of different types using a union type:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"function f(x: number | number[]) {\n  if (typeof x === 'number') {\n    return x + 10;\n  } else {\n    // return sum of numbers\n  }\n}\n"})}),"\n",(0,r.jsx)(t.p,{children:"Once you have a value of a union type, you can use a typeof and instanceof checks to use the value in a type-safe way. You'll notice we use this in the above example and can treat x as a number type inside of the if-block."}),"\n",(0,r.jsx)(t.p,{children:"Union types are a new kind of type and work any place you specify a type."}),"\n"]}),"\n",(0,r.jsx)(t.p,{children:"Lovely right? But what's missing? Well, to my mind, the most helpful aspect of Union Types. Definition file creation."}),"\n",(0,r.jsx)(t.h2,{id:"a-little-history",children:"A little history"}),"\n",(0,r.jsx)(t.h3,{id:"thats-right---the-days-before-union-types-are-now-history--",children:'That\'s right - the days before Union Types are now "history" :-)'}),"\n",(0,r.jsxs)(t.p,{children:["When creating definition files (",(0,r.jsx)(t.code,{children:"*.d.ts"}),') in the past there was a problem with TypeScript. A limitation. JavaScript often relies on "option bags" to pass configuration into a method. An "option bag" is essentially a JavaScript object literal which contains properties which are used to perform configuration. A good example of this is the ',(0,r.jsx)(t.code,{children:"route"})," parameter passed into Angular's ngRoute ",(0,r.jsx)(t.code,{children:'<a href="https://docs.angularjs.org/api/ngRoute/provider/$routeProvider#when">when</a>'})," method."]}),"\n",(0,r.jsx)(t.p,{children:"I'd like to draw your attention to 2 of the properties that can be passed in (quoted from the documentation):"}),"\n",(0,r.jsxs)(t.blockquote,{children:["\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsxs)(t.p,{children:["controller \u2013 ",(0,r.jsx)(t.code,{children:"{(string|function()=}"})," \u2013 Controller fn that should be associated with newly created scope or the name of a registered controller if passed as a string."]}),"\n"]}),"\n",(0,r.jsxs)(t.li,{children:["\n",(0,r.jsxs)(t.p,{children:["template \u2013 ",(0,r.jsx)(t.code,{children:"{string=|function()=}"})," \u2013 html template as a string or a function that returns an html template as a string which should be used by ngView or ngInclude directives. This property takes precedence over templateUrl."]}),"\n",(0,r.jsx)(t.p,{children:"If template is a function, it will be called with the following parameters:"}),"\n",(0,r.jsxs)(t.p,{children:[(0,r.jsx)(t.code,{children:"{Array.&lt;Object&gt;}"})," - route parameters extracted from the current $location.path() by applying the current route"]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(t.p,{children:"Both of these properties can be of more than 1 type."}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"controller"})," can be a ",(0,r.jsx)(t.code,{children:"string"}),(0,r.jsx)(t.em,{children:"or"})," a ",(0,r.jsx)(t.code,{children:"function"}),"."]}),"\n",(0,r.jsxs)(t.li,{children:[(0,r.jsx)(t.code,{children:"template"})," can be a ",(0,r.jsx)(t.code,{children:"string"}),(0,r.jsx)(t.em,{children:"or"})," a ",(0,r.jsx)(t.code,{children:"function"})," that returns a ",(0,r.jsx)(t.code,{children:"string"}
1)," and has ",(0,r.jsx)(t.code,{children:"$routeParams"})," as a parameter."]}),"\n"]}),"\n",(0,r.jsxs)(t.p,{children:["There's the rub. Whilst it was possible to overload functions in TypeScript pre 1.4, it was ",(0,r.jsx)("u",{children:"not"})]}),"\n",(0,r.jsxs)(t.p,{children:["possible to overload interface members. This meant the only way to model these sorts of properties was by seeking out a best common type which would fit all scenarios. This invariably meant using the ",(0,r.jsx)(t.code,{children:"any"})," type. Whilst that worked it didn't lend any consuming code a great deal of type safety. Let's look at a truncated version of ",(0,r.jsx)(t.a,{href:"https://github.com/borisyankov/DefinitelyTyped/blob/c71628e0765eb8e240d8eabd2225f64ea2e2fdb8/angularjs/angular-route.d.ts",children:(0,r.jsx)(t.code,{children:"angular-route.d.ts"})})," for these properties prior to union types:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"declare module ng.route {\n  // ...\n\n  interface IRoute {\n    /**\n     * {(string|function()=}\n     * Controller fn that should be associated with newly created scope or\n     * the name of a registered controller if passed as a string.\n     */\n    controller?: any;\n\n    /**\n     * {string=|function()=}\n     * Html template as a string or a function that returns an html template\n     * as a string which should be used by ngView or ngInclude directives. This\n     * property takes precedence over templateUrl.\n     *\n     * If template is a function, it will be called with the following parameters:\n     *\n     * {Array.<Object>} - route parameters extracted from the current\n     * $location.path() by applying the current route\n     */\n    template?: any;\n\n    // ...\n  }\n\n  // ...\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["It's ",(0,r.jsx)(t.code,{children:"any"})," city... Kind of sticks in the craw doesn't it?"]}),"\n",(0,r.jsx)(t.h2,{id:"a-new-dawn",children:"A new dawn"}),"\n",(0,r.jsxs)(t.p,{children:["TypeScript 1.4 has shipped and Union Types are with us. We can do better than ",(0,r.jsx)(t.code,{children:"any"}),". So what does ",(0,r.jsx)(t.a,{href:"https://github.com/borisyankov/DefinitelyTyped/blob/30ce45e0e706322f34608ab6fa5de141bba59c90/angularjs/angular-route.d.ts",children:(0,r.jsx)(t.code,{children:"angular-route.d.ts"})})," look like now we have Union Types?"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"declare module ng.route {\n  // ...\n\n  interface IRoute {\n    /**\n     * {(string|function()=}\n     * Controller fn that should be associated with newly created scope or\n     * the name of a registered controller if passed as a string.\n     */\n    controller?: string | Function;\n\n    /**\n     * {string=|function()=}\n     * Html template as a string or a function that returns an html template\n     * as a string which should be used by ngView or ngInclude directives. This\n     * property takes precedence over templateUrl.\n     *\n     * If template is a function, it will be called with the following parameters:\n     *\n     * {Array.<Object>} - route parameters extracted from the current\n     * $location.path() by applying the current route\n     */\n    template?:\n      | string\n      | { ($routeParams?: ng.route.IRouteParamsService): string };\n\n    // ...\n  }\n\n  // ...\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["With these changes in place we are now accurately modelling the ",(0,r.jsx)(t.code,{children:"route"})," option bags in TypeScript. Hoorah!!!"]}),"\n",(0,r.jsxs)(t.p,{children:["Let's dig in a little. If you look at the ",(0,r.jsx)(t.code,{children:"controller"})," definition it's pretty straightforward. ",(0,r.jsx)(t.code,{children:"string|Function"})," - clearly the ",(0,r.jsx)(t.code,{children:"controller"})," can be a ",(0,r.jsx)(t.code,{children:"string"}),(0,r.jsx)(t.em,{children:"or"})," a ",(0,r.jsx)(t.code,{children:"Function"}),". Simple."]}),"\n",(0,r.jsxs)(t.p,{children:["Now let's look at the ",(0,r.jsx)(t.code,{children:"template"})," definition by itself:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"template?: string | { ($routeParam
1s?: ng.route.IRouteParamsService) : string; }\n"})}),"\n",(0,r.jsxs)(t.p,{children:["As with the ",(0,r.jsx)(t.code,{children:"controller"})," the ",(0,r.jsx)(t.code,{children:"template"}),' can be a string - that is pretty clear. But what\'s that hovering on the other side of the "|"? What could ',(0,r.jsx)(t.code,{children:"{ ($routeParams?: ng.route.IRouteParamsService) : string; }"})," be exactly?"]}),"\n",(0,r.jsxs)(t.p,{children:["Well, in a word, it's a ",(0,r.jsx)(t.code,{children:"Function"}),". The ",(0,r.jsx)(t.code,{children:"controller"})," would allow any kind of function at all. However the ",(0,r.jsx)(t.code,{children:"template"})," definition is deliberately more restrictive. This defines a function which must return a ",(0,r.jsx)(t.code,{children:"string"})," and which receives an optional parameter of ",(0,r.jsx)(t.code,{children:"$routeParams"})," of type ",(0,r.jsx)(t.code,{children:"ng.route.IRouteParamsService"}),"."]}),"\n",(0,r.jsx)(t.h2,{id:"state-of-the-union",children:"State of the Union"}),"\n",(0,r.jsx)(t.p,{children:"Hopefully you can now see just how useful Union Types are and how you can express specific sorts of function definitions as part of a Union Type."}),"\n",(0,r.jsxs)(t.p,{children:["The thing that prompted me first to write this post was seeing that there don't appear to be any examples out there of how to express functions inside Union Types. I only landed on the syntax myself after a little experimentation in Visual Studio after I'd installed TS 1.4. I've started work on bringing Union Types to the typings inside ",(0,r.jsx)(t.a,{href:"https://github.com/borisyankov/DefinitelyTyped",children:"DefinitelyTyped"})," and so you'll start to see them appearing more and more. But since it's rather \"hidden knowledge\" at present I wanted to do my bit to make it a little better known."]}),"\n",(0,r.jsxs)(t.p,{children:["As ",(0,r.jsx)(t.a,{href:"https://twitter.com/Rickenhacker",children:"Daniel"})," helpfully points out in the comments there is an alternate syntax - lambda style. So instead of this:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"template?: string | { ($routeParams?: ng.route.IRouteParamsService) : string; }\n"})}),"\n",(0,r.jsx)(t.p,{children:"You could write this:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-ts",children:"template?: string | (($routeParams?: ng.route.IRouteParamsService) => string);\n"})}),"\n",(0,r.jsx)(t.p,{children:"Just remember to place parentheses around the lambda to clearly delineate it."}),"\n",(0,r.jsx)(t.h2,{id:"bonfire-of-the-overloads",children:"Bonfire of the Overloads"}),"\n",(0,r.jsx)(t.p,{children:'Before I sign off I should mention the ability Union Types give you to define a much terser definition file. Basically the "|" operator makes for a bonfire of the overloads. Where you previously may have had 6 overloads for the same method (each with identical JSDoc) you now only need 1. Which is beautiful (and DRY).'}),"\n",(0,r.jsxs)(t.p,{children:["It's surprising just what a difference it makes. This is ",(0,r.jsx)(t.a,{href:"https://github.com/borisyankov/DefinitelyTyped/blob/9bd7fe69d98337db56144c3da131d413f5b7e895/jquery/jquery.d.ts",children:(0,r.jsx)(t.code,{children:"jQuery.d.ts"})})," last week (pre TypeScript 1.4). This is ",(0,r.jsx)(t.a,{href:"https://github.com/borisyankov/DefinitelyTyped/blob/9f64372a065541fe2b8f6c5c5cd9b55a1d631f19/jquery/jquery.d.ts",children:(0,r.jsx)(t.code,{children:"jQuery.d.ts"})})," now - with Union Types aplenty. Last week it was ~4000 lines of code. This week it's ~3200 lines of code. With the same functionality. Union Types are ",(0,r.jsx)(t.em,{children:"FANTASTIC"}),"!"]})]})}function d(e={}){let{wrapper:t}={...(0,o.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},28453(e,t,n){n.d(t,{R:()=>s,x:()=>a});var i=n(96540);let r={},o=i.createContext(r);function s(e){let t=i.useContext(o);return i.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),i.createElement(o.Provider,{value:t},e.children)}},31195(e){e.exports=JSON.parse('{"permalink":"/typescript-using-functions-with-union-types","editUrl":"https://github.com/johnnyreilly/blog.johnnyreilly.com/edit/main/blog-website/blog/2015-01-20-typescript-using-functions-with-union-types/index.md","source":"@site/blog/2015-01-20-typescript-using-functions-with-union-types/index.md","title":"TypeScript: In Praise of Union Types","description":"TypeScript 1.4s Union Types offer a way to specify a value that is of one of many different types and results in a much terser definition file.","date":"2015-01-20T00:00:00.000Z","tags":[{"inline":false,"label":"TypeScript","permalink":"/tags/typescript","description":"The TypeScript programming language."}],"readingTime":6.06,"hasTruncateMarker":true,"authors":[{"name":"John Reilly","title":"OSS Engineer - TypeScript, Azure, React, Node.js, .NET","url":"https://johnnyreilly.com/about","imageURL":"https://johnnyreilly.com/img/profile-2025.jpg","key":"johnnyreilly","page":null}],"frontMatter":{"slug":"typescript-using-functions-with-union-types","title":"TypeScript: In Praise of Union Types","authors":"johnnyreilly","date":"2015-01-20T00:00:00.000Z","tags":["typescript"],"hide_table_of_contents":false,"description":"TypeScript 1.4s Union Types offer a way to specify a value that is of one of many different types and results in a much terser definition file.","atUri":"at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.document/3mr5bsxtimw2g"},"unlisted":false,"prevItem":{"title":"The Convent with Continuous Delivery","permalink":"/the-convent-with-continuous-delivery"},"nextItem":{"title":"Deploying from ASP.Net MVC to GitHub Pages using AppVeyor part 2","permalink":"/deploying-aspnet-mvc-to-github-pages-with-appveyor-part-2"}}')}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.