Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Typed Ember extends Confidence

Krystan HuffMenne
March 30, 2021
78

Typed Ember extends Confidence

As a developer with a non-traditional background, I struggled with imposter syndrome as I climbed the ranks to senior developer. This is the story of how converting our 7-year-old legacy Ember app to TypeScript in the throes of a pandemic helped me build confidence and write code that JustWorks™️. With Octane and native classes, using TypeScript with Ember is more straightforward than ever. Learn why you too should consider moving to TypeScript and tips for a painless transition. Not interested in switching? We'll talk about how to harness some of TypeScript's benefits even without TypeScript.

Krystan HuffMenne

March 30, 2021
Tweet

Transcript

  1. Krystan Hu
    ff
    Menne
    TypedEmber extends Confidence
    How to defeat imposter syndrome with TypeScript

    View Slide

  2. Maya Angelou
    “Uh-oh, they’re going to find out now. I’ve
    run a game on everybody, and they're
    going to find me out.”

    View Slide

  3. Elizabeth Cox, Educator
    “To call it a syndrome is to downplay how
    universal it is.”

    View Slide

  4. an observable fact or event
    phenomenon

    View Slide

  5. View Slide

  6. It's not an issue of competence.
    It's an issue of confidence.

    View Slide

  7. It me!

    View Slide

  8. Image: Marco Zecchin for SERA Architects (with permission)
    LinkedIn

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. Maybe I don’t belong here.

    View Slide

  14. Nevertheless, she persisted.

    View Slide

  15. View Slide

  16. View Slide

  17. 2020

    View Slide

  18. View Slide

  19. View Slide

  20. Eureka!

    View Slide

  21. TODO
    Surround yourself
    with supportive
    people.

    Own your
    accomplishments.

    Learn to take your
    mistakes in stride.

    See yourself as a
    work in progress.

    Train yourself not to
    need external
    validation.

    Realize there’s no
    shame in asking for
    help.

    Use positive
    a
    ffi
    rmations.

    Say “yes” to
    opportunities.

    Visualize success.

    Go to therapy.

    Do some yoga.

    Embrace feeling like
    an imposter.

    Decide to be
    con
    fi
    dent.

    View Slide

  22. Convert your app to TypeScript to foster
    confidence in your engineering team.
    👍👍

    View Slide

  23. Don’t panic.

    View Slide

  24. What even is a “type”?
    • What kind of data

    • What you can and cannot do

    View Slide

  25. primitive-types.js
    1;


    'Hello, EmberConf!';


    true;


    9007199254740991n;


    Symbol();


    undefined;


    null;


    number


    string


    boolean


    bigint


    symbol


    undefined


    null
    Primitives

    View Slide

  26. primitive-types.js
    typeof 1;


    //=> 'number'


    typeof 'Hello, EmberConf!';


    //=> 'string'


    typeof true;


    //=> 'boolean'


    typeof 9007199254740991n;


    //=> 'bigint'


    typeof Symbol();


    //=> 'symbol'


    typeof undefined;


    //=> 'undefined'


    typeof null;


    //=> 'object'


    null === null;


    //=> true
    👋 handwave 👋 handwave

    Making typeof null === 'null'
    would break the internet
    Primitives

    View Slide

  27. Structural types
    structural/object.js
    typeof { hello: 'EmberConf!' };


    //=> 'object'


    typeof ['Hello', 'EmberConf!'];


    //=> 'object'


    typeof new Set(['Hello', 'EmberConf!']);


    //=> 'object'


    typeof new Map([['Hello', 'EmberConf!']]);


    //=> 'object'


    class Tomster {}


    let tomster = new Tomster();


    typeof tomster;


    //=> 'object'
    object

    View Slide

  28. 👋 handwave 👋 handwave

    For framework code or when using iFrames, you might not want to use instanceof either.
    Structural types
    structural/object.js
    class Tomster {}


    let tomster = new Tomster();


    tomster instanceof Tomster;


    //=> true


    Array.isArray(['Hello', 'EmberConf!']);


    //=> true


    View Slide

  29. Structural types
    structural/function.js
    function hello(conf = 'EmberConf') {


    return `Hello, ${conf}!`;


    }


    typeof hello;


    //=> 'function'


    hello();


    //=> 'Hello, EmberConf!'


    hello.call(this, 'RailsConf');


    //=> 'Hello, RailsConf!'


    hello.hola = 'Hola!';


    hello.hola;


    'Hola!'


    object

    View Slide

  30. Wut?
    “JavaScript is a loosely
    typed and dynamic language.”

    View Slide

  31. loosely-typed/reassignment.js
    let year = 2021;


    typeof year;


    //=> 'number'


    year = 'two thousand and twenty one';


    typeof year;


    //=> 'string';


    Loosey goosey

    View Slide

  32. loosely-typed/coercion.js
    2 + 2;


    //=> 4


    2 + '2';


    //=> '22'


    2 + [2];


    //=> '22'


    2 + true;


    //=> 3


    2 + null;


    //=> 2


    2 + undefined;


    //=> NaN


    2 + new Set([2]);


    //=> '2[object Set]’
    Loosey goosey
    🤪

    View Slide

  33. My Unassuming JavaScript Website


    Thank you for coming to my website! I wrote some loosely typed and dynamic JavaScript.


    2 + 2 = 22 LOLOL


    Here’s a cat pic.


    /\_/\


    ( o.o )


    > ^ <


    www.mywebsite.com
    A real dynamo
    Uncaught TypeError: Cannot read property 'oops' of undefined
    Uncaught TypeError: undefined is not a function
    Uncaught TypeError: Cannot set property 'foo' of undefined

    View Slide

  34. Meme: KC Greene

    View Slide

  35. What could go wrong
    1. Uncaught TypeError: Cannot read property 'oops' of undefined


    2. TypeError: 'undefined' is not an object (evaluating 'undefined.oops')


    3. TypeError: null is not an object (evaluating 'null.oops')


    5. TypeError: Object doesn’t support property or method 'oops'


    6. Uncaught TypeError: undefined is not a function


    8. Uncaught TypeError: Cannot read property 'length' of undefined


    9. Uncaught TypeError: Cannot set property 'oops' of undefined


    10. ReferenceError: oops is not defined
    🥴

    View Slide

  36. Uncertainty


    is the enemy of


    confidence.

    View Slide

  37. View Slide

  38. JavaScript syntax
    shout.js
    function shout(message) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> 'HELLO'


    View Slide

  39. shout.ts
    function shout(message: string) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> 'HELLO'
    TypeScript syntax

    View Slide

  40. TypeScript compiler (tsc)
    shout.ts
    function shout(message: string) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> 'HELLO'
    Terminal
    $ yarn tsc shout.ts


    shout.js (compiled)
    function shout(message) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> 'HELLO'


    View Slide

  41. TypeScript compiler (tsc)
    shout.ts
    function shout(message: string) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> 'HELLO'


    shout(42);


    Terminal
    $ yarn tsc shout.ts


    $ yarn tsc shout.ts


    shout.ts:8:7 - error TS2345:
    Argument of type 'number' is not
    assignable to parameter of type
    'string'.


    8 shout(42);


    ~~


    Found 1 error.
    shout.js (compiled)
    function shout(message) {


    return message.toUpperCase();


    }


    shout('hello');


    //=> ‘HELLO'


    shout(42);


    //=> Uncaught TypeError:
    message.toUpperCase is not a
    function


    View Slide

  42. shout.ts
    TypeScript in your text editor

    View Slide

  43. shout.ts
    TypeScript in your text editor

    View Slide

  44. shout.ts
    TypeScript in your text editor

    View Slide

  45. dom-stuff.ts
    TypeScript in your text editor

    View Slide

  46. TypeScript is the answer key
    shout.ts
    shout.ts
    shout.ts

    View Slide

  47. JavaScript: loose, dynamic


    TypeScript: strict, static

    View Slide

  48. Statically Typed
    Terminal
    $ yarn tsc shout.ts


    shout.ts:8:7 - error TS2345:
    Argument of type 'number' is not
    assignable to parameter of type
    'string'.


    8 shout(42);


    ~~


    Found 1 error.

    View Slide

  49. statically-typed/inference.ts
    Statically Typed

    View Slide

  50. statically-typed/inference.ts
    Statically Typed

    View Slide

  51. statically-typed/annotation.ts
    Statically Typed

    View Slide

  52. statically-typed/annotation.ts
    Statically Typed

    View Slide

  53. Statically Typed*
    (*but also dynamically typed because it compiles to JavaScript)

    View Slide

  54. Don’t shoot the messenger.

    View Slide

  55. strict/reassignment-error.ts
    Strict

    View Slide

  56. Strict
    strict/coercion-error.ts

    View Slide

  57. strict/coercion-allowed.ts
    2 + '2';


    //=> '22'


    2 + document.querySelector('input[type="number"]').value;


    //=> '22'


    Strict, but not TOO strict
    😬

    View Slide

  58. strict/coercion-allowed.ts
    let itemCount = 42;


    throw 'Too many items in queue! Item count: ' + itemCount;


    //=> Error: Too many items in queue! Item count: 42


    Strict, but not TOO strict
    idiomatic: (adj.) everyone does it

    View Slide

  59. strict/coercion-allowed.ts
    Strict, but not TOO strict
    tscon
    fi
    g.json
    {


    "compilerOptions": {


    "noImplicitAny": true,


    "noImplicitThis": true,


    "alwaysStrict": true,


    "strictNullChecks": true,


    "strictPropertyInitialization": true,


    "noFallthroughCasesInSwitch": true,


    "noUnusedLocals": true,



    View Slide

  60. Strictness → fewer bugs → confidence

    View Slide

  61. primitives.ts
    let myVariable: number = 1;


    let myVariable: bigint = 9007199254740991n;


    let myVariable: string = 'Hello, EmberConf!';


    let myVariable: boolean = true;


    let myVariable: symbol = Symbol();


    let myVariable: undefined;


    let myVariable: null = null;


    Primitives in TypeScript

    View Slide

  62. primitives.ts
    let myVariable = 1;


    let myVariable = 9007199254740991n;


    let myVariable = 'Hello, EmberConf!';


    let myVariable = true;


    let myVariable = Symbol();


    let myVariable;


    let myVariable = null;


    Primitives in TypeScript

    View Slide

  63. structural/array.ts
    let myVariable: Array = ['Hello', 'EmberConf!'];


    Structural types in TypeScript
    generic: (adj.) a reusable type that takes another type as an argument

    View Slide

  64. structural/function.ts
    function sayHello(crowd: string): string {


    return `Hello, ${crowd}!`;


    }


    sayHello('EmberConf');


    //=> 'Hello, EmberConf!'


    Structural types in TypeScript

    View Slide

  65. structural/object.ts
    Structural types in TypeScript
    interface: (noun) a syntax that enforces the shape of an object

    View Slide

  66. Moar types!

    View Slide

  67. Additional-types/any.ts
    Into the void…

    View Slide

  68. additional-types/unknown.ts
    function prettyPrint(raw: unknown): string {


    if (typeof raw === 'string') {


    // TypeScript now knows that `raw` is a string


    return raw;


    }


    if (Array.isArray(raw)) {


    // TypeScript now knows that `raw` is an array


    return raw.join(', ');


    }


    throw '`prettyPrint` not implemented for this type';


    }
    Into the unknown…
    narrow: (verb) to re
    fi
    ne a type to a more speci
    fi
    c type

    View Slide

  69. Additional-types/any.ts
    let yolo: any = 'hehehe';


    // TypeScript won't yell at you here


    yolo = null;


    // or here


    yolo.meaningOfLife;


    //=> TypeError: Cannot read property 'meaningOfLife' of null


    Into the abyss…
    Pro tip: You can disallow any with tsconfig and eslint-typescript rules!

    View Slide

  70. Converting your legacy app:


    a meta-tutorial

    View Slide

  71. ember-super-rentals.netlify.app

    View Slide

  72. Ember + TypeScript = ?

    View Slide

  73. Ember Octane + TypeScript = 🧡

    View Slide

  74. Terminal
    $ ember install ember-cli-typescript


    🚧 Installing packages…


    ember-cli-typescript,


    typescript,


    @types/ember,


    @types/ember-data,


    Etc…


    create tsconfig.json


    create app/config/environment.d.ts


    create types/super-rentals/index.d.ts


    create types/ember-data/types/registries/model.d.ts


    create types/global.d.ts
    ember-cli-typescript

    View Slide

  75. Terminal
    $ ember install ember-cli-typescript


    🚧 Installing packages…


    ember-cli-typescript,


    typescript,


    @types/ember,


    @types/ember-data,


    Etc…


    create tsconfig.json


    create app/config/environment.d.ts


    create types/super-rentals/index.d.ts


    create types/ember-data/types/registries/model.d.ts


    create types/global.d.ts


    Installed addon package.
    ember-cli-typescript

    View Slide

  76. Gradual typing hacks

    View Slide

  77. Gradual typing hacks

    View Slide

  78. Gradual typing hacks
    • TypeScript declaration
    fi
    les (.d.ts)

    • unknown


    • any


    • @ts-expect-error

    View Slide

  79. tscon
    fi
    g.json
    {


    "compilerOptions": {


    // "alwaysStrict": true,


    // "noImplicitAny": true,


    // "noImplicitThis": true,


    // "strictBindCallApply": true,


    // "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  80. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    // "noImplicitAny": true,


    // "noImplicitThis": true,


    // "strictBindCallApply": true,


    // "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  81. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    // "noImplicitThis": true,


    // "strictBindCallApply": true,


    // "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  82. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    "noImplicitThis": true,


    // "strictBindCallApply": true,


    // "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // …


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  83. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    "noImplicitThis": true,


    "strictBindCallApply": true,


    // "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  84. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    "noImplicitThis": true,


    "strictBindCallApply": true,


    "strictFunctionTypes": true,


    // "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  85. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    "noImplicitThis": true,


    "strictBindCallApply": true,


    "strictFunctionTypes": true,


    "strictNullChecks": true,


    // "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  86. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "alwaysStrict": true,


    "noImplicitAny": true,


    "noImplicitThis": true,


    "strictBindCallApply": true,


    "strictFunctionTypes": true,


    "strictNullChecks": true,


    "strictPropertyInitialization": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  87. tscon
    fi
    g.json
    {


    "compilerOptions": {


    "strict": true,


    // ...


    }


    }


    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Less strict More strict

    View Slide

  88. Terminal
    $ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
    Gradual strictness
    Or: How I Learned to Stop Worrying and Love the Strictness
    Ludicrous mode!
    🤯
    Less strict More strict

    View Slide

  89. Where do we start?

    View Slide

  90. Where do we start?
    Outer Leaves

    Models
    Routes, Services, Adapters, Serializers
    Inner Leaves

    Components, Controllers

    View Slide

  91. Where do we start?
    Outer Leaves

    Models
    Routes, Services, Adapters, Serializers
    Inner Leaves

    Components, Controllers

    View Slide

  92. Where do we start?
    Outer Leaves

    Models
    Routes, Services, Adapters, Serializers
    Inner Leaves

    Components, Controllers
    👉
    👇
    👈

    View Slide

  93. Where do we start?
    Outer Leaves

    Models
    Routes, Services, Adapters, Serializers
    Inner Leaves

    Components, Controllers
    🥸
    🤠 🤩
    🤡

    View Slide

  94. Where do we start?
    Outer Leaves

    Models
    Routes, Services, Adapters, Serializers
    Inner Leaves

    Components, Controllers
    🥸
    🤠 🤩
    🤡
    👉
    👇
    👈

    View Slide

  95. app/models/rental.js
    import Model, { attr } from '@ember-data/model';


    const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment'];


    export default class RentalModel extends Model {


    @attr title;


    @attr owner;


    @attr city;


    @attr location;


    @attr category;


    @attr image;


    @attr bedrooms;


    @attr description;


    get type() {


    if (COMMUNITY_CATEGORIES.includes(this.category)) {


    return 'Community';


    } else {


    return 'Standalone';


    }


    }


    }
    Models

    View Slide

  96. app/models/rental.js
    import Model, { attr } from '@ember-data/model';


    const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment'];


    export default class RentalModel extends Model {


    @attr title;


    @attr owner;


    @attr city;


    @attr location;


    @attr category;


    @attr image;


    @attr bedrooms;


    @attr description;


    get type() {


    if (COMMUNITY_CATEGORIES.includes(this.category)) {


    return 'Community';


    } else {


    return 'Standalone';


    }


    }


    }
    Models

    View Slide

  97. app/models/rental.js
    import Model, { attr } from '@ember-data/model';


    const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment'];


    export default class RentalModel extends Model {


    @attr title;


    @attr owner;


    @attr city;


    @attr location;


    @attr category;


    @attr image;


    @attr bedrooms;


    @attr description;


    get type() {


    if (COMMUNITY_CATEGORIES.includes(this.category)) {


    return 'Community';


    } else {


    return 'Standalone';


    }


    }


    }
    Models

    View Slide

  98. app/models/rental.js
    import Model, { attr } from '@ember-data/model';


    const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment'];


    export default class RentalModel extends Model {


    @attr title;


    @attr owner;


    @attr city;


    @attr location;


    @attr category;


    @attr image;


    @attr bedrooms;


    @attr description;


    get type() {


    if (COMMUNITY_CATEGORIES.includes(this.category)) {


    return 'Community';


    } else {


    return 'Standalone';


    }


    }


    }
    Models
    Terminal
    $ mv app/models/rental.js app/models/rental.ts

    View Slide

  99. app/models/rental.ts
    import Model, { attr } from '@ember-data/model';


    const COMMUNITY_CATEGORIES = ['Condo', 'Townhouse', 'Apartment'];


    export default class RentalModel extends Model {


    @attr title;


    @attr owner;


    @attr city;


    @attr location;


    @attr category;


    @attr image;


    @attr bedrooms;


    @attr description;


    get type() {


    if (COMMUNITY_CATEGORIES.includes(this.category)) {


    return 'Community';


    } else {


    return 'Standalone';


    }


    }


    }
    Models

    View Slide

  100. app/models/rental.ts
    Models

    View Slide

  101. app/models/rental.ts
    Models

    View Slide

  102. app/models/rental.ts
    Models
    public/api/rentals.json
    {


    "data": [


    {


    "type": "rentals",


    "id": "grand-old-mansion",


    "attributes": {


    "title": "Grand Old Mansion",


    "owner": "Veruca Salt",


    "city": "San Francisco",


    "location": {


    "lat": 37.7749,


    "lng": -122.4194


    },


    "category": "Estate",


    "image": "https://upload.wikimedia.org/mansion.jpg",


    "bedrooms": 15,


    "description": "This grand old mansion sits..."


    }


    },


    // ...


    ]


    }


    View Slide

  103. app/models/rental.ts
    Models

    View Slide

  104. app/models/rental.ts
    Models

    View Slide

  105. app/models/rental.ts
    Models

    View Slide

  106. app/models/rental.ts
    Models

    View Slide

  107. app/models/rental.ts
    Models

    View Slide

  108. app/models/rental.ts
    Models

    View Slide

  109. app/models/rental.ts
    Models

    View Slide

  110. app/models/rental.ts
    Models

    View Slide

  111. app/models/rental.ts
    Models

    View Slide

  112. app/models/rental.ts
    Models

    View Slide

  113. app/models/rental.ts
    Models
    Terminal
    $ git commit -a -m “Convert Rental Model to TS”

    View Slide

  114. relationship-example.ts
    import Model, {


    AsyncBelongsTo,


    AsyncHasMany,


    belongsTo,


    hasMany,


    } from '@ember-data/model';


    import Comment from 'my-app/models/comment';


    import User from 'my-app/models/user';


    export default class PostModel extends Model {


    @belongsTo('user') declare author: AsyncBelongsTo;


    @hasMany('comments') declare comments: AsyncHasMany;


    }
    Model Relationships

    View Slide

  115. app/routes/index.js
    import Route from '@ember/routing/route';


    import { inject as service } from '@ember/service';


    export default class IndexRoute extends Route {


    @service store;


    model() {


    return this.store.findAll('rental');


    }


    }


    Routes

    View Slide

  116. app/routes/index.js
    import Route from '@ember/routing/route';


    import { inject as service } from '@ember/service';


    export default class IndexRoute extends Route {


    @service store;


    model() {


    return this.store.findAll('rental');


    }


    }


    Routes
    Terminal
    $ mv app/routes/index.js app/routes/index.ts

    View Slide

  117. app/routes/index.ts
    Routes

    View Slide

  118. app/routes/index.ts
    Routes

    View Slide

  119. app/routes/index.ts
    Routes

    View Slide

  120. app/routes/index.ts
    Routes

    View Slide

  121. app/routes/index.ts
    Routes

    View Slide

  122. app/routes/index.ts
    Routes

    View Slide

  123. app/routes/index.ts
    Routes
    app/models/rental.ts
    import Model, { attr } from '@ember-data/model';


    export default class RentalModel extends Model {


    // ...


    }


    declare module 'ember-data/types/registries/model' {


    export default interface ModelRegistry {


    rental: RentalModel;


    }


    }


    View Slide

  124. app/routes/index.ts
    Routes

    View Slide

  125. app/routes/index.ts
    Routes

    View Slide

  126. app/routes/index.ts
    Routes
    Terminal
    $ git commit -a -m “Convert Index Route to TS”

    View Slide

  127. Components
    ember-super-rentals.netlify.app

    View Slide

  128. app/components/rentals/
    fi
    lter.js
    import Component from '@glimmer/component';


    export default class RentalsFilterComponent extends Component {


    get results() {


    let { rentals, query } = this.args;


    if (query) {


    rentals = rentals.filter((rental) => rental.title.includes(query));


    }


    return rentals;


    }


    }


    Components
    Terminal
    $ mv app/components/rentals/filter.js app/components/rentals/filter.ts

    View Slide

  129. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  130. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  131. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  132. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  133. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  134. app/components/rentals/
    fi
    lter.ts
    Components
    app/components/rentals.hbs



    {{#each results as |rental|}}





    {{/each}}



    app/templates/index.hbs

    View Slide

  135. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  136. app/components/rentals/
    fi
    lter.ts
    Components
    🐛

    View Slide

  137. app/components/rentals/
    fi
    lter.ts
    Components
    🐛

    View Slide

  138. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  139. app/components/rentals.ts
    Components
    import Component from '@glimmer/component';


    import { tracked } from '@glimmer/tracking';


    import IndexRoute from 'super-rentals/routes';


    import { ModelFrom } from 'super-rentals/types/util';


    interface RentalsArgs {


    rentals: ModelFrom;


    }


    export default class RentalsComponent extends Component {


    @tracked query = '';


    }


    View Slide

  140. app/components/rentals/
    fi
    lter.ts
    Components

    View Slide

  141. app/components/rentals/
    fi
    lter.ts
    Components
    Terminal
    $ git commit -a -m “Convert Rentals::Filter to TS”

    View Slide

  142. Components app/components/map.js
    import Component from '@glimmer/component';


    import ENV from 'super-rentals/config/environment';


    export default class MapComponent extends Component {


    get src() {


    let { lng, lat } = this.args;


    return `${ENV.MAPBOX_URL}/${lng},${lat}`;


    }


    }


    View Slide

  143. Components app/components/map.js
    import Component from '@glimmer/component';


    import ENV from 'super-rentals/config/environment';


    export default class MapComponent extends Component {


    get src() {


    let { lng, lat } = this.args;


    return `${ENV.MAPBOX_URL}/${lng},${lat}`;


    }


    }


    Terminal
    $ mv app/components/map.js app/components/map.ts

    View Slide

  144. Components app/components/map.ts

    View Slide

  145. Components app/components/map.ts

    View Slide

  146. Components app/components/map.ts
    app/components/rental.hbs


    @lat={{@rental.location.lat}}


    @lng={{@rental.location.lng}}


    />


    View Slide

  147. Components app/components/map.ts
    Thinking in
    TypeScript

    View Slide

  148. Components app/components/map.ts
    Thinking in
    TypeScript

    View Slide

  149. Components app/components/map.ts
    Thinking in
    TypeScript
    (Once we have template type-checking,
    this won’t be necessary.)

    View Slide

  150. Components app/components/map.ts
    Terminal
    $ git commit -a -m “Convert Map to TS”

    View Slide

  151. View Slide

  152. app/components/map.js
    /**


    * @typedef {object} MapArgs


    * @property {number | undefined} lng


    * @property {number | undefined} lat


    */


    /** @type {Component} */


    export default class MapComponent extends Component {


    /** @type {number} */


    get lng() {


    assert('Please provide `lng` arg', this.args.lng);


    return this.args.lng;


    }


    /** @type {number} */


    get lat() {


    assert('Please provide `lat` arg', this.args.lat);


    return this.args.lat;


    }


    /** @type {string} */


    get src() {


    return `${ENV.MAPBOX_URL}/${this.lng},${this.lat}`;


    }


    }
    TS without TS
    JSDoc + VSCode

    View Slide

  153. app/components/map.js
    // @ts-check


    /**


    * @typedef {object} MapArgs


    * @property {number | undefined} lng


    * @property {number | undefined} lat


    */


    /** @type {Component} */


    export default class MapComponent extends Component {


    /** @type {number} */


    get lng() {


    assert('Please provide `lng` arg', this.args.lng);


    return this.args.lng;


    }


    /** @type {number} */


    get lat() {


    assert('Please provide `lat` arg', this.args.lat);


    return this.args.lat;


    }


    /** @type {string} */


    get src() {


    return `${ENV.MAPBOX_URL}/${this.lng},${this.lat}`;


    }


    }
    TS without TS
    JSDoc + VSCode

    View Slide

  154. The real benefits of TypeScript

    View Slide

  155. Confidence


    to refactor the crufty stuff

    View Slide

  156. Confidence


    that your code will JustWorkTM

    View Slide

  157. Confidence


    to contribute to open source

    View Slide

  158. Confidence


    to try other strictly typed languages

    View Slide

  159. Confidence


    to make you listen to this talk

    View Slide