This post describes how to refactor component to use ViewChildren to query DOM elements. It is a common case when inline template has several elements of the same type and the component uses ViewChild to query them. When this pattern occurs, we can consider to render the elements using ngFor and applying ViewChildren to obtain the elements in QueryList.
In PokemonControlsComponent standalone component, the inline template has four buttons with different template variable names. In the component, I applied ViewChild decorators to access the button elements and create button click Observables.
Then, I use merge operator to merge these Observables to emit the current Pokemon id and call Pokemon API to retrieve the current Pokemon
Since these buttons have the same type, I can refactor the template to render them using NgFor and assigning the same template variable name. Moreover, I can simplify all occurrences of ViewChild with a single ViewChildren decorator.
Rather than copy and paste <button> four times, I will render the buttons using ngFor. Before using ngFor, NgFor has to be imported because the component is standalone.
The inline template renders all the buttons and assigns #btn template variable name to them. [attr.data-delta]="delta" stores the delta in data attribute named delta and the data attribute will be used when creating button click Observables. In the component, I update the code to apply ViewChildren to query the button elements.
this.btns is QueryList and merge expects a spread array. Therefore, I iterate the QueryList to construct an array of Observables and pass the array to merge.
This post wants to illustrate how to use switchMap operator to cancel previous HTTP requests and retrieve the latest Pokemon by an id. It is an important technique to preserve scarce network resource on a device such as mobile phone that has limited data plan. It also improves performance because the application does not have to wait for previous requests to return and display until the last request comes back with the final result.
Bootstrap AppComponent
// main.ts
import 'zone.js/dist/zone';
import { provideHttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { PokemonComponent } from './pokemon/pokemon/pokemon.component';
@Component({
selector: 'my-app',
standalone: true,
imports: [PokemonComponent],
template: `
<app-pokemon></app-pokemon>
`,
})
export class AppComponent {
name = 'Angular';
}
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()]
});
In main.ts, I bootstrapped AppComponent as the root element of the application. It is possible because AppComponent is a standalone component and Component decorator defines standalone: true option. In the imports array, I imported PokemonComponent (that also a standalone component) and it is responsible to display information of a Pokemon. The second argument of bootstrapApplication is an array of providers and provideHttpClient makes HttpClient available for injection in components.
createButtonClickObservable creates an Observable that emits a number when button click occurs. When button text is +1 or +2, the Observables emit 1 or 2 respectively. When button text is -1 or -2, the Observables emit -1 or -2 respectively.
When btnPokemonId$ emits a Pokemon id, I use switchMap to cancel previous HTTP requests and make a new request to retrieve the Pokemon. The result is then assigned to this.pokemon$ that is an Observable of Pokemon.
Inline template uses async pipe to resolve pokemon$ Observable and displays the details of the Pokemon.
This is it and I have added HTTP capability to call Pokemon API. Moreover, I leverage switchMap to end earlier requests and trigger a new one whenever new pokemon id is emitted.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This post wants to illustrate how powerful RxJS is when building a reactive user interface in Angular. The application is consisted of a group of buttons that can increment and decrement Pokemon id. When button click occurs, the observable emits the new id and renders the images of the new Pokemon.
In main.ts, I bootstrapped AppComponent as the root element of the application. It is possible because AppComponent is a standalone component and Component decorator defines standalone: true option. In the imports array, I imported PokemonComponent (that also a standalone component) and it is responsible to implement the reactive user interface with RxJS.
createButtonClickObservable creates an Observable that emits a number when button click occurs. When button text is +1 or +2, the Observables emit 1 or 2 respectively. When button text is -1 or -2, the Observables emit -1 or -2 respectively.
User scan operator to update the Pokemon id. When Pokemon id is less than 1, reset the id to 1. When Pokemon id is greater than 100, reset the id to 100.
The application displays the first 100 Pokemons only
startWith(1)
Set the initial Pokemon id to 1 to display the first Pokemon
This is day 20 of Wes Bos’s JavaScript 30 challenge where I build a speech detection application using RxJS, Angular standalone components and Web Speech API. Angular not only can call it’s own APIs to render components but it can also interact with Web Speech API to guess what I spoke in English and outputted its confidence level. The API amazed me due to its high accuracy and confidence level as if a real human being was listening to me and understood my accent.
Create a new Angular project
ng generate application day20-speech-detection-standalone
Bootstrap AppComponent
First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SpeechDetectionComponent } from './speech-detection/speech-detection/speech-detection.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [SpeechDetectionComponent],
template: '<app-speech-detection></app-speech-detection>',
styles: [
`
:host {
display: block;
}
`,
],
})
export class AppComponent {
title = 'Day20 Speech Detection Standalone';
constructor(titleService: Title) {
titleService.setTitle(this.title);
}
}
In Component decorator, I put standalone: true to convert AppComponent into a standalone component.
Instead of importing SpeechDetectionComponent in AppModule, I import SpeechDetectionComponent (that is also a standalone component) in the imports array because the inline template references it. It is because main.ts uses bootstrapApplication to render AppComponent as the root component of the application. When compiler sees <app-speech-detection> in the inline template and AppComponent does not import SpeechDetectionComponent, the application fails to compile.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
Next, I can delete AppModule that has no use now.
Declare speech detection component
I declare standalone component, SpeechDetectionComponent, to start the speech detection process. To verify the component is standalone, standalone: true is specified in the Component decorator.
createRecognition function instantiates a SpeechRecognition service, sets the language to English and interimResult to true.
Explain createRecognitionSubscription
createRecognitionSubscription function restarts speech recognition and subscribes the Observable after the previous speech recognition ends.
fromEvent(recognition, ‘end’) – Emit value when end event of speech recognition service occurs
tap(() => recognition.start()) – restart speech recognition to recognize the next sentence
subscribe() – subscribe observable
Explain createWordListObservable
createWordListObservable function emits a list of phrases recognized by Web Speech API.
fromEvent(recognition, ‘result’) – Speech recognition emits a word or phrase
map() – replace foul languages with emoji and return the phrase, confidential level and the isFinal flag
filter(({ isFinal }) => isFinal) – emit value when the final result is ready
scan() – append the phrase and confidence level to the word list
Demystifies RxJS logic in SpeechDetectionComponent
SpeechDetectionComponent class is succinct now after refactoring Observable and Subscription codes to speech-detection.helper.ts. They are extracted to functions in helper file
This is it and I have built a speech recognition application with RxJS, Angular standalone components and Web Speech API.
Final Thoughts
In this post, I show how to use RxJS, Angular standalone components and web speech API to build speech detection. The application has the following characteristics after using Angular 15’s new features:
The application does not have NgModules and constructor boilerplate codes.
The standalone component is very clean because I moved as many RxJS codes to separate helper file as possible.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 29 of Wes Bos’s JavaScript 30 challenge where I build a countdown timer using RxJS, standalone components and standalone directive in Angular 15.
Create a new Angular project
ng generate application day19-countdown-timer-standalone
Bootstrap AppComponent
First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TimerComponent } from './timer/timer/timer.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [
TimerComponent
],
template: '<app-timer></app-timer>',
styles: [`
:host {
display: block;
}
`]
})
export class AppComponent {
title = 'Day 29 Standalone Countdown Timer';
constructor(titleService: Title) {
titleService.setTitle(this.title);
}
}
In Component decorator, I put standalone: true to convert AppComponent into a standalone component.
Instead of importing TimerComponent in AppModule, I import TimerComponent (that is also a standalone component) in the imports array because the inline template references it.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent).catch(err => console.error(err));
Next, I can delete AppModule that is now useless.
Declare Timers components to build a countdown timer
I declare standalone components, TimerComponent, TimerControlsComponent and TimerPaneComponent to build a reactive countdown timer. To verify the components are standalone, standalone: true is specified in the Component decorator.
TimerComponent acts like a shell that encloses TimerControlsComponent and TimerPaneComponent. For your information, <app-timer> is the tag of TimerComponent.
TimerComponent imports TimerControlsComponent and TimerPaneComponent in the imports array. TimerControlsComponent is included such that I can use <app-timer-controls> in the inline template. Similarly, TimerPaneComponent provides <app-timer-pane> selector that is also seen in the inline template.
TimerControlsComponent encapsulates buttons and input field to emit selected seconds whereas TimePaneComponent subscribes to the emitted value to initiate count down and render time left.
TimerControlsComponent is consisted of a template form and a list of buttons; therefore, I import FormsModule and a standalone directive, TimerButtonDirective. I define the directive in order to query the button elements with ViewChildren decorator.
I declare all the components that require to build the countdown timer. Next section is going to describe TimerService that shares specified seconds between components.
Add timer service to share RxJS subjects and observables
TimerService stores Subjects and Observables that the components subscribe to receive specified seconds.
// timer.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class TimerService {
private readonly secondsSub = new Subject<number>();
readonly seconds$ = this.secondsSub.asObservable();
updateSeconds(seconds: number) {
this.secondsSub.next(seconds);
}
}
Demystifies RxJS logic in TimerControlsComponent
TimerControlsComponent encapsulates all RxJS observables and subscription in the following lines:
createButtonObservablesFn is a high order function that returns a function. The function is ultimately executed in ngAfterViewInit to construct timer button observables.
const totalSeconds = +(nativeElement.dataset[‘seconds’] || ‘0’) – extract the value of data-seconds and convert to a number
fromEvent(nativeElement, ‘click’).pipe(map(() => totalSeconds)) – emit the specified seconds when user clicks button element
Explain timerInputSubscriptionFn
timerInputSubscriptionFn is also a high order function that returns a function. The function returns a subscription that updates the Subject in TimerService.
inject(TimerService) – inject TimerService in function instead of constructor
merge(…observables) – merge all observables to create a new observable that emits second
subscribe((seconds) => { timerService.updateSeconds(seconds); }) – subscribe observable and update TimerService with the selected seconds
Demystifies RxJS logic in TimerPaneComponent
TimerPaneComponent class is so succinct that you wonder where the Observable codes go. They are extracted to functions in helper file
nowTo function observes timeService.second$, caches the value with shareReplay and return the Observable.
displayEndTimeFn is a function that returns the end time of the the timer in an Observable. displayEndTime function performs DateTime manipulation and prints the result in a message.
displayTimeLeftFn simulates the count down effect reactively.
When nowTo$ emits seconds, (let’s say N), I have to cancel the previous timer and create a new timer that emits (N + 1) values (0, 1, 2, ….N). Therefore, I use switchMap to return a timer observable
When countDown$ emits a value, 1 second has elapsed and time remained should also decrement.
withLatestFrom(nowTo$) obtains the selected seconds
map(([countdown, secondsLeft]) => secondsLeft – countdown) derives the remaining seconds
map((secondsLeft) => displayTimeLeft(secondsLeft)) displays the remaining seconds in mm:ss format
tap((strTimeLeft) => titleService.setTitle(strTimeLeft)) updates the document title to display the remaining time
This is it and I have built a reactive countdown timer using standalone components and directive only.
Final Thoughts
In this post, I show how to use RxJS, Angular standalone components and directive to build a countdown timer. The application has the following characteristics after using Angular 15’s new features:
The application does not have NgModules and constructor boilerplate codes.
The standalone components are very clean because I moved as many RxJS codes and dependency injections to separate helper files as possible.
Using inject offers flexibility in code organization. Initially, I constructed Observables functions outside of component class and later moved those functions to helper files. Pre-Angular 15, TimeService and Title must inject in constructor and I implement additional statements and methods inside the component to access them.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 6 of Wes Bos’s JavaScript 30 challenge where I create real-time search input box to filter out cities and states in the USA. This is a challenge for me because I had to rewrite the original real-time search using Angular and adopting the styles of the framework. In the tutorial, I created the components using RxJS, custom operators, Angular standalone components and removed the NgModules. Moreover, Angular HTTP Client is responsible for retrieving the JSON data from GitHub gist.
In this blog post, I define a function that injects HttpClient, retrieves USA cities from external JSON file and caches the response. Next, I create an observable that emits search input to filter out USA cities and states. Finally, I use async pipe to resolve the observable in the inline template to render the matching results.
Create a new Angular project
ng generate application day6-ng-type-ahead
Bootstrap AppComponent
First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TypeAheadComponent } from './type-ahead/type-ahead/type-ahead.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [
TypeAheadComponent
],
template: '<app-type-ahead></app-type-ahead>',
styles: [`
:host {
display: block;
}
`]
})
export class AppComponent {
title = 'Day 6 NG Type Ahead';
constructor(titleService: Title) {
titleService.setTitle(this.title);
}
}
In Component decorator, I put standalone: true to convert AppComponent into a standalone component.
Instead of importing TypeAheadComponent in AppModule, I import TypeAheadComponent (that is also a standalone component) in the imports array because the inline template references it. It is because main.ts uses bootstrapApplication to render AppComponent as the root component of the application. When compiler sees <app-type-ahead> in the inline template and AppComponent does not import TypeAheadComponent, the application fails to compile.
// main.ts
import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent,
{
providers: [provideHttpClient()]
})
.catch(err => console.error(err));
provideHttpClient is a function that configures HttpClient to be available for injection.
Next, I delete AppModule because it is not used anymore.
Declare Type Ahead component
I declare standalone component, TypeAheadComponent, to create a component with search box. To verify the component is a standalone, standalone: true is specified in the Component decorator.
TypeAheadComponent imports CommonModule, FormsModule and HighlightSuggestionPipe in the imports array. CommonModule is included to make ngIf and async pipe available in the inline template. After importing FormsModule, I can build a template form to accept search value. Finally, HighlightSuggestionPipe highlights the search value in the search results for aesthetic purpose.
cities$ is an observable that fetches USA cities from external JSON file. Angular 15 introduces inject that simplifies HTTP request logic in a function. Thus, I don’t need to inject HttpClient in the constructor and perform the same logic.
suggestions$ is an observable that holds the matching cities and states after search value changes. It is subsequently resolved in inline template to render in a list.
Create RxJS custom operator
It is a matter of taste but I prefer to refactor RxJS operators into custom operators when observable has many lines of code. For suggestion$, I refactor the chain of operators into findCities custom operator and reuse it in TypeAheadComponent.
// find-cities.operator.ts
const findMatches = (formValue: { searchValue: string }, cities: City[]) => {
const wordToMatch = formValue.searchValue;
if (wordToMatch === '') {
return [];
}
const regex = new RegExp(wordToMatch, 'gi');
// here we need to figure out if the city or state matches what was searched
return cities.filter(place => place.city.match(regex) || place.state.match(regex));
}
export const findCities = (cities$: Observable<City[]>) => {
return (source: Observable<{ searchValue: string }>) =>
source.pipe(
skip(1),
debounceTime(300),
distinctUntilChanged(),
withLatestFrom(cities$),
map(([formValue, cities]) => findMatches(formValue, cities)),
startWith([]),
);
}
skip(1) – The first valueChange emits undefined for unknown reason; therefore, skip is used to discard it
debounceTime(300) – emit search value after user stops typing for 300 milliseconds
distinctUntilChanged() – do nothing when search value is unchanged
withLatestFrom(cities$) – get the cities returned from HTTP request
map(([formValue, cities]) => findMatches(formValue, cities)) – call findMatches to filter cities and states by search value
startWith([]) – initially, the search result is an empty array
Finally, I use findCities to compose suggestion$ observable.
Use RxJS and Angular to implement observable in type ahead component
this.searchForm.form.valueChanges – emit changes in template form
findCities(this.cities$) – apply custom operator to find matching cities and states
This is it, we have created a real-time search that filters out USA cities and states by search value.
Final Thoughts
In this post, I show how to use RxJS and Angular standalone components to create real-time search of the USA cities and states. The application has the following characteristics after using Angular 15’s new features:
The application does not have NgModules and constructor boilerplate codes.
In main.ts, the providers array provides the HttpClient by invoking providerHttpClient function
In TypeAheadComponent, I inject HttpClient in a function to make http request and obtain the results. In construction phase, I assign the function to cities$ observable
Using inject to inject HttpClient offers flexibility in code organization. I can define getCities function in the component or move it to a utility file. Pre-Angular 15, HttpClient is usually injected in a service and the service has a method to make HTTP request and return the results
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 2 of Wes Bos’s JavaScript 30 challenge where I create an analog clock that displays the current time of the day. In the tutorial, I created the components using RxJS, custom operators, Angular standalone components and removed the NgModules.
In this blog post, I describe how to create an observable that draws the hour, minute and second hands of an analog clock. The clock component creates a timer that emits every second to get the current time, calculates the rotation angle of the hands and set the CSS styles to perform line rotation.
Create a new Angular project
ng generate application day2-ng-and-css-clock
Bootstrap AppComponent
First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ClockComponent } from './clock';
@Component({
selector: 'app-root',
standalone: true,
imports: [
ClockComponent
],
template: '<app-clock></app-clock>',
styles: [`
:host {
display: block;
}
`],
})
export class AppComponent {
constructor(titleService: Title) {
titleService.setTitle('Day 2 NG and CSS Clock');
}
}
In Component decorator, I put standalone: true to convert AppComponent into a standalone component.
Instead of importing ClockComponent in AppModule, I import ClockComponent (that is also a standalone component) in the imports array because the inline template references it.
// main.ts
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
bootstrapApplication(AppComponent).catch(err => console.error(err));
Second, I delete AppModule because it is not used anymore.
Declare Clock component
I declare standalone component, ClockComponent, to create an analog clock. To verify the component is a standalone, standalone: true is specified in the Component decorator.
clock-operators.ts encapsulates two custom RxJS operators that help draw the clock hands of the analog clock. currentTime operator returns seconds, minutes and hours of the current time. rotateClockHands receives the results of currentTime and calculate the rotation angle of the hands.
ClockComponent imports NgIf and AsyncPipe because the components uses ngIf and async keywords to resolve clockHandsTransform$ observable. clockHandsTransform$ is an observable that is consisted of the CSS styles to draw hour, minute and second hands. The observable is succinct because the currentTime and rotateClockHands custom operators encapsulate the logic.
Create RxJS custom operators
It is a matter of taste but I prefer to refactor RxJS operators into custom operators when observable has many lines of code. For clockHandsTransform$, I refactor map into custom operators and reuse them in ClockComponent.
// clock.operator.ts
export function currentTime() {
return map(() => {
const time = new Date();
return {
seconds: time.getSeconds(),
minutes: time.getMinutes(),
hours: time.getHours()
}
});
}
currentTime operator gets the current time and calls the methods of the Date object to return the current second, minutes and hours.
currentTime emits the results to rotateClockHands and the rotateClockHands operator invokes a helper function, rotateAngle, to derive the CSS styles of the hands.
Finally, I use both operators to compose clockHandsTransform$ observable.
Use RxJS and Angular to implement observable in clock component
timer(0, this.oneSecond) – emits an integer every second
currentTime() – return the current second, minute and hour
rotateClockHands() – calculate the rotation angle of second, minute and hour hands
This is it, we have created a functional analog clock that displays the current time.
Final Thoughts
In this post, I show how to use RxJS and Angular standalone components to create an analog clock. The application has the following characteristics after using Angular 15’s new features:
The application does not have NgModules and constructor boilerplate codes.
In ClockComponent, I import NgIf and AsyncPipe rather than CommonModule, only the minimum parts that the component requires.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 1 of Wes Bos’s JavaScript 30 challenge where I create a drum kit to play sounds when keys are pressed. In the tutorial, I created the components using RxJS, Angular standalone components and removed the NgModules.
In this blog post, I describe how the drum component (parent) uses RxJS fromEvent to listen to keydown event, discard unwanted keys and play sound when “A”, “S”, “D”, “F”, “G”, “H”, “J”, “K” or “L” is hit. When the correct key is pressed, the parent updates the subject that drum kit components (children) subscribe to. Then, the child with the matching key plays the corresponding sound to make a tune.
Create a new Angular project
ng generate application day1-javascript-drum-kit
Bootstrap AppComponent
First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { DrumComponent } from './drum';
@Component({
selector: 'app-root',
imports: [
DrumComponent,
],
template: '<app-drum></app-drum>',
styles: [`
:host {
display: block;
height: 100vh;
}
`],
standalone: true,
})
export class AppComponent {
title = 'RxJS Drum Kit';
constructor(private titleService: Title) {
this.titleService.setTitle(this.title);
}
}
In Component decorator, I put standalone: true to convert AppComponent into a standalone component.
Instead of importing DrumComponent in AppModule, I import DrumComponent (that is also a standalone component) in the imports array because the inline template references it.
// main.ts
import { enableProdMode, inject } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
import { AppComponent } from './app/app.component';
import { browserWindowProvider, windowProvider } from './app/core/services';
import { environment } from './environments/environment';
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_BASE_HREF,
useFactory: () => inject(PlatformLocation).getBaseHrefFromDOM(),
},
browserWindowProvider,
windowProvider,
]
})
.catch(err => console.error(err));
browserWindowProvider and windowProvider are providers from core folder and I will show the source codes later.
Second, I delete AppModule because it is not used anymore.
Add window service to listen to keydown event
In order to detect key down on native Window, I write a window service to inject to ScrollComponent to listen to keydown event. The sample code is from Brian Love’s blog post here.
// core/services/window.service.ts
import { isPlatformBrowser } from "@angular/common";
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';
/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');
/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {
get nativeWindow(): Window | Object {
throw new Error('Not implemented.');
}
}
/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
override get nativeWindow(): Object | Window {
return window;
}
}
/* Create an factory function that returns the native window object. */
function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
if (isPlatformBrowser(platformId)) {
return browserWindowRef.nativeWindow;
}
return new Object();
}
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
export const browserWindowProvider: ClassProvider = {
provide: WindowRef,
useClass: BrowserWindowRef
};
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
export const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [ WindowRef, PLATFORM_ID ]
};
I export browserWindowProvider and windowProvider to inject both providers in main.ts.
Declare Drum and DrumKey components
I declare standalone components, DrumComponent and DrumKeyComponent, to create a drum kit. To verify they are standalone, standalone: true is specified in the Component decorator.
getFullAssetPath and getHostNativeElement are helper functions that inject application base href and host native element in the construction phase of the components.
DrumComponent imports DrumKeyComponent and NgFor to render different drum keys. NgFor is required because inline template uses ng-for directive to create a drum kit. windowKeydownSubscription uses RxJS to create an Observable to observe keydown event and subscribe the Observable to return an instance of Subscription.
DrumKeyComponent constructs playSoundSubscription and transitionSubscription subscriptions to play the actual sound and display a yellow border until the sound ends. Using inject operator, I construct these subscriptions outside of constructor and ngOnInit.
Declare drum service to pass data from Drum to DrumKey component
When DrumComponent observes the correct key is pressed, the key must emit to DrumKeyComponent to perform CSS animation and play sound. The data is emit to Subject that is encapsulated in DrumService.
inject(DrumService).playDrumKey$ – observe playDrumKey$ observable of DrumService
filter(key => key === this.entry.key) – compare component’s key and the key pressed, and they are the same
subscribe(() => this.playSound()) – play the wav file
Let’s demystify drumKeyTranstionEnd and transitionSubscription
fromEvent(getHostNativeElement(), ‘transitionend’)- observe transition event of the host element
filter(evt => evt instanceof TransitionEvent) – filter event is an instance of TransitionEvent
map(evt => evt as TransitionEvent) – cast event to TransitionEvent
filter(evt => evt.propertyName === ‘transform’) – filter the event property is transform
subscribe(() => { this.isPlaying = false; this.cdr.markForCheck(); }) – subscribe the observable to update host class to display yellow border until the sound stops
This is it, we have created a drum kit that plays sound after pressing key.
Final Thoughts
In this post, I show how to use RxJS and Angular standalone components to create a drum kit. The application has the following characteristics after using Angular 15’s new features:
The application does not have NgModules and constructor boilerplate codes.
Apply inject operator to inject services in const functions outside of component classes. The component classes are shorter and become easy to comprehend.
In DrumKeyComponent, I assign subscriptions to instance members directly and don’t have to implement OnInit lifecycle hook.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 26 of Wes Bos’s JavaScript 30 challenge where I hover link and open dropdown below it. I am going to use RxJS and Angular to rewrite the challenge from Vanilla JavaScript.
In this blog post, I describe how to use RxJS fromEvent to listen to mouseenter event and emit the event to concatMap operator. In the callback of concatMap, it emits a timer to add another CSS class after 150 milliseconds. When CSS classes are added, a white dropdown appears below the link. The RxJS code creates the effect of hover link and open dropdown. Moreover, the tutorial also uses RxJS fromEvent to listen to mouseleave event to remove the CSS classes to close the dropdown.
Create a new Angular project
ng generate application day26-strike-follow-along-link
Create Stripe feature module
First, we create a Stripe feature module and import it into AppModule. The feature module encapsulates StripeNavPageComponent that is comprised of three links.
Import StripeModule in AppModule
// stripe.module.ts
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CoolLinkDirective } from './directives/cool-link.directive';
import { StripeNavPageComponent } from './stripe-nav-page/stripe-nav-page.component';
@NgModule({
declarations: [
StripeNavPageComponent,
CoolLinkDirective
],
imports: [
CommonModule
],
exports: [
StripeNavPageComponent
]
})
export class StripeModule { }
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { StripeModule } from './stripe';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
StripeModule
],
bootstrap: [AppComponent]
})
export class AppModule { }
Declare Stripe component in feature module
In Stripe feature module, we declare StripeNavPageComponent to build the application. StripeNavPageComponent depends on inline template, encapsulated SCSS file and global styles in order to work properly.
After numerous trials and errors, the CSS styles of navigation bar and dropdowns only work when I put them in the global stylesheet.
// stripe-nav-page.component.ts
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { CoolLinkDirective } from '../directives/cool-link.directive';
import { StripeService } from '../services/stripe.service';
@Component({
selector: 'app-stripe-nav-page',
template: `
<ng-container>
<h2>Cool</h2>
<nav class="top" #top>
<div class="dropdownBackground" #background>
<span class="arrow"></span>
</div>
<ul class="cool">
<li class="link">
<a href="#">About Me</a>
<ng-container *ngTemplateOutlet="aboutMe"></ng-container>
</li>
<li class="link">
<a href="#">Courses</a>
<ng-container *ngTemplateOutlet="courses"></ng-container>
</li>
<li class="link">
<a href="#">Other Links</a>
<ng-container *ngTemplateOutlet="social"></ng-container>
</li>
</ul>
</nav>
</ng-container>
<ng-template #aboutMe>
<div class="dropdown">
<div class="bio">
<img src="https://logo.clearbit.com/wesbos.com">
<p>Wes Bos sure does love web development. He teaches things like JavaScript, CSS and BBQ. Wait. BBQ isn't part of web development. It should be though!</p>
</div>
</div>
</ng-template>
<ng-template #courses>
<ul class="dropdown courses">
<ng-container *ngIf="coursesTaught$ | async as coursesTaught">
<li *ngFor="let x of coursesTaught; trackBy: trackByIndex">
<span class="code">{{ x.code }}</span>
<a [href]="x.link">{{ x.description }}</a>
</li>
</ng-container>
</ul>
</ng-template>
<ng-template #social>
<ul class="dropdown">
<ng-container *ngIf="socialAccounts$ | async as socialAccounts">
<li *ngFor="let account of socialAccounts; trackBy: trackByIndex">
<a class="button" [href]="account.link">{{ account.description }}</a>
</li>
</ng-container>
</ul>
</ng-template>
`,
styleUrls: ['./stripe-nav-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class StripeNavPageComponent implements AfterViewInit, OnDestroy {
socialAccounts$ = this.stripeService.getSocial();
coursesTaught$ = this.stripeService.getCourses();
constructor(private stripeService: StripeService) { }
ngAfterViewInit(): void {}
trackByIndex(index: number) {
return index;
}
ngOnDestroy(): void {}
}
Next, I delete boilerplate codes in AppComponent and render StripeNavPageComponent in inline template.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
@Component({
selector: 'app-root',
template: '<app-stripe-nav-page></app-stripe-nav-page>',
styles: [`
:host {
display: block;
}
`],
})
export class AppComponent {
title = 'Day 26 Stripe Follow Along Nav';
constructor(titleService: Title) {
titleService.setTitle(this.title);
}
}
Declare stripe service to retrieve data
In an enterprise application, courses and personal information are usually kept in a server. Therefore, I define a service that constructs fake requests to remote server. In the methods, the codes wait 250 – 300 milliseconds before returning the data array in Observable.
import { Injectable } from '@angular/core';
import { map, timer } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class StripeService {
getSocial() {
return timer(300)
.pipe(
map(() => ([
{
link: 'http://twitter.com/wesbos',
description: 'Twitter'
},
... other social media accounts ...
])
)
);
}
getCourses() {
return timer(250)
.pipe(
map(() => ([
{
code: 'RFB',
link: 'https://ReactForBeginners.com',
description: 'React For Beginners'
},
... other courses ...
])
)
);
}
}
StripeNavPageComponent injects the service to retrieve data, uses async pipe to resolve the Observables in the inline template and iterates the arrays to render the HTML elements.
coursesTaught$ = this.stripeService.getCourses();
<ng-container *ngIf="coursesTaught$ | async as coursesTaught">
<li *ngFor="let x of coursesTaught; trackBy: trackByIndex">
<span class="code">{{ x.code }}</span>
<a [href]="x.link">{{ x.description }}</a>
</li>
</ng-container>
Refactor RxJS logic to custom operator
In the complete implementation, ngAfterViewInit becomes bulky after putting in the RxJS logic. To make the logic more readable, I refactor the hover link logic to a custom RxJS operator.
hoverLink operator adds trigger-enter class to <li> element and emits the event to concatMap. concatMap emits a timer that waits 150 milliseconds before firing to add the second class, trigger-enter-active to the same element.
Use RxJS and Angular to implement hover link and open dropdown effect
Use ViewChildren to obtain references to <li> elements
fromEvent(nativeElement, ‘mouseenter’) – observe mouseenter event on the <li> element
hoverLink(nativeElement) – a custom decorator that encapsulates the logic to add trigger-enter-active and trigger-enter classes to the <li> element
subscribe(() => { … }) – select the associated dropdown and invoke translateBackground function. translateBackground function translates the white background to the position of the dropdown and opens it
this.subscriptions.add(mouseEnterSubscription) – append mouseEnterSubscription subscription to this.subscriptions in order to release the memory in ngOnDestroy
fromEvent(nativeElement, ‘mouseleave’) – observe mouseleave event on the <li> element
subscribe(() => { … }) – subscribe the observable to remove trigger-enter-active and trigger-enter class from the <li> element. Then, remove ‘open’ class to close the dropdown
this.subscriptions.add(mouseLeaveSubscription) – append mouseLeaveSubscription subscription to this.subscriptions in order to release the memory in ngOnDestroy
This is it, we have completed the tutorial that opens the associated dropdown when mouse hovers a list item.
Final Thoughts
In this post, I show how to use RxJS and Angular to hover link and open dropdown. fromEvent observes mouseenter event to add the first CSS class and emits the event to concatMap to add the second CSS class after 150 milliseconds. The observable is subscribed to translate the white background and open the dropdown. Moreover, another fromEvent emits mouseleave to remove the previously added classes and close the dropdown.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.
This is day 27 of Wes Bos’s JavaScript 30 challenge and I am going to use RxJS and Angular to click and slide a series of div elements. When mouse click occurs, I add a CSS class to the parent <div> element to perform scale transformation. When mouse is up or it exits the browser, I remove the class to undo the CSS transformation.
In this blog post, I describe how to use RxJS fromEvent to listen to mousedown event and emit the event to concatMap operator. In the callback of concatMap, it streams mousemove events, updates the scrollLeft property of the div element and flattens the inner Observables. Moreover, the Angular component resolves another Observable in the inline template in order to toggle CSS class in ngClass property.
Create a new Angular project
ng generate application day27-click-and-drag
Create Slider feature module
First, we create a Slider feature module and import it into AppModule. The feature module encapsulates SliderComponent with a list of <div> elements.
Import SliderModule in AppModule
// slider.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SliderComponent } from './slider/slider.component';
@NgModule({
declarations: [
SliderComponent
],
imports: [
CommonModule
],
exports: [
SliderComponent
]
})
export class SliderModule { }
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { SliderModule } from './slider';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
SliderModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Declare Slider component in feature module
In Slider feature module, we declare SliderComponent to build the application. SliderComponent depends on inline template and SCSS file because styling is long in this tutorial.
SliderComponent generates 25 <div> elements numbered between 01 and 25. When I click any <div> element, I can slide from left to right and vice versa. Moreover, the parent <div> of the <div> elements includes an active CSS class when sliding occurs.
this.active$ is a boolean Observable, it resolves and toggles the active CSS class of the <div> element. When the class is found in the element , scale transformation occurs and background color changes.
Next, I delete boilerplate codes in AppComponent and render SliderComponent in inline template.
// app.component.ts
import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
@Component({
selector: 'app-root',
template: '<app-slider></app-slider>',
styles: [`
:host {
display: block;
}
`]
})
export class AppComponent {
title = 'Day 27 Click and Drag';
constructor(titleService: Title) {
titleService.setTitle(this.title);
}
}
Use RxJS and Angular to implement SliderComponent
I am going to rewrite active$ Observable and create a click-and-slide subscription in ngOnInit method.
filter((moveDownEvent) => moveDownEvent instanceof MouseEvent) – filter event is MouseEvent
map((moveDownEvent) => moveDownEvent as MouseEvent) – cast event as MouseEvent
concatMap(…) – create a new Observable that flattens mouseMove$ inner Observables
mouseMove$.pipe(…) – when sliding occurs, I update the scrollLeft property of div element
tap((moveEvent) => moveEvent.preventDefault()) – invoke preventDefault method of the mouse event
takeUntil(stop$) – sliding stops when mouse exits browser or mouse up
Final Thoughts
In this post, I show how to use RxJS and Angular to click and slide div elements until mouse up or mouse leave occurs. fromEvent observes mousedown event and emits the event to concatMap to stream mousemove events. For each mousemove event, scrollLeft property of the div element is calculated and concatMap flattens the Observable. Then, the div element can scroll left or right.
Moreover, when mouse down occurs, the Observable emits true to add the active class to div element. Otherwise, the Observable emits false to remove the class.
This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.