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 theuserCardNumber$
BehaviorSubject
on theinput
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>