Frequently asked - TypeScript Interview Questions and Answers - Part 02
21. What is Union types?
In TypeScript we can use union types to describe values that can be of more than one type. It therefore allows to avoid using any
. Define union types with the |
character to separate the different possible types. For example, consider a function uses parameters that can be string or number.
function add(v1: any, v2: any) {
let value1 = typeof v1 === "string" ? +v1 : v1;
let value2 = typeof v2 === "string" ? +v2 : v2;
console.log(value1 + value2);
}
Instead of that, we can re-write using union type.
function add(v1: number | string, v2: number | string) {
let value1 = typeof v1 === "string" ? +v1 : v1;
let value2 = typeof v2 === "string" ? +v2 : v2;
console.log(value1 + value2);
}
22. What is string literal types?
In TypeScript, string literal types let us define types that only accept a defined string literal. They are useful to limit the possible string values of variables. Here are examples of how to define and use string literal types:
let version: '1.0.0';
version = '1.0.0'; //OK
version = '2.0.0'; //Compiler error
We can also use string literal with union operator:
let gender: 'Male' | 'Female' | 'Other';
gender = 'NA'; // Compiler error
23. Explain Type Compatibility.
Type compatibility in TypeScript is based on structural subtyping. Structural typing is a way of relating types based solely on their members. This is in contrast with nominal typing. Consider the following code:
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();
In nominally-typed languages like C# or Java, the equivalent code would be an error because the Person
class does not explicitly describe itself as being an implementor of the Named
interface.
24. What is Modules in TypeScript?
Modules provide the possibility to group related logic, encapsulate it, structure our code and prevent pollution of the global namespace. Modules can provide functionality that is only visible inside the module, and they can provide functionality that is visible from the outside using the export
keyword.
TypeScript’s module system comes in two flavors: internal and external modules. The internal modules are called namespaces (from TypeScript version 1.5). “External modules” are simply “modules”. Any declaration (such as a variable, function, class, type alias, or interface) can be exported by adding the export keyword.
//Validation.ts
export const numberRegexp = /^[0-9]+$/;
export interface StringValidator {
isAcceptable(s: string): boolean;
}
Importing a module is as easy as exporting a module. It is done through using one of the import
forms below:
import { StringValidator } from "./Validation";
//it can be renamed
import { StringValidator as SV } from "./Validation";
25. How to work with other JavaScript libraries? What is Type declaration? What is Ambient declarations?
To describe the shape of libraries not written in TypeScript, we need to declare the API that the library exposes. Typically, these are defined in .d.ts
files.
We can tell TypeScript that we are trying to describe code that exists elsewhere (e.g. written in JavaScript/CoffeeScript/The runtime environment like the browser or Node.js) using the declare
keyword. As a quick example:
declare var foo: any;
foo = 123;
If a file has the extension .d.ts
then each root level definition must have the declare
keyword prefixed to it. This helps make it clear that there will be no code emitted by TypeScript.
- Ambient declarations is a promise that we are making with the compiler. If these do not exist at runtime and we try to use them, things will break without warning.
- Ambient declarations are like docs. If the source changes, the docs need to be kept updated. So we might have new behaviours that work at runtime but no one’s updated the ambient declaration and hence we get compiler errors.
26. What is namespace
in TypeScript?
Namespaces provide a convenient syntax around a common pattern used in JavaScript. This is commonly used in JavaScript for making sure that stuff doesn’t leak into the global namespace. With file based modules we don’t need to worry about this, but the pattern is still useful for logical grouping of a bunch of functions. Therefore TypeScript provides the namespace
keyword to group these.
namespace Utility {
export function log(msg) {
console.log(msg);
}
export function error(msg) {
console.error(msg);
}
}
Utility.log('Test message');
Utility.error('Test error');
The namespace
keyword generates the JavaScript that would look like this:
(function (Utility) {
// Add functions and properties
})(Utility || (Utility = {}));
One thing to note is that namespaces can be nested so you can do stuff like namespace Utility.Messaging
to nest a Messaging
namespace under Utility
.
27. Explain Relative vs. Non-relative module imports.
Module imports are resolved differently based on whether the module reference is relative or non-relative.
A relative import is one that starts with /
, ./
or ../
. Some examples include:
-
import Entry from "./components/Entry";
-
import { DefaultHeaders } from "../constants/http";
, Any other import is considered non-relative. Some examples include: -
import * as $ from "jquery";
-
import { Component } from "@angular/core";
, A relative import is resolved relative to the importing file and cannot resolve to an ambient module declaration. We should use relative imports for our own modules that are guaranteed to maintain their relative location at runtime.
A non-relative import can be resolved relative to baseUrl, or through path mapping.
28. What is Triple-Slash Directive? What are some of the triple-slash directives?
Triple-slash directives are single-line comments containing a single XML tag. The contents of the comment are used as compiler directives.
Triple-slash directives are only valid at the top of their containing file. A triple-slash directive can only be preceded by single or multi-line comments, including other triple-slash directives. If they are encountered following a statement or a declaration they are treated as regular single-line comments, and hold no special meaning. Below are some of the triple-slash directives in TypeScript:
- The
/// <reference path="..." />
directive is the most common of this group. It serves as a declaration of dependency between files. Triple-slash references instruct the compiler to include additional files in the compilation process. If the compiler flag--noResolve
is specified, triple-slash references are ignored; they neither result in adding new files, nor change the order of the files provided. - Similar to a
/// <reference path="..." />
directive, this directive serves as a declaration of dependency; a/// <reference types="..." />
directive, however, declares a dependency on a package. For example, including/// <reference types="node" />
in a declaration file declares that this file uses names declared in@types/node/index.d.ts
; and thus, this package needs to be included in the compilation along with the declaration file.
29. What is Decorators?
Decorators are simply functions that modify a class, property, method, or method parameter. The syntax is an “@
” symbol followed by a function.
In other words, Decorators are functions that take their target as the argument. With decorators we can run arbitrary code around the target execution or even entirely replace the target with a new definition. There are 4 things we can decorate in Typescript: constructors, methods, properties and parameters.
Below is the example of class decorators. A class decorator is a function that accepts a constructor function and returns a constructor function. Returning undefined is equivalent to returning the constructor function passed in as argument.
const log = <T>(originalConstructor: new(...args: any[]) => T) => {
function newConstructor(... args) {
console.log("Logging arguments: ", args.join(", "));
new originalConstructor(args);
}
newConstructor.prototype = originalConstructor.prototype;
return newConstructor;
}
@log
class Friend {
constructor(name: string, age: number) {}
}
new Friend("John Doe", 25);
//Logging arguments: John Doe, 25
The log
decorator replaces the original constructor with a function that logs the arguments and than invokes the original constructor.
30. Explain Method decorators.
A method decorator is a function that accepts 3 arguments: the object on which the method is defined, the key for the property (a string name or symbol) and a property descriptor. The function returns a property descriptor; returning undefined is equivalent to returning the descriptor passed in as argument.
const log = (target: Object, key: string | symbol, descriptor: TypedPropertyDescriptor<Function>) => {
return {
value: function( ... args: any[]) {
console.log("Arguments: ", args.join(", "));
const result = descriptor.value.apply(target, args);
console.log("Result: ", result);
return result;
}
}
}
class Calculator {
@log
add(x: number, y: number) {
return x + y;
}
}
new Calculator().add(1, 3);
//Arguments: 1, 3
//Result: 4
The log
decorator replaces the original function with a new function that logs received arguments, executes the original method and stores the result in a local variable, logs and returns the result.
31. Explain Property decorators.
Property decorators are similar to method decorators. The only difference is they do not accept property descriptor as argument and do not return anything.
const log = (target: Object, key: string | symbol) => {
let value = target[key];
const getter = () => {
console.log("Getting value: ", value);
return value;
};
const setter = (val) => {
console.log("Setting value: ", val);
value = val;
}
Reflect.deleteProperty[key];
Reflect.defineProperty(target, key, {
get: getter,
set: setter
});
}
class Box<T> {
@log
item: T;
}
const numberInABox = new Box<number>();
numberInABox.item = 12;
numberInABox.item;
//Setting value: 12
//Getting value: 12
The log
decorator above redefines the decorated property on the object.
32. Explain Parameter decorator.
A parameter decorator is not supposed to modify the behavior of a constructor, method or property. A parameter decorator should only be used to generate some sort of metadata. Once the metadata has been created we can use another decorator to read it.
class Person {
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
public saySomething(@logParameter something : string) : string {
return this.name + " " + this.surname + " says: " + something;
}
}
A parameter decorator accepts 3 parameters: The prototype of the class being decorated, the name of the method that contains the parameter being decorated and the index of that parameter being decorated.
function logParameter(target: any, key : string, index : number) {
var metadataKey = `log_${key}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
}
else {
target[metadataKey] = [index];
}
}
The parameter decorator above adds a new property (metadataKey
) to the class prototype. The new property is an array and contains the indices of the parameters being decorated. We can consider this new property as metadata.
33. What is Decorator factory?
A decorator factory is a function that can accept any number of arguments, and must return one of the types of decorator.
We can implement and consume all the available types of decorator (class, method, property and parameter). By using Decorator factory, we could just consume a decorator everywhere without having to worry about its type as follows
@log
class Person {
@log
public name: string;
public surname: string;
constructor(name : string, surname : string) {
this.name = name;
this.surname = surname;
}
@log
public saySomething(@log something : string) : string {
return this.name + " " + this.surname + " says: " + something;
}
}
We can achieve this by wrapping all the decorators with a decorator factory. The decorator factory is able to identify what type of decorator is required by checking the arguments passed to the decorator:
function log(...args : any[]) {
switch(args.length) {
case 1:
return logClass.apply(this, args);
case 2:
return logProperty.apply(this, args);
case 3:
if(typeof args[2] === "number") {
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
default:
throw new Error("Decorators are not valid here!");
}
}
34. What is Declaration Merging?
Declaration merging means that the compiler merges two separate declarations declared with the same name into a single definition. This merged definition has the features of both of the original declarations. Any number of declarations can be merged; it’s not limited to just two declarations.
The simplest, and perhaps most common, type of declaration merging is interface merging.
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
Not all merges are allowed in TypeScript. Currently, classes can not merge with other classes or with variables.
35. What is JSX? Can we use JSX in TypeScript
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript. JSX came to popularity with the React framework. TypeScript supports embedding, type checking, and compiling JSX directly into JavaScript.
In order to use JSX in our file: we must name our file with a .tsx
extension and should enable jsx
option.
36. What are all the JSX modes TypeScript supports?
TypeScript ships with three JSX modes: preserve
, react
, and react-native
.
The preserve
mode will keep the JSX as part of the output to be further consumed by another transform step (e.g. Babel). Additionally the output will have a .jsx
file extension. The react
mode will emit React.createElement
, does not need to go through a JSX transformation before use, and the output will have a .js
file extension. The react-native
mode is the equivalent of preserve in that it keeps all JSX, but the output will instead have a .js
file extension.
37. What is the difference between these two types: string
and String
? Which one should be used?
The title-cased types (Number
, String
, Boolean
and Object
) refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.
It is best to use the primitive types number
, string
, boolean
and object
.
/* WRONG */
function reverse(s: String): String;
/* OK */
function reverse(s: string): string;
38. Why TypeScript is referred as Optionally Statically Typed Language?
TypeScript is referred as optionally statically typed, which means we can make the compiler to ignore the type of a variable optionally. Using any
data type, we can assign any type of value to the variable. TypeScript will not give any error checking during compilation.
var unknownType: any = 4;
unknownType = "Okay, I am a string";
unknownType = false; // set a boolean. No error thrown at compile-time.
39. Does TypeScript supports Function overloading?
TypeScript does support function overloading but it is not same like other OO languages. Since JavaScript does not support, we ended up checking the types of our parameters.
function add(value1: string, value2: string) : number;
function add(value1: number, value2: number) : number {
if(typeof value1 === "string") {
value1 = parseInt(value1, 10);
}
if(typeof value2 === "string") {
value2 = parseInt(value2, 10);
}
return value1 + value2;
}
We could re-write the same function with union operator instead of overload.
function add(value1: number | string, value2: number | string) : number {
if(typeof value1 === "string") {
value1 = parseInt(value1, 10);
}
if(typeof value2 === "string") {
value2 = parseInt(value2, 10);
}
return value1 + value2;
}
40. What is TypeScript Definition Manager?
When using TypeScript, you will need TypeScript definition files to work with external libraries. TypeScript Definition Manager (TSD) is a package manager to search and install TypeScript definition files directly from the community driven DefinitelyTyped repository.
Consider we need typings file for jQuery
so that we can use jQuery with TypeScript. This command, tsd query jquery --action install
(we need to have tsd
installed), finds and install the typings file for jQuery
. Now we can include the below directive at the top of the file where we want to use jQuery
.
/// <reference path="typings/jquery/jquery.d.ts" />
TSD is now offically deprecated, and we should use typings
instead.