RxJS in Angular page

Learn how to read and write observables in Angular.

Video

Who has time to read? This video covers the content on this page. Watch fullscreen.

The problem

In this section, we will:

  • Create a CodePen setup with Angular and the HTML of our form.
  • Write values to a Subject and write out the value of the subject in the template.

Observables and the $ suffix

At Bitovi, we follow the ts.dev Style Guide and prefer to suffix Observables with $.

Although not enforced, it is useful to visually differentiate between observable and non-observable values.

How to solve this problem

  • Create a userCardNumber$ BehaviorSubject.
  • Write the value of the cardNumber input to the userCardNumber$ BehaviorSubject on the input event.
  • Write the value of cardNumber out in the template under the </form> element like:
    </form>
    UserCardNumber: {{ GET-CARD-NUMBER-HERE }} <br />
    

What you need to know

Click "Run in your browser" at the bottom of the following example to launch a CodePen with Angular and the HTML of the credit card form. You will edit this CodePen for the remainder of this tutorial.

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.19.0/core.js"></script>
<script src="https://unpkg.com/@angular/core@12.2.16/bundles/core.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js"></script>
<script src="https://unpkg.com/@angular/common@12.2.16/bundles/common.umd.js"></script>
<script src="https://unpkg.com/@angular/compiler@12.2.16/bundles/compiler.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser@12.2.16/bundles/platform-browser.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser-dynamic@12.2.16/bundles/platform-browser-dynamic.umd.js"></script>
<my-app></my-app>
<script type="typescript">
  // app.js
  const { Component, VERSION } = ng.core;

  @Component({
    selector: 'my-app',
    template: `
      <form>
        <div class="message"></div>

        <input type="text" name="cardNumber" placeholder="Card Number" />

        <input type="text" name="expiry" placeholder="MM-YY" />

        <input type="text" name="cvc" placeholder="CVC" />

        <button>
          PAY
        </button>
      </form>
    `
  })
  class AppComponent {

      constructor() {

      }
  }

  // main.js
  const { BrowserModule } = ng.platformBrowser;
  const { NgModule } = ng.core;
  const { CommonModule } = ng.common;

  @NgModule({
    imports: [
      BrowserModule,
      CommonModule,
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: []
  })
  class AppModule {}

  const { platformBrowserDynamic } = ng.platformBrowserDynamic;

  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
</script>
<style>
  @import url('https://fonts.googleapis.com/css?family=Raleway:400,500');
  body {
    background-color: rgba(8, 211, 67, 0.3);
    padding: 2%;
    font-family: 'Raleway', sans-serif;
    font-size: 1em;
  }
  input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    font-weight: 500;
    padding: 12px;
    border: 1px solid #ccc;
    outline-color: white;
    transition: background-color 0.5s ease;
    transition: outline-color 0.5s ease;
  }
  input[name='cardNumber'] {
    border-bottom: 0;
  }
  input[name='expiry'],
  input[name='cvc'] {
    width: 50%;
  }
  input[name='expiry'] {
    float: left;
    border-right: 0;
  }
  input::placeholder {
    color: #999;
    font-weight: 400;
  }
  input:focus {
    background-color: rgba(130, 245, 249, 0.1);
    outline-color: #82f5f9;
  }
  input.is-error {
    background-color: rgba(250, 55, 55, 0.1);
  }
  input.is-error:focus {
    outline-color: #ffbdbd;
  }
  button {
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    background-color: #08d343;
    border: 0;
    box-shadow: 0px 1px 3px 1px rgba(51, 51, 51, 0.16);
    color: white;
    font-weight: 500;
    letter-spacing: 1px;
    margin-top: 30px;
    padding: 12px;
    text-transform: uppercase;
    width: 100%;
  }
  button:disabled {
    opacity: 0.4;
    background-color: #999999;
  }
  form {
    background-color: white;
    box-shadow: 0px 17px 22px 1px rgba(51, 51, 51, 0.16);
    padding: 40px;
    margin: 0 auto;
    max-width: 500px;
  }
  .message {
    margin-bottom: 20px;
    color: #fa3737;
  }
</style>

Initialize a BehaviorSubject instance on a class like the following:

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script type="typescript">
  const { BehaviorSubject } = rxjs;

  class AppComponent {
    userCardNumber$ = new BehaviorSubject<string>();
  }

  const app = new AppComponent();
  console.log(app.userCardNumber$) // logs Observable
</script>

A BehaviorSubject is just like a Subject except that it remembers its last value. Any new subscribers will immediately be sent the last value.

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script type="typescript">
  const { BehaviorSubject } = rxjs;

  const subject = new BehaviorSubject<string>();

  subject.next("THE PAST");

  subject.subscribe((value) => console.log(value));
  // logs "THE PAST"
</script>

Emit a value on a Subject by calling subject.next():

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script type="typescript">
  const { BehaviorSubject } = rxjs;

  class AppComponent {
    userCardNumber$ = new BehaviorSubject<string>();
  }

  const app = new AppComponent();

  app.userCardNumber$.subscribe(console.log); // logs undefined

  app.userCardNumber$.next("1111-2222-3333-4444"); // logs "1111-2222-3333-4444"
</script>

You can call subject.next() in the DOM as follows:

<input
  type="text"
  name="cardNumber"
  placeholder="Card Number"
  (input)="userCardNumber$.next($event.target.value)"
/>

Use | async to write out an observable’s value in a template as follows:

UserCardNumber: {{ userCardNumber$ | async }} <br />

Read more about this technique here.

The solution

Click to see the solution

<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/core-js/3.19.0/core.js"></script>
<script src="https://unpkg.com/@angular/core@12.2.16/bundles/core.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js"></script>
<script src="https://unpkg.com/@angular/common@12.2.16/bundles/common.umd.js"></script>
<script src="https://unpkg.com/@angular/compiler@12.2.16/bundles/compiler.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser@12.2.16/bundles/platform-browser.umd.js"></script>
<script src="https://unpkg.com/@angular/platform-browser-dynamic@12.2.16/bundles/platform-browser-dynamic.umd.js"></script>
<my-app></my-app>
<script type="typescript">
  // app.js
  const { Component, VERSION } = ng.core;
  const { BehaviorSubject } = rxjs;

  @Component({
    selector: 'my-app',
    template: `
      <form>
          <div class="message"></div>

          <input
            type="text"
            name="cardNumber"
            placeholder="Card Number"
            (input)="userCardNumber$.next($event.target.value)"
          />

          <input type="text" name="expiry" placeholder="MM-YY" />

          <input type="text" name="cvc" placeholder="CVC" />

          <button>
            PAY
          </button>
      </form>
      UserCardNumber: {{ userCardNumber$ | async }} <br />
    `
  })
  class AppComponent {
    userCardNumber$ = new BehaviorSubject<string>();

    constructor() {

    }
  }

  // main.js
  const { BrowserModule } = ng.platformBrowser;
  const { NgModule } = ng.core;
  const { CommonModule } = ng.common;

  @NgModule({
    imports: [
      BrowserModule,
      CommonModule,
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent],
    providers: []
  })
  class AppModule {}

  const { platformBrowserDynamic } = ng.platformBrowserDynamic;

  platformBrowserDynamic()
    .bootstrapModule(AppModule)
    .catch(err => console.error(err));
</script>
<style>
  @import url('https://fonts.googleapis.com/css?family=Raleway:400,500');
  body {
    background-color: rgba(8, 211, 67, 0.3);
    padding: 2%;
    font-family: 'Raleway', sans-serif;
    font-size: 1em;
  }
  input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    font-weight: 500;
    padding: 12px;
    border: 1px solid #ccc;
    outline-color: white;
    transition: background-color 0.5s ease;
    transition: outline-color 0.5s ease;
  }
  input[name='cardNumber'] {
    border-bottom: 0;
  }
  input[name='expiry'],
  input[name='cvc'] {
    width: 50%;
  }
  input[name='expiry'] {
    float: left;
    border-right: 0;
  }
  input::placeholder {
    color: #999;
    font-weight: 400;
  }
  input:focus {
    background-color: rgba(130, 245, 249, 0.1);
    outline-color: #82f5f9;
  }
  input.is-error {
    background-color: rgba(250, 55, 55, 0.1);
  }
  input.is-error:focus {
    outline-color: #ffbdbd;
  }
  button {
    font-size: 1em;
    font-family: 'Raleway', sans-serif;
    background-color: #08d343;
    border: 0;
    box-shadow: 0px 1px 3px 1px rgba(51, 51, 51, 0.16);
    color: white;
    font-weight: 500;
    letter-spacing: 1px;
    margin-top: 30px;
    padding: 12px;
    text-transform: uppercase;
    width: 100%;
  }
  button:disabled {
    opacity: 0.4;
    background-color: #999999;
  }
  form {
    background-color: white;
    box-shadow: 0px 17px 22px 1px rgba(51, 51, 51, 0.16);
    padding: 40px;
    margin: 0 auto;
    max-width: 500px;
  }
  .message {
    margin-bottom: 20px;
    color: #fa3737;
  }
</style>