Getting started with Angular Signals

1. Before you begin

black Angular logo

Angular Signals introduce three reactive primitives to the Angular you know and love, simplifying your development and helping you build faster apps by default.

What you'll build

  • You learn about the three reactive primitives introduced with Angular Signals: signal(), computed(), and effect().
  • Use Angular Signals to power an Angular Cipher game. Ciphers are systems for encrypting and decrypting data. In this game, users can decode a secret message by dragging and dropping clues to solve a cipher, customize the message, and share the URL to send secret messages to friends.

Angular Cypher game in the style of a vintage green gaming console, with a hidden message on the screen of 'Anqnxaa Lpcnaxl aaf pn jfafxyofa aofapfm pn a16 wyjak!'

Prerequisites

2. Get the code

Everything you need for this project is in a Stackblitz. Stackblitz is the recommended method for working through this codelab. As an alternative, clone the code and open it in your favorite dev environment.

Open Stackblitz and run the app

To get started, open the Stackblitz link in your favorite web browser:

  1. Open a new browser tab and go to https://stackblitz.com/edit/io-signals-codelab-starter?file=src%2Fcipher%2Fservice.cipher.ts,src%2Fsecret-message%2Fservice.message.ts&service.massage.ts
  2. Fork the Stackblitz to create your own editable workspace. Stackblitz should automatically run the app, and you're ready to go!

Alternative: Clone the repository and serve the app

Using VSCode or a local IDE is an alternative method for working through this codelab:

  1. Open a new browser tab and go to https://github.com/angular/codelabs/tree/signals-get-started.
  2. Fork and clone the repository, and use the cd codelabs/ command to move into the repository.
  3. Check out the starter code branch with the git checkout signals-get-started command.
  4. Open the code in VSCode or your preferred IDE.
  5. To install the dependencies required to run the server, use the npm install command.
  6. To run the server, use the ng serve command.
  7. Open a browser tab to http://localhost:4200.

3. Establish a baseline

Your starting point is an Angular Cipher game, but it's not working yet. Angular Signals will power the game's functionality.

Angular Cypher game in the style of a vintage green gaming console, with a hidden message on the screen of 'Anqnxaa Lpcnaxl aaf pn jfafxyofa aofapfm pn a16 wyjak!'

To get started, walk through the finished version of what you'll be building: Angular Signals Cypher.

  1. View the coded message on the screen.
  2. Drag and drop a letter button in the keypad to work toward solving the cipher and decoding the secret message.
  3. On success, see how the message updates to decode more of the secret message.
  4. Click Customize to change the Sender and Message, and then click Create & Copy URL to see the values on the screen and the URL change.
  5. Bonus: Copy and paste the URL to a new tab, or share with a friend, and see how the sender and message are stored in the URL.

GIF of the Angular Cypher game, with a hidden message being decoded on the screen to spell 'Angular Signals are in developer preview in v16 today!'

4. Define your first signal()

A signal is a value that can tell Angular when it changes. Some signals can be changed directly, while others calculate their values from the values of other signals. Together, signals create a directed graph of dependencies that models how data flows in your app.

Angular can use the notifications from signals to know which components need to be change-detected or to execute effect functions that you define.

Convert superSecretMessage to a signal()

superSecretMessage is a value in MessageService that defines the secret message the player decodes. Currently, the value does not notify the app of changes, so the Customize button is broken. You can solve this with a signal.

By making superSecretMessage a signal, you can notify parts of the app that depend on knowing when the message has changed. When you customize the message in a dialog, you'll set the signal to update the rest of the app with the new message.

To define your first signal, perform the following steps under the TODO(1): Define your first signal() comment in each file:

  1. In the service.message.ts file, use the Signals library to make superSecretMessage reactive:

src/app/secret-message/service.message.ts

superSecretMessage = signal(
  'Angular Signals are in developer preview in v16 today!'
);

This automatically prompts you to import signal from @angular/core. If you refresh the page, you'll likely run into errors where you previously referred to superSecretMessage. This is because you've changed the type of superSecretMessage from string to SettableSignal<string>. You can fix this by changing all references of superSecretMessage to use the Signals API. Wherever you read the value, call the Signal getter superSecretMessage(). And wherever you write the value, use the .set API on SettableSignal to set the new value for the message.

  1. In the secret-message.ts and service.message.ts files, update all references of superSecretMessage to superSecretMessage():

src/app/secret-message/secret-message.ts

// Before
this.messages.superSecretMessage
this.messages.superSecretMessage = message;

// After
this.messages.superSecretMessage()
this.messages.superSecretMessage.set(message);

src/app/secret-message/service.message.ts

// Before
this.superSecretMessage

// After
this.superSecretMessage()

Explore the two other signals

  • Notice that you have two other signals in your app:

src/app/cipher/service.cipher.ts

cipher = signal(this.createNewCipherKey());
decodedCipher = signal<CipherKey[]>([]);

The CipherService defines a cipher signal, a randomly generated mapping of key-value pairs of one letter of the alphabet to a new cipher letter. You use this to scramble the message and determine if the player finds a successful match on the keyboard.

You also have a decodedCipher signal of the successfully decoded key-value pairs that you'll add to as the player solves the cipher.

A unique and powerful attribute of Angular's Signals library design is that you can introduce reactivity everywhere. You defined signals once in the app's services, and you can use them in a template, components, pipes, other services, or anywhere you can write application code. They're not limited or bound to a component scope.

Verify changes

  • You have one more step to perform before the app works. For now, try adding a console.log() in different parts of your app to see how your new superSecretMessage is being set.

Stackblitz with a console.log() message showing the superSecretMessage correctly logging the new message.

5. Define your first computed()

In many situations you might find yourself deriving state from existing values. It's better to have the derived state update when the dependent value changes.

With computed(), you can declaratively express a signal that derives its value from other signals.

Convert solvedMessage to a computed()

solvedMessage translates the secretMessage value from encoded to decoded using the decodedCipher signal.

This is extra cool because you can see you're deriving a computed based on another computed, so any time a signal within that mapped reactive context changes, the dependencies are notified.

Currently, the solvedMessage isn't updated when you change the secretMessage, decodedCipher, or superSecretMessage. So you're not seeing updates to the screen when the player solves the cipher.

By making solvedMessage a computed, you create a reactive context so that when you update the message or solve the cipher, you can derive state update from the tracked dependencies.

To convert solvedMessage to a computed(), perform the following steps under the TODO(2): Define your first computed() comment in each file:

  1. In the service.message.ts file, use the Signals library to make solvedMessage reactive:

src/app/secret-message/service.message.ts

solvedMessage = computed(() =>
  this.translateMessage(
    this.secretMessage(), 
    this.cipher.decodedCipher()
  )
);

This automatically prompts you to import computed from @angular/core. If you refresh the page, you'll likely run into errors where you previously referred to solvedMessage. This is because you've changed the type of superSecretMessage from string to Signal<string>, a function. You can fix this by changing all references of solvedMessage to solvedMessage().

  1. In the secret-message.ts file, update all references of solvedMessage to solvedMessage():

src/app/secret-message/secret-message.ts

// Before
<span *ngFor="let char of this.messages.solvedMessage.split(''); index as i;" [class.unsolved]="this.messages.solvedMessage[i] !== this.messages.superSecretMessage()[i]" >{{ char }}</span>

// After
<span *ngFor="let char of this.messages.solvedMessage().split(''); index as i;" [class.unsolved]="this.messages.solvedMessage()[i] !== this.messages.superSecretMessage()[i]" >{{ char }}</span>

Note that unlike superSecretMessage, solvedMessage is not a SettableSignal—you can't change its value directly. Instead, its value is kept up to date whenever either of its dependency signals (secretMessage and decodedCipher) are updated.

Explore the two other computed() functions

  • Notice that you have two other computed values in your app:

src/app/secret-message/service.message.ts

secretMessage = computed(() => 
  this.translateMessage(
    this.superSecretMessage(),
    this.cipher.cipher()
  )
);

src/app/cipher/service.cipher.ts

unsolvedAlphabet = computed(() =>
  ALPHABET.filter(
    (letter) => !this.decodedCipher().find((guess) => guess.value === letter)
  )
);

The MessageService defines a secretMessage computed, the superSecretMessage encoded by the cipher that players work to solve.

The CipherService defines an unsolvedAlphabet computed, a list of all of the letters the player has not solved, which is derived from the list of solved cipher keys in decodedCipher.

Verify changes

Now that superSecretMessage is a signal and solvedMessage is a computed, the app should work! Test out the game's functionalities:

  1. Drag and drop a LetterGuessComponent to a LetterKeyComponent in your CipherComponent to work toward solving the cipher and decoding the secret message.
  2. See how the SecretMessageComponent updates as you decode more of the secret message.
  3. Click Customize to change the Sender and Message, and then click Create & Copy URL to see the values on the screen and the URL change.
  4. Bonus: Copy and paste the URL to a new tab, or share with a friend, and see how the sender and message are stored in the URL.

GIF of the Angular Cypher game, with a hidden message being decoded on the screen to spell 'Angular Signals are in developer preview in v16 today!'

6. Add your first effect()

There are times when you might want something to occur when a signal has a new value. With effect(), you can schedule and run a handler function in response to signals changing.

Add confetti when the cipher is solved

Now that the app is functional, you can add some fun by adding confetti when the cipher is solved and the secret message is decoded.

To add confetti, perform the following steps under the TODO(3): Add your first effect() comment:

  1. In the cipher.ts file, schedule an effect to add confetti when the message is decoded:

src/app/cipher/cipher.ts

import * as confetti from 'canvas-confetti';

ngOnInit(): void {
  ...

  effect(() => {
    if (this.messages.superSecretMessage() === this.messages.solvedMessage()) {
      var confettiCanvas = document.getElementById('confetti-canvas');
      confetti.create()(confettiCanvas, { particleCount: 100 });
    }
  });
}

Notice how this effect depends on a signal and a computed value: this.messages.superSecretMessage() and this.messages.solvedMessage().

Effect helps you schedule the confetti function inside a reactive context to track and reevaluate when its dependencies are updated.

Verify changes

  • Try solving the cipher (hint: you can change the message to something short to test quicker!). A confetti pop will congratulate you on your first effect()!

GIF of the Angular Cypher game, with a hidden message being decoded on the screen to spell 'Confetti time!' and confetti poppers going off when the message is solved.

7. Congratulations!

Your Angular Cipher is now ready to decode and share secret messages! Have a message for the Angular Team? Tag our social media at @Angular so we can decode it! 🎉

Angular Cypher game solved with a hidden message on the screen of 'Angular Signals are in developer preview in v16 today!'

You now have three new reactive primitives in your Angular toolbox to simplify your development and build faster apps by default.

Learn more

Check out these codelabs:

Read these materials: