Skip to content

Exercise 7 of 10 · Strings and formatting

The League Table

What you will make

A league table whose team names, games played and points all sit in straight columns, every row built by the one function you rewrote.

The one new idea: template literals build text with values inside it

Tables, receipts, timetables and log files are all text with values placed at fixed positions. Template literals are how TypeScript builds that text, and because every row here comes from one function, a single fix in one place tidies every row that uses it.

Go straight to the code ↓

Text with values inside it

So far, text and values have shared a line by sitting side by side in console.log, separated by commas, with exactly one space between them. That is fine until you want a column, which needs every value to take up a fixed amount of room.

A template literal is a single piece of text with values dropped into it. It is written between backtick quotes (on US and UK keyboards, the key to the left of the 1), and anywhere inside it, ${...} marks a place for a value:

TypeScript
const city: string = "Lisbon";
const degrees: number = 24;

console.log(`${city} is ${degrees} degrees today`);
console.log("${city} is ${degrees} degrees today");
Output
Lisbon is 24 degrees today
${city} is ${degrees} degrees today

The two lines differ only in their quotes. In double quotes, the dollar signs and braces are ordinary characters. Anything that works out to a value can go inside the braces, so ${degrees + 3} would put 27 there.

padEnd and padStart make columns

A column is a fixed width, and two things text can do make one:

  • .padEnd(9) adds spaces to the end of a piece of text until it is 9 characters long.
  • .padStart(4) adds spaces to the start instead, so numbers of different lengths end in the same place.

Those belong to text, not to numbers, and the compiler knows the type of every value. Ask a number to pad itself and nothing runs:

TypeScript
const degrees: number = 24;

console.log(degrees.padStart(4));
Output
main.ts(3,21): error TS2339: Property 'padStart' does not exist on type 'number'.

A property is something a value has or can do. String(degrees) turns the number into text first, and text can be padded:

TypeScript
const city: string = "Lisbon";
const degrees: number = 24;

console.log(`[${city.padEnd(9)}][${String(degrees).padStart(4)}]`);
console.log(`[${"Oslo".padEnd(9)}][${String(7).padStart(4)}]`);
Output
[Lisbon   ][  24]
[Oslo     ][   7]

The square brackets are only there to make the spaces visible. Both names take nine characters and both numbers take four, so every bracket lands in the same column.

The function keeps its promise

A function with : string after its brackets promises to hand text back. Build the template literal but forget to return it, and that promise is broken:

TypeScript
function label(city: string): string {
  `[${city}]`;
}

console.log(label("Oslo"));
Output
main.ts(1,31): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.

The long message comes down to this: the function said it would return a string, and it returns nothing at all.

Your turn

Press Run first. The heading and the borders are tidy, but the three rows are ragged, because row still glues its values together with +. Here is that line on its own, with one call:

TypeScript
function row(team: string, played: number, points: number): string {
  return team + " " + played + " " + points;
}

console.log(row("Otters", 8, 14));
Output
Otters 8 14

Rewrite that return line in the editor as a template literal. The heading is laid out for these widths: the team name padded to 12 characters, games played to 3, and points to 6, with nothing typed between the three insertions. Every row changes at once, because every row is built by that one function.

If something goes wrong

If every row prints dollar signs and braces back at you, the line is still in double quotes. Nothing has broken; the quotes need to be backticks.

If the compiler reports Property 'padStart' does not exist on type 'number'., a number is being padded before it has been turned into text. Wrap it in String(...) first.

If the rows print but sit in the wrong place, compare your three widths with 12, 3 and 6, and check that padEnd is on the team name and padStart on the two numbers.

Write your code

Runs in your browser. Press Run (or Ctrl/Cmd+Enter) and the output is checked for you.

Ctrl/Cmd+Enter to run

Press Esc then Tab to move keyboard focus out of the code editor.

Ready
Output will appear here after you run your code.

The runtime is starting in the background. You can type now — it will be ready before you are.

The answer appears here once you have run your code at least once.

Things that often go wrong here

Leaving the line in double quotes
Every row prints the dollar signs, the braces and the names back at you, character for character. The compiler has no complaint, because text with braces in it is perfectly good text; only backtick quotes turn those braces into places for values.
Padding a number without String around it
Padding is something text can do and a number cannot, and the compiler knows played is a number, so it stops before anything prints with Property 'padStart' does not exist on type 'number'. Wrapping the number in String first turns it into text.
Swapping padEnd and padStart
The team names are pushed to the right of their twelve characters and the numbers to the left of theirs, so each name runs straight into its games-played number and nothing lines up under the heading.
Deleting return while rewriting the line
The function promised with : string that it would hand back text. Without return it hands back nothing, and the compiler says so before running: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.

Want a blank editor instead? Open the TypeScript playground.