const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is in view!');
}
});
});
observer.observe(document.querySelector('#someElement'));
//Error: Argument of type 'Element | null' is not assignable to parameter of type 'Element'. Type 'null' is not assignable to type 'Element'.
//solution:
const observer: IntersectionObserver = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
if (entry.isIntersecting) {
console.log('Element is in view!');
}
});
});
observer.observe(document.querySelector('#someElement')!);
const userSettings = localStorage.getItem('userSettings');
console.log(JSON.parse(userSettings));
//error: Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'
//solution
const userSettings = localStorage.getItem('userSettings');
if (userSettings !== null) {
console.log(JSON.parse(userSettings)); // Safely parsing, knowing it's not null
} else {
console.log("No user settings found.");
}
const myCanvas = document.querySelector('#myCanvas');
console.log(myCanvas.getContext('2d')); //Error: 'myCanvas' is possibly 'null'
//=> can't call the function getContext()
//solution:
const myCanvas = document.querySelector('#myCanvas') as HTMLCanvasElement;
console.log(myCanvas.getContext('2d')); // TypeScript knows myCanvas is a canvas
<div>
<ul id="main">
<li>hello</li>
<li>world</li>
</ul>
</div>
// Selecting an element
const myList = document.getElementById('main') as HTMLUListElement;
// Accessing children
const listItems = myList.children; // HTMLCollection
console.log("listItems", listItems)
// Accessing the first child
const firstItem = myList.firstElementChild as HTMLLIElement;
console.log("firstItem", firstItem)
// Navigating to siblings
const secondItem = firstItem.nextElementSibling as HTMLLIElement;
console.log("secondItem", secondItem)
// Accessing the parent node
const listParent = secondItem.parentElement as HTMLElement;
console.log("listParent", listParent)
// Creating a new list item
const newItem = document.createElement('li') as HTMLLIElement;
newItem.textContent = 'New Item';
// Appending the new item to the list
myList.appendChild(newItem);
Example: create dynamic list
const addItemToList = (itemText: string): void =>{
const list = document.getElementById('main') as HTMLUListElement;
const newItem = document.createElement('li') as HTMLLIElement;
newItem.textContent = itemText;
list.appendChild(newItem);
}
// Example usage
addItemToList('Learn TypeScript');
// In a TypeScript module (.ts file)
declare global {
interface Window {
customMethod: () => void;
}
}
// Implementing the custom method
window.customMethod = () => console.log("This is a custom method.");
// Using the custom method
window.customMethod();
// Defining a custom interface that extends the original Window interface
interface CustomWindow extends Window {
userToken?: string; // Example of a custom property
}
// Casting the global window object to the type of your custom interface
declare let window: CustomWindow;
// Setting a dynamic value
window.userToken = 'abc123';
// Accessing the custom property
console.log(window.userToken); // Outputs: abc123
// Method 1: Direct Extension Using declare global
// Use Case: Custom Error Logging
// Implementing a global error logging mechanism that can capture and report errors across the entire application.
//
// Extending the window object for global error logging
declare global {
interface Window {
logError: (error: Error, context?: string) => void;
}
}
// Implementation of the global error logging function
window.logError = (error, context = "General") => {
console.log(`Error in ${context}:`, error);
// Here, you'd typically send the error information to a monitoring service
};
// Global error handler
window.onerror = (message, source, lineno, colno, error) => {
window.logError(error || new Error(message.toString()), "Unhandled Exception");
return true; // Prevents the default handling of the error
};
// Example of using the logging function directly
try {
// Some error-prone operation
throw new Error('Something went wrong');
} catch (error) {
window.logError(error, "Example Function");
}
//Method 2: Extending Window with a Custom Interface
// Use Case: Global State Management
// In applications that don't use state management libraries or frameworks, it can be useful to attach a global state object to the window for easy access across different parts of the application, especially for small to medium-sized project
// Defining an interface for the global state
interface CustomWindow extends Window {
appState: {
userLoggedIn: boolean;
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
};
}
// Casting the global window object to the type of the custom interface
declare let window: CustomWindow;
// Initializing the global state
window.appState = {
userLoggedIn: false,
theme: 'light',
setTheme: function(theme: 'light' | 'dark') {
this.theme = theme;
// Additional logic to apply the theme across the application
console.log(`Theme set to ${theme}`);
},
};
// Example usage
window.appState.setTheme('dark');
console.log(`User logged in: ${window.appState.userLoggedIn}`);
Common Elements Requiring Type Assertions
Recognizing When to Use Type Assertions
//example 1:
const myInput = document.getElementById("myInput");
console.log(myInput.value); // Error: Property 'value' does not exist on type 'HTMLElement | null'.
//solution
const myInput = document.getElementById("myInput") as HTMLInputElement;
console.log(myInput.value); // Correctly accesses 'value'.
//example 2:
const myAudio = document.getElementById("myAudio");
myAudio.play(); // Error: Property 'play' does not exist on type 'HTMLElement | null'.
//solution
const myAudio = document.getElementById("myAudio") as HTMLAudioElement;
myAudio.play(); // Correctly accesses 'play'.
TypeScript can't guarantee the actual type of an element at runtime. Therefore, checking an element's type before performing operations is crucial for avoiding errors.
//example 1:
const myCanvas = document.getElementById("myCanvas");
const ctx = myCanvas.getContext("2d"); // Error: 'getContext' does not exist on type 'HTMLElement | null'.
//solution
const myCanvas = document.getElementById("myCanvas");
if (myCanvas instanceof HTMLCanvasElement) {
const ctx = myCanvas.getContext("2d");
// Safe to use 'getContext'
}
//example 2:
const myVideo = document.getElementById("myVideo");
myVideo.play(); // Error: 'play' does not exist on type 'HTMLElement | null'.
//solution
const myVideo = document.getElementById("myVideo");
if (myVideo instanceof HTMLVideoElement) {
myVideo.play(); // Safe to use 'play'
}
//example 1:
const myDiv = document.getElementById("myDiv");
myDiv.textContent = "Hello World"; // Error: Object is possibly 'null'.
//solution
const myDiv = document.getElementById("myDiv");
if (myDiv) {
myDiv.textContent = "Hello World"; // Safely updates the text.
}
//or
const myDiv = document.getElementById("myDiv");
myDiv?.textContent = "Hello World"; // Safely updates the text if myDiv is not null.
//example 2:
const myButton = document.getElementById("myButton");
myButton.classList.add("active"); // Error: Object is possibly 'null'.
//solution
const myButton = document.getElementById("myButton");
if (myButton) {
myButton.classList.add("active"); // Safely adds the class.
}
//or
const myButton = document.getElementById("myButton");
myButton?.classList.add("active"); // Safely adds the class if myButton is not null.
Creating utility functions for DOM manipulation without losing type information can be tricky, leading to type assertion repetition or errors.
//example 1:
//Utility Function Without Generics:
const getElement = (id: string) => {
return document.getElementById(id); // Returns 'HTMLElement | null'.
}
getElement("myInput").value; // Error: Property 'value' does not exist.
//solution
const getElement<T extends HTMLElement> = (id: string): T | null => {
return document.getElementById(id) as T | null;
}
const myInput = getElement<HTMLInputElement>("myInput");
console.log(myInput?.value); // Correct usage with type safety.
//example 2:
const getElementsByClassName = (className: string) => {
return document.getElementsByClassName(className); // Returns 'HTMLCollectionOf<Element>'.
}
getElementsByClassName("myClass")[0].value; // Error: Property 'value' does not exist on type 'Element'.
//solution
const getElementsByClassName = <T extends Element>(className: string): HTMLCollectionOf<T> => {
return document.getElementsByClassName(className) as HTMLCollectionOf<T>;
}
const myInputs = getElementsByClassName<HTMLInputElement>("myInputClass");
console.log(myInputs[0]?.value); // Correct usage with type safety.
const button = document.getElementById("myButton") as HTMLButtonElement;
button.addEventListener("click", (event) => {
console.log("Button clicked!");
});
Type-Safe Event Handler:
button.addEventListener("click", (event: MouseEvent) => {
console.log(event.clientX, event.clientY); // Access MouseEvent properties with type safety
});
this
in Event Handlersbutton.addEventListener("click", function(this: HTMLButtonElement) {
this.classList.toggle('active');
console.log(`Button ${this.id} state: ${this.classList.contains('active') ? 'Active' : 'Inactive'}`);
});
const input = document.getElementById("myInput") as HTMLInputElement;
input.addEventListener("keypress", (event: KeyboardEvent) => {
console.log(`Key pressed: ${event.key}`);
});
2.2 Managing Form Submit Events
Example: Preventing a form from submitting.
const form = document.getElementById("myForm") as HTMLFormElement;
form.addEventListener("submit", (event: SubmitEvent) => {
const inputElement = document.getElementById('myInput') as HTMLInputElement;
if (!inputElement.value.match(/^[a-zA-Z]+$/)) {
event.preventDefault(); // Prevent form submission
console.log('Form submission prevented due to validation failure.');
alert('Please enter only letters.');
} else {
console.log('Form submitted successfully.');
}
});
Event Delegation
Instead of adding an event listener to each element, add a single listener to a parent element and use event delegation.
//Example: Handling clicks on multiple buttons within a container.
const container = document.getElementById("buttonContainer") as HTMLElement;
container.addEventListener("click", (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (target.tagName === "BUTTON") {
console.log(`Button ${target.id} clicked`);
}
});
Generic solution
function api<T>(url: string, options?: RequestInit): Promise<T> {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<{ data: T }>;
})
.then(data => data.data)
.catch((error: Error) => {
console.error(error); // Log the error or use an external logging service
throw error; // Rethrow for further handling
});
}
Example 1: Fetching user information
interface User {
id: number;
name: string;
email: string;
}
// Fetching user data
api<User>('/api/users/1')
.then(user => {
console.log(user.name, user.email);
})
.catch(error => {
console.error("Failed to fetch user data:", error);
});
Example 2: Posting Data to an API
interface Post {
userId: number;
title: string;
body: string;
}
//1. Function to post data
async function createPost(postData: Post): Promise<Post> {
return api<Post>('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
}
// Usage
const newPost: Post = {
userId: 1,
title: 'Hello TypeScript',
body: 'TypeScript with Fetch API is awesome!',
};
createPost(newPost)
.then(post => console.log("Post created:", post))
.catch(error => console.error("Failed to create post:", error));
//2. Update post
async function updatePost(postId: number, postData: Post): Promise<Post> {
return api<Post>(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
}
// Usage
const updatedPost: Post = {
userId: 1,
title: 'Updated Title',
body: 'Updated body content.',
};
updatePost(1, updatedPost)
.then(post => console.log("Post updated:", post))
.catch(error => console.error("Failed to update post:", error));
//3. Delete post
async function deletePost(postId: number): Promise<void> {
return api<void>(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
method: 'DELETE',
});
}
// Usage
deletePost(1)
.then(() => console.log("Post deleted successfully."))
.catch(error => console.error("Failed to delete post:", error));
Promise
//create a promise
const myPromise: Promise<string> = new Promise((resolve, reject) => {
const isSuccess = true; // Simulated outcome
isSuccess ? resolve("Operation successful") : reject("Operation failed");
});
//handle promise using .then and .catch
myPromise
.then((result: string) => console.log(result)) // Success path
.catch((error: string) => console.error(error)); // Error path
Using async - await
// Create a promise
const myPromise: Promise<string> = new Promise((resolve, reject) => {
const isSuccess = true; // Simulated outcome
isSuccess ? resolve("Operation successful") : reject("Operation failed");
});
// Function to handle the promise using async - await
const handleMyPromise = async () => {
try {
const result: string = await myPromise; // Await the resolution of the promise
console.log(result);
} catch (error) {
console.error(error);
}
};
// Execute the async function
handleMyPromise();
const fetchAllData = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetch('https://api.example1.com').then(res => res.json()),
fetch('https://api.example2.com').then(res => res.json()),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
//--------- Or
const fetchJsonData = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);
}
return await response.json();
};
const fetchAllDataConcurrently = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetchJsonData('https://api.example1.com'),
fetchJsonData('https://api.example2.com'),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
// Invoke the async function
fetchAllDataConcurrently();
const fetchAllData = async (): Promise<void> => {
try {
const [data1, data2] = await Promise.all([
fetch('https://api.example1.com').then(res => res.json()),
fetch('https://api.example2.com').then(res => res.json()),
]);
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data:", error);
}
};
//--------- or
const fetchJsonData = async (url: string) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);
}
return await response.json();
};
const fetchSequentialData = async (): Promise<void> => {
try {
const data1 = await fetchJsonData('https://api.example1.com');
const data2 = await fetchJsonData('https://api.example2.com');
console.log(data1, data2);
} catch (error) {
console.error("Error fetching data sequentially:", error);
}
};
// Invoke the async function
fetchSequentialData();
// Assuming these are the accessible URLs to files served over HTTP
const fileURLs = [
'/file1.txt',
'/file2.txt'
];
const processFile = async (fileUrl: string): Promise<string> => {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.text();
return data
} catch (error) {
console.error(`Error processing file ${fileUrl}:`, error);
throw error; // Rethrow or handle as needed
}
};
const processAllFiles = async (): Promise<string[]> => {
const promises = fileURLs.map(fileUrl => processFile(fileUrl));
return Promise.all(promises);
};
// Invoke the async function
processAllFiles()
.then(results => console.log(results))
.catch(error => console.error("Failed to process files:", error));
Let's say you have an array of items, and for each item, you want to perform an operation that requires waiting for a certain amount of time (a simulated delay) before proceeding to the next item. This pattern is useful for rate-limiting or when dealing with APIs that have usage constraints.
const items = [1, 2, 3, 4, 5];
const delayedOperation = async (item: number): Promise<number> => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Processed item ${item}`);
resolve(item * 2); // Example operation
}, 1000 * item); // Delay increases with each item
});
};
const processItemsSequentially = async (): Promise<number[]> => {
const results = [];
for (const item of items) {
const result = await delayedOperation(item);
results.push(result);
}
return results;
};
// Invoke the async function
processItemsSequentially()
.then(results => console.log("Final results:", results))
.catch(error => console.error("Failed to process items:", error));
In this scenario, we have a series of validation steps that must be completed sequentially. After each step, the user receives feedback on the progress.
We'll simulate these steps with asynchronous functions that resolve after a delay, representing time-consuming operations like server validations or database checks.
type ValidationStepResult = {
step: number;
message: string;
isValid: boolean;
};
const simulateValidationStep = async (step: number): Promise<ValidationStepResult> => {
return new Promise((resolve) => {
setTimeout(() => {
const isValid = Math.random() > 0.2; // Simulate a validation check with an 80% chance to pass
const message = isValid ? `Step ${step} completed successfully.` : `Step ${step} failed validation.`;
console.log(message); // In a real app, you would update the UI instead
resolve({ step, message, isValid });
}, 1000 * step); // Simulate a delay for each validation step
});
};
const validateSignUpProcess = async (): Promise<void> => {
const steps = [1, 2, 3, 4]; // Representing each step of the signup/validation process
let allStepsValid = true;
for (const step of steps) {
const result = await simulateValidationStep(step);
if (!result.isValid) {
allStepsValid = false;
break; // Stop the validation process if any step fails
}
}
if (allStepsValid) {
console.log("All steps completed successfully. User successfully signed up.");
// Proceed with the next step in the signup process, e.g., creating the user account
} else {
console.log("Signup process halted due to failed validation step.");
// Provide feedback to the user and potentially allow them to correct the input
}
};
// Invoke the validation process
validateSignUpProcess().catch(error => console.error("Signup validation process encountered an error:", error));
//example console for all step success senarios
// main.js:7 Step 1 completed successfully.
// main.js:7 Step 2 completed successfully.
// main.js:7 Step 3 completed successfully.
// main.js:7 Step 4 completed successfully.
// main.js:23 All steps completed successfully. User successfully signed up.
//example console for a fail senario
// main.js:7 Step 1 completed successfully.
// main.js:7 Step 2 failed validation.
// main.js:27 Signup process halted due to failed validation step.
Example 1:
interface LoginForm {
username: string;
password: string;
}
// Type guard for LoginForm
function isLoginForm(data: any): data is LoginForm {
return typeof data === 'object' &&
typeof data.username === 'string' &&
typeof data.password === 'string';
}
// Sample usage
const formData = { username: 'user123', password: 'pass123' }; // Assume this comes from user input
if (isLoginForm(formData)) {
console.log("Valid login form data:", formData);
} else {
console.error("Invalid login form data");
}
Example 2: File Upload Validation
interface UploadedFile {
size: number; // File size in bytes
type: string; // MIME type
}
// Type guard for UploadedFile
function isValidFile(file: any): file is UploadedFile {
const maxSize = 5 * 1024 * 1024; // 5 MB
const allowedTypes = ['image/jpeg', 'image/png', 'text/plain'];
return typeof file === 'object' &&
typeof file.size === 'number' && file.size <= maxSize &&
typeof file.type === 'string' && allowedTypes.includes(file.type);
}
// Sample usage with a mock file object
const file = { size: 300000, type: 'image/jpeg' }; // Assume this comes from an <input type="file" />
if (isValidFile(file)) {
console.log("File is valid for upload:", file);
} else {
console.error("File is invalid");
}
Create a JSON file (userInputRules.json) defining the expected structure of data from an API call:
In tsconfig.json
...
"resolveJsonModule": true,
...
userInputRules.json
{
"username": {
"minLength": 3,
"maxLength": 20
},
"password": {
"minLength": 8,
"requireSpecialCharacter": true
}
}
import * as userInputRules from './userInputRules.json';
type UserInputRules = typeof userInputRules;
function validateUserInput(input: { username: string; password: string }, rules: UserInputRules): boolean {
// Simplified example of using the rules for validation
const { username, password } = input;
if (username.length < rules.username.minLength || username.length > rules.username.maxLength) {
console.error("Username validation failed.");
return false;
}
// Example check for a special character in the password
const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
if (password.length < rules.password.minLength || (rules.password.requireSpecialCharacter && !hasSpecialCharacter)) {
console.error("Password validation failed.");
return false;
}
return true;
}
// Sample usage
const userInput = { username: "user1", password: "pass!word123" };
validateUserInput(userInput, userInputRules);
interface UserRegistrationForm {
username: string;
password: string;
email: string;
}
//----
import * as yup from 'yup';
const registrationSchema = yup.object({
username: yup.string().required().min(3).max(20),
password: yup.string()
.min(8, 'Password must be at least 8 characters long')
.matches(/[!@#$%^&*(),.?":{}|<>]/, 'Password must contain at least one special character')
.required('Password is required');
email: yup.string().email().required(),
});
const validateUserRegistration = async (formData: any): Promise<UserRegistrationForm> => {
try {
return await registrationSchema.validate(formData);
} catch (error) {
console.error("Validation error:", error);
throw error;
}
};
interface UserProfile {
id: number;
name: string;
email: string;
}
// Type guard for UserProfile
function isUserProfile(data: any): data is UserProfile {
return typeof data === 'object' &&
typeof data.id === 'number' &&
typeof data.name === 'string' &&
typeof data.email === 'string';
}
// Simulated API call
async function fetchUserProfile(userId: number): Promise<UserProfile | null> {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
if (isUserProfile(data)) {
return data;
} else {
console.error("Invalid user profile data");
return null;
}
}
Create a JSON file (apiDataSchema.json) defining the expected structure of data from an API call:
In tsconfig.json
...
"resolveJsonModule": true,
...
apiDataSchema.json
{
"userId": "number",
"name": "string",
"email": "string"
}
import {default as apiDataSchema} from './apiDataSchema.json';
type APIDataSchema = typeof apiDataSchema;
async function fetchAndValidateUserData(url: string, schema: APIDataSchema): Promise<any> {
const response = await fetch(url);
// const userData = await response.json();
//check data:
const userData = {
userId: 1,
name:"John Cena"
email: "john.cena@gmail.com"
}
// Check if all fields in userData match the schema
const isValid = Object.entries(schema).every(([field, type]) => typeof userData[field] === type);
if (!isValid) {
console.error("API data validation failed.");
return null;
}
return userData;
}
// Sample usage
const apiUrl = "https://api.example.com/user/1";
fetchAndValidateUserData(apiUrl, apiDataSchema).then(data => {
if (data) {
console.log("Validated user data:", data);
}
});
import * as yup from 'yup';
interface ApiResponse {
userId: number;
username: string;
email: string;
}
const registrationSchema = yup.object({
username: yup.string().required().min(3),
password: yup.string().required().min(8),
email: yup.string().email().required(),
});
const fetchUserData = async (userId: number): Promise<ApiResponse> => {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
try {
await registrationSchema.validate(data);
} catch (error) {
throw new Error("Invalid API response format.");
}
return data as ApiResponse;
};
Understanding "Type X is not assignable to type Y"
Mar 15, 2024//1.4 How to check if a string is part of an Enum?
Mar 6, 2024As a user, I can choose to start a conversation with providing diagnosis or a random disease for the chatbot
Dec 18, 20231. CSS Selectors and Specificity CSS Selector Priority: Hierarchy - Inline styles > IDs > Classes, attributes, and pseudo-classes > Elements and pseudo-elements. CSS selector priority is the machanism that CSS use to determine which element will be selected. Specificity Calculation: Count 1 for each element and pseudo-element, 10 for each class, attribute, and pseudo-class, 100 for each ID, and 1000 for inline styles. CSS Pseudo Classes: Used to define a special state of an element (e.g., a:hover for hovering state). CSS Pseudo Elements: Used to style a specific part of an element (e.g., p::first-letter styles the first letter). Inappropriateness of CSS Generated Content: Not accessible by screen readers, not selectable or copyable, doesn't change with user language settings. 2. AJAX and JavaScript
May 17, 2023or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up