Categories
Express JavaScript Nodejs

Add Basic Authentication to an Express App

To add basic auth capabilities to an Express app, we can use the express-basic-auth package.

With it, we can check for a given username and password in the URL in our protected routes.

In this article, we’ll take a look at how to use it.

How to Install

express-basic-auth is available as a Node package, we can install it by running:

npm install express-basic-auth

Basic Usage

We can use it as follows:

const express = require('express');  
const basicAuth = require('express-basic-auth')  
const app = express();
app.use(basicAuth({  
    users: { 'admin': 'supersecret' }  
}))

app.get('/', (req, res) => {  
  res.send('authorized');  
});

app.listen(3000, () => console.log('server started'));

In the code above, we included the basicAuth middleware from express-basic-auth from in our app.

Then we can call app.use with an object that has the users property with the usernames as the keys and the corresponding passwords as the values.

Then if we make a GET request to a URL like:

https://admin:supersecret@ThistleDarkExponents--five-nine.repl.co

Then we’ll get the authorized response back since the username and password matches what we listed in the object in our app.

Otherwise, we’ll get a 401 response and a configurable body, which is empty by default.

Custom Authorization

We can also pass in our own authorizer function to check for credentials that we want.

We shouldn’t use standard string comparison with == or === when comparing user input with sercet credentials since it’ll make our app vulenrable to timing attacks.

Timing attacks are a side-channel attack where the attacker tries to compromise a system by analyzing the time taken to run cryptographic algorithms.

We should use the safeCompare method provided by the package instead.

Also, we should use bitwise logic operators instead of standard ones for the same reason.

For example, we can use the following:

const express = require('express');  
const basicAuth = require('express-basic-auth')  
const app = express();
app.use(basicAuth({  
  authorizer: (username, password) => {  
    const userMatches = basicAuth.safeCompare(username, 'admin')  
    const passwordMatches = basicAuth.safeCompare(password, 'supersecret')  
    return userMatches & passwordMatches  
  }  
}))

app.get('/', (req, res) => {  
  res.send('authorized');  
});

app.listen(3000, () => console.log('server started'));

In the code above, we have a function that calls safeCompare to match the username and password respectively and then use the AND bitwise operator to combine the 2 to make sure that both are true .

Async Authorization

We can set the authorizeAsync option to true and set a function with a cb parameter for the callback as the authorizer function to check credentials asynchronously:

const express = require('express');  
const basicAuth = require('express-basic-auth')  
const app = express();
app.use(basicAuth({  
  authorizer: (username, password, cb) => {  
    const userMatches = basicAuth.safeCompare(username, 'admin')  
    const passwordMatches = basicAuth.safeCompare(password, 'supersecret')  
    if (userMatches & passwordMatches)  
      return cb(null, true)  
    else  
      return cb(null, false)  
  },  
  authorizeAsync: true,  
}))

app.get('/', (req, res) => {  
  res.send('authorized');  
});

app.listen(3000, () => console.log('server started'));

We return the cb callback call instead of returning the result directly in our authorizer function.

Unauthorized Response Body

We can set the unauthorizedResponse property to a function that returns a response that we want to show when a credential check failed.

For example, we can write:

const express = require('express');  
const basicAuth = require('express-basic-auth')  
const app = express();  
app.use(basicAuth({  
  users: { 'admin': 'supersecret' },  
  unauthorizedResponse: (req) => {  
    return `unauthorized. ip: ${req.ip}`  
  }  
}))  
app.get('/', (req, res) => {  
  res.send('authorized');  
});  
app.listen(3000, () => console.log('server started'));

to return something like:

unauthorized. ip: ::ffff:172.18.0.1

when basic authentication fails.

The function we set unauthorizedResponse to takes the Express request object, so we can use any properties from there in the function.

Challenge

We can make browsers show a popup so that users can enter credentials for authentication by add a challenge: true option to the object.

In addition, we set the realm to identify the system to authenticate against and can be used to save credentials of the challenge by passing a static or a function that gets passed the request object and is expected to return the challenge:

const express = require('express');  
const basicAuth = require('express-basic-auth')  
const app = express();app.use(basicAuth({  
  users: { 'admin': 'supersecret' },  
  challenge: true,  
  realm: 'foo',  
}))

app.get('/', (req, res) => {  
  res.send('authorized');  
});

app.listen(3000, () => console.log('server started'));

With the code above, we should get a dialog box to enter the username and password when we load the / page.

Conclusion

Adding basic auth to an Express app is easy with the express-basic-auth package.

It lets us add a list of valid credentials or use a function to validate credentials.

We can also let users enter the username and password and display custom content when the user is unauthorized.

Categories
JavaScript Vue

How to Create Vue Directive

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how to make our directives accept arguments and values to directives and use them in our code.

Dynamic Directive Arguments

We can pass in arguments and values to a directive and then get these values from the binding parameter.

For example, we can make a directive that takes an argument and value as follows:

src/index.js :

Vue.directive("position", {  
  bind(el, binding, vnode) {  
    const validPositions = ["relative", "fixed", "absolute"];  
    if (validPositions.includes(binding.arg)) {  
      el.style.position = binding.arg;  
      el.style.top = `${binding.value}px`;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    message: "Hello"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p v-position:absolute="50">{{message}}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we defined a positiion directive that takes an argument, which is accessed in the bind hook by using the binding parameter.

The binding parameter has an arg property to get the argument, which is absolute in index.html .

binding.value gets us the value, which is 50 in index.html .

Then we get the element that the directive is bound to with el and then we set the positioning of the element via the values from the binding parameter.

Accepting arguments and values make directives much more flexible than without arguments.

Function Shorthand

If we only have code in the bind and update hooks, then we can just pass in a function with the same signature as those hooks.

For example, we can shorten the example above to:

Vue.directive("position", (el, binding, vnode) => {  
  const validPositions = ["relative", "fixed", "absolute"];  
  if (validPositions.includes(binding.arg)) {  
    el.style.position = binding.arg;  
    el.style.top = `${binding.value}px`;  
  }  
});

They both do the same thing.

Object Literals

We can pass in an object literal as the value of a directive if our directive needs to accept multiple values.

For example, we can write the following code to accept an object’s properties’ values in our directive:

src/index.js:

Vue.directive("position", (el, binding, vnode) => {  
  const validPositions = ["relative", "fixed", "absolute"];  
  const { position, top } = binding.value;  
  if (validPositions.includes(position)) {  
    el.style.position = position;  
    el.style.top = top;  
  }  
});

new Vue({  
  el: "#app",  
  data: {  
    message: "Hello"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p v-position="{ position: 'absolute', top: '50px' }">{{message}}</p>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above accepts an object with the position and top property and we take the object’s values and then set them as the style of the p element which the position directive is bound to.

A directive’s value can be any valid JavaScript expression.

Conclusion

Vue directives can take arguments and values. We can get argument values from the binding parameter of the bind hook via the binding.arg property.

To get the value passed to a directive, we can get it from the bind hook’s binding parameter via the binding.value property.

If our directive only has bind or update hooks, then we can shorten it to a function, which has the same signature as the bind or update hooks.

Categories
JavaScript Vue

Vue Components — Dynamic Slot Names and Shorthands

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at add dynamic slot names.

Dynamic Slot Names

Since Vue.js 2.6.0, we can pass in dynamic slot names with v-slot . For example, we can use it as follows:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user" name='first-name'></slot>  
    <slot v-bind:user="user" name='last-name'></slot>  
  </p>`  
});

new Vue({  
  el: "#app",  
  data: {  
    firstname: "first-name",  
    lastname: "last-name"  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template v-slot:[firstname]="{ user }">  
          {{ user.firstName }}  
        </template>  
        <template v-slot:[lastname]="{ user }">  
          {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, firstname and lastname in data are set as the names of the slots in our root template.

Named Slots Shorthand

v-slot has a shorthand. We can shorten v-slot: to the symbol # . For example, we can rewrite our example as follows:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user" name='first-name'></slot>  
    <slot v-bind:user="user" name='last-name'></slot>  
  </p>`  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template #first-name="{ user }">  
          {{ user.firstName }}  
        </template>  
        <template #last-name="{ user }">  
          {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

For referencing the default slot, we have to write #default . For example, we write:

src/index.js :

Vue.component("user", {  
  data() {  
    return {  
      user: {  
        firstName: "Joe",  
        lastName: "Smith"  
      }  
    };  
  },  
  template: `<p>  
    <slot v-bind:user="user"></slot>      
  </p>`  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <user>  
        <template #default="{ user }">  
          {{ user.firstName }} {{ user.lastName }}  
        </template>  
      </user>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Use with v-for

We can add a slot to provided fallback content in lists and allow us to change the content in the parent. For example, we can write the following:

src/index.js :

Vue.component("people", {  
  data() {  
    return {  
      persons: [  
        { name: "Joe", isAdult: true },  
        { name: "Jane", isAdult: false }  
      ]  
    };  
  },  
  template: `  
    <ul>    
      <li v-for='person of persons'>  
        <slot v-bind:person="person"></slot>      
      </li>  
    </ul>  
  `  
});

new Vue({  
  el: "#app"  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  
  <body>  
    <div id="app">  
      <people v-bind:persons="persons">  
        <template #default="{ person }">  
          {{person.name}}  
          <span v-if="person.isAdult"> - Adult</span>  
          <span v-else> - Child</span>  
        </template>  
      </people>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the people component with persons data. We rendered persons with v-for in the people component and make the entries available via v-bind :

<slot v-bind:person="person"></slot>

Then in the root template, we have:

<people v-bind:persons="persons">  
  <template #default="{ person }">  
    {{person.name}}  
    <span v-if="person.isAdult"> - Adult</span>  
    <span v-else> - Child</span>  
  </template>  
</people>

to get person from people and then provide a template to display the entries as we wish without modifying the people component.

Conclusion

Since Vue 2.6.0, we can use dynamic slot names instead of hard-coded ones.

Also, the shorthand for named slots is # . For default slots, we always have to write #default .

Finally, we can use it with v-for to let us define a template that can be used with each entry. We use v-bind on the entry of the array instead of the whole array and then we can access that data in the parent component and add a templarte to display data the way we like by defining it in the parent.

Categories
JavaScript

JavaScript Events Handlers — Metadata and Ajax Events

In JavaScript, events are actions that happen in an app. They are triggered by various things like inputs being entered, forms being submitted, changes in an element like resizing, or errors that happen when an app is running, etc. We can assign an event handler to take action on these events.

Events that happen to DOM elements can be handled by assigning an event handler to properties of the DOM object for the corresponding events. In this article, we will look at the onloadedmetadata property of a media DOM element, and the onloadend property of an XmlHttpRequest object.

onloadedmetadata

The onloadedmetadata property of a media DOM element lets us set an event handler function that’s run when the loademetadata event is fired. Whenever media metadata is loaded this event will fire.

For example, we can use it by first adding a video tag to our HTML code:

<video src='https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4'></video>

Then in the corresponding JavaScript code, we can set an event handler function to the onloadedmetadata property as we do in the following code:

const video = document.querySelector('video');
video.onloadedmetadata = (e) => {  
  console.log(e);   
}

Then we get the video’s metadata in the srcElement property of the e parameter, which is an Event object. We get something like:

clientHeight: 240  
clientLeft: 0  
clientTop: 0  
clientWidth: 320  
contentEditable: "inherit"  
controls: false  
controlsList: DOMTokenList [value: ""]  
crossOrigin: null  
currentSrc: "https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4"  
currentTime: 0  
dataset: DOMStringMap {}  
defaultMuted: false  
defaultPlaybackRate: 1  
dir: ""  
disablePictureInPicture: false  
disableRemotePlayback: false  
draggable: false  
duration: 368.2  
elementTiming: ""  
ended: false  
enterKeyHint: ""  
error: null  
firstChild: null  
firstElementChild: null  
height: 0  
hidden: false  
id: ""  
innerHTML: ""  
innerText: ""  
inputMode: ""  
isConnected: true  
isContentEditable: false  
lang: ""  
lastChild: null  
lastElementChild: null  
localName: "video"  
loop: false  
mediaKeys: null  
muted: false  
namespaceURI: "[http://www.w3.org/1999/xhtml](http://www.w3.org/1999/xhtml)"  
networkState: 1  
nextElementSibling: script  
nextSibling: text  
nodeName: "VIDEO"  
nodeType: 1  
nodeValue: null  
nonce: ""  
offsetHeight: 240  
offsetLeft: 8  
offsetParent: body  
offsetTop: 8  
offsetWidth: 320  
...  
outerHTML: "<video src="https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4"></video>"  
outerText: ""  
ownerDocument: document  
parentElement: body  
parentNode: body  
part: DOMTokenList [value: ""]  
paused: true  
playbackRate: 1  
played: TimeRanges {length: 0}  
playsInline: false  
poster: ""  
prefix: null  
preload: "metadata"  
previousElementSibling: null  
previousSibling: text  
readyState: 4  
remote: RemotePlayback {state: "disconnected", onconnecting: null, onconnect: null, ondisconnect: null}  
scrollHeight: 240  
scrollLeft: 0  
scrollTop: 0  
scrollWidth: 320  
seekable: TimeRanges {length: 1}  
seeking: false  
shadowRoot: null  
sinkId: ""  
slot: ""  
spellcheck: true  
src: "https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4"  
srcObject: null  
style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}  
tabIndex: -1  
tagName: "VIDEO"  
textContent: ""  
textTracks: TextTrackList {length: 0, onchange: null, onaddtrack: null, onremovetrack: null}  
title: ""  
translate: true  
videoHeight: 240  
videoWidth: 320  
volume: 1  
webkitAudioDecodedByteCount: 10995  
webkitDecodedFrameCount: 4  
webkitDisplayingFullscreen: false  
webkitDroppedFrameCount: 0  
webkitSupportsFullscreen: true  
webkitVideoDecodedByteCount: 37638

Some useful metadata include the videoHeight, which tells us the height of the video in pixels, videoWidth, which tells us the width of the video in pixels, and duration which tells us the length of the video in seconds. duration is also available audio elements.

onloadend

The onloadend property of an XMLHttpRequest object let us assign an event handler to it which is run whenever the loadend event is fired. The loadend event is fired whenever a request is completed, regardless of whether the request is completed successfully or not. If it’s successful then this event will be fired after the load event. Otherwise, it’ll fire after the abort or error events.

For example, we can assign an event handler to the onloadend event as we do in the following code:

const loadButtonSuccess = document.querySelector('.load.success');  
const loadButtonError = document.querySelector('.load.error');  
const loadButtonAbort = document.querySelector('.load.abort');  
const log = document.querySelector('.event-log');

function handleLoadEnd(e) {  
    log.textContent = log.textContent + `${e.type}: ${e.loaded} bytes transferred\n`;  
}

function addListeners(xhr) {  
  xhr.onloadend = handleLoadEnd;  
}

function get(url) {  
  log.textContent = ''; const xhr = new XMLHttpRequest();  
  addListeners(xhr);  
  xhr.open("GET", url);  
  xhr.send();  
  return xhr;    
}

loadButtonSuccess.addEventListener('click', () => {  
    get('https://jsonplaceholder.typicode.com/todos/1');  
});

loadButtonError.addEventListener('click', () => {  
    get('https://somewhere.org/i-dont-exist'));  
});

loadButtonAbort.addEventListener('click', () => {  
    get('https://jsonplaceholder.typicode.com/todos/1').abort();  
});

In the code above, we have 3 buttons that run the click event handler whenever each button is clicked. The ‘Load (success)’ button will run the get function when it’s clicked. We’ll pass in a valid URL in the call for the get function. The click handling for the ‘Load (success)’ button is done by the following block:

loadButtonSuccess.addEventListener('click', () => {  
    get('https://jsonplaceholder.typicode.com/todos/1');  
});

The JSONPlaceholder has a test API that can serve any URL since it hosts a fake API so we can load it and not get any errors. Likewise, we have buttons for load a URL that’ll give an error, and another button to load a valid URL but then we abort the request. Once the XmlHttpRequest is finished, then the function we assigned to the onloadend event handler, which is the handleLoadEnd function, will be run.

The handleLoadEnd function has one parameter, which is an Event object with some data about the request that’s finished. In the function, we get the value of the type property, which has the event type that’s fired, which should be loadend . Also, we get the value of the loaded property which has the number of bytes of data that’s been loaded.

Then in the HTML code, we add the elements listed in the querySelector calls above:

<div class="controls">  
    <input class="load success" type="button" name="xhr" value="Load (success)" />  
    <br>  
    <input class="load error" type="button" name="xhr" value="Load (error)" />  
    <br>  
    <input class="load abort" type="button" name="xhr" value="Load (abort)" />  
</div>
<textarea readonly class="event-log"></textarea>

We have 3 buttons to click on to load the successful HTTP request, an HTTP request with a non-existent URL, and an aborted HTTP request respectively. Then we display the event that’s fired and the number of bytes loaded.

The onloadedmetadata property of a media DOM element lets us set an event handler function that’s run when the loademetadata event is fired. Whenever media metadata is loaded this event will fire. We can get the metadata that’s loaded from the srcElement property of the event parameter of the event handler function.

The onloadend property of an XMLHttpRequest object let us assign an event handler to it which is run whenever the loadend event is fired. The loadend event is fired whenever a request is completed, regardless of whether the request is completed successfully or not. If it’s successful then this event will be fired after the load event. Otherwise, it’ll fire after the abort or error events. We can handle this event by setting the onloadend property of an XMLHttpRequest object. The event handler function should have an event parameter which is an event object that has the type property, which has the event type that’s fired, which should be loadend . Also, we get the value of the loaded property which has the number of bytes of data that’s been loaded.

Categories
JavaScript TypeScript

Introduction to TypeScript Generics — Classes

One way to create reusable code is to create code that lets us use it with different data types as we see fit. TypeScript provides the generics construct to create reusable components where we can work with a variety of types with one piece of code. This allows devs to use these components by putting in their own types. In this article, we’ll look at the many ways to define generic classes and also validate properties by defining interfaces and the extends keyword and also force the code to only accept property names that are in an object by using the keyof keyword.

Defining a Generic Class

To define generic classes in TypeScript, we put the generic type marker in between the <> after the class name. We can have more than one type marker separated by a comma. Also, we can use the same type marker to mark the type of the function to let us change parameter and return type of methods within our class. For example, we can write the following code to define a generic class with a single generic type marker:

class GenericPerson<T> {    
  name: T;  
  getName: (name: T) => T;  
}let person = new GenericPerson<string>();  
person.name = 'Jane';  
person.getName = function(x) { return x; };

In the code above, we passed in the string type inside the <> . We can also change to any other type we want like number, boolean, or any interface like in the following code:

class GenericPerson<T> {    
  name: T;  
  getName: (name: T) => T;  
}let person = new GenericPerson<number>();  
person.name = 2;  
person.getName = function(x) { return x; };

We can also insert multiple types within the same class as in the following code:

class GenericPerson<T, U> {    
  name: T;  
  getName: (name: U) => U;  
}let person = new GenericPerson<string, number>();  
person.name = 'Jane';  
person.getName = function (x) { return x; };  
person.getName(2);

In the code above, T and U may or may not be different types. As long as we put them both in and that we pass in and assign data with the types we specified in the class definition, then the TypeScript compiler will accept it. The examples above are only useful for a limited amount of cases since we can’t reference any properties in our class definition since the TypeScript compiler doesn’t know what properties T or U contains.

To make the TypeScript compiler aware of the properties that may be used in our class definition, we can use an interface to list all the properties that can be used with the class and the use the extends keyword after the generic type marker to denote the that type may have the list of properties that are listed in the interface. For example, if we want to have different complex types in our class methods, we can write something like the following code:

interface PersonInterface {  
  name: string;  
  age: number;  
}

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

In the code above, we defined 2 interfaces, the PersonInterface and the GreetingInterface to denote the properties that can be referenced with T and U respectively. In the PersonInterface, we have the name and age properties listed so we can reference these properties for data with type T. For the data with U type we can call the toString method on it. Therefore, in our greet method, we can call toString on greeting since it has the U type and since this.person has the T type, we can get the name and age properties from it.

Then after the class definition, we can instantiate the class and then set values for the name and age properties on the jane object that we created. Then when we run console.log on jane.greet(‘Hi’) then we should see ‘Hi Jane. You’re 20 years old’ since we set the values for jane.person.

We can also put in the types explicitly when we instantiate the object to make the types more clear. Instead of what we have above, we can change it slightly and write the following code:

interface PersonInterface {  
  name: string;  
  age: number;  
}

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.toString()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson<PersonInterface, string>();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

The only difference is that we add the <PersonInterface, string> after new GenericPerson. Note that we can add interfaces or primitive types in between the brackets. TypeScript only cares that the type has the methods listed in the interface we defined. Now that the types are constrained by these generic types, we don’t have to worry about referencing any unexpected properties. For example, if we reference something that doesn’t exist like in the following code:

interface PersonInterface {  
  name: string;  
  age: number;  
}

interface GreetingInterface {  
  toString(): string;  
}

class GenericPerson<T extends PersonInterface, U extends GreetingInterface> {    
  person: T;  
  greet(greeting: U): string {  
    return `${greeting.foo()} ${this.person.name}. You're ${this.person.age} years old`;  
  }  
}

let jane = new GenericPerson<PersonInterface, string>();  
jane.person = {  
  name: 'Jane',  
  age: 20  
};  
console.log(jane.greet('Hi'));

We would get “Property ‘foo’ does not exist on type ‘U’.(2339)” since we didn’t list foo in our GreetingInterface .

Constraining Retrieval of Object Properties

TypeScript also provides a way to let us get the properties of an object safely with generics. We can use the keyof keyword to constrain the values of the than an object can take on to the key names of another object. For example, we can use the keyof keyword like in the following code:

function getProperty<T, K extends keyof T>(obj: T, key: K) {  
  return obj[key];  
}

let x = { foo: 1, bar: 2, baz: 3 };console.log(getProperty(x, "foo"));

In the code above, we constrain the generic marker K to only accept the keys of whatever object is passed in to obj as the valid values of key since K has the extends keyof T marker after K . This means that whatever keys the T type has, then the keys are valid values for K . With code like the ones above, we don’t have to worry about getting values for properties that don’t exist. So if we pass in a key name that doesn’t exist in obj , like in the following code:

getProperty(x, "a");

Then the TypeScript compiler will reject the code and give the error message “Argument of type ‘“a”’ is not assignable to parameter of type ‘“foo” | “bar” | “baz”’.(2345)“. This means that only 'foo' , 'bar' and 'baz' are valid values for key since it has the type K , which has the extends keyof T marker after it to constrain the valid uses to be the key names of obj .

We can define a generic class easily with TypeScript. To define generic classes in TypeScript, we put the generic type marker in between the <> after the class name. We can have more than one type marker separated by a comma. Also, we can use the same type marker to mark the type of the function to let us change parameter and return type of methods within our class. Also, we can use the extends keyword to define the properties that can be referenced with our data marked by generic types. We can use the keyof keyword to constrain the values of the than an object can take on to the key names of another object.