Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
RxJS Cookbook for Reactive Programming
RxJS Cookbook for Reactive Programming

RxJS Cookbook for Reactive Programming: Discover 40+ real-world solutions for building async, event-driven web apps

Arrow left icon
Profile Icon Nikola Mitrović
Arrow right icon
$33.99
Paperback Mar 2025 310 pages 1st Edition
eBook
$26.99
Paperback
$33.99
Subscription
Free Trial
Renews at $19.99p/m
Arrow left icon
Profile Icon Nikola Mitrović
Arrow right icon
$33.99
Paperback Mar 2025 310 pages 1st Edition
eBook
$26.99
Paperback
$33.99
Subscription
Free Trial
Renews at $19.99p/m
eBook
$26.99
Paperback
$33.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

RxJS Cookbook for Reactive Programming

Building User Interfaces with RxJS

One of the areas where RxJS excels is handling user interactions and orchestrating events in the browser. In this chapter, we’re going to explore how to build awesome and interactive UI components that handle any interaction or side effect seamlessly.

In this chapter, we’ll cover the following recipes:

  • Unlocking a phone with precision using RxJS-powered swipe gestures
  • Learning indications with the progress bar
  • Streaming image loading seamlessly with Progressive Image
  • Optimizing loading tab content
  • Reacting to drag-and-drop events
  • Crafting your perfect audio player using flexible RxJS controls
  • Streamlining real-time updates with RxJS-powered notifications
  • Fetching data with the Infinite Scroll Timeline component

Technical requirements

To complete this chapter, you’ll need the following:

  • Angular v19+
  • Angular Material
  • RxJS v7
  • Node.js v22+
  • npm v11+ or pnpm v10+

The code for the recipes in this chapter can be found in this book’s GitHub repository: https://github.com/PacktPublishing/RxJS-Cookbook-for-Reactive-Programming/tree/main/Chapter02.

Unlocking a phone with precision using RxJS-powered swipe gestures

How cool would it be to have a phone unlock pattern component? In this recipe, we’re going to build a component like that so that we can seamlessly react to every user touch swipe, orchestrate all user events, and unlock the phone once a correct combination of numbers is entered.

How to do it…

To create a phone unlock component, we’ll create UI controls representing number pads and identify key events to react to user actions. Once the user lifts their finger off the screen, we’ll compare the result with the correct pattern to unlock our phone.

Step 1 – Creating number pads

Our swipe-unlock.component.html file must contain the following markup for the swipe area and all phone buttons:

<div #swipeArea class="swipe-area">
    <button #one class="number">1</button>
    <button #two class="number">2</button>
    <button #three class="number">3</button>
    <button #four class="number">4</button>
    <button #five class="number">5</button>
    <button #six class="number">6</button>
    <button #seven class="number">7</button>
    <button #eight class="number">8</button>
    <button #nine class="number">9</button>
    <button #zero class="number">0</button>
</div>

With a little bit of CSS magic, we can see the component in the UI:

Figure 2.1: Phone swipe component

Figure 2.1: Phone swipe component

Meanwhile, in our swipe-unlock.component.ts file, we can reference various elements of the number pad’s UI so that we can manipulate any events that are performed on them:

@ViewChild('swipeArea')
swipeArea!: ElementRef;
@ViewChildren('one, two, three, four, five, six, seven,
              eight, nine, zero')
numbers!: QueryList<ElementRef>;

Step 2 – Identifying user touch events

What we’re interested in are the events where a user touches the screen, moves (swipes), and lifts their finger off the screen. We can create those streams of events like so:

const touchStart$ = fromEvent<TouchEvent>(
    this.swipeArea.nativeElement,
    'touchstart'
);
const touchMove$ = fromEvent<TouchEvent>(
    this.swipeArea.nativeElement,
    'touchmove'
);
const touchEnd$ = fromEvent<TouchEvent>(
    this.swipeArea.nativeElement,
    'touchend'
);

From here, we can react to these events and figure out the coordinates of a touch event, check if it’s intersecting with the number pad area, and highlight it in the UI:

const swipe$ = touchStart$.pipe(
    switchMap(() =>
        touchMove$.pipe(
            takeUntil(touchEnd$),
            map((touchMove) => ({
                x: touchMove.touches[0].clientX,
                y: touchMove.touches[0].clientY,
            }))
        )
    ),
);

Now, when we subscribe to those swipe coordinates, we can perform the required actions in sequence, such as selecting the number pad and creating a dot trail:

swipe$.pipe(
    tap((dot) => this.selectNumber(dot)),
    mergeMap((dot) => this.createTrailDot(dot)),
).subscribe();

Step 3 – Marking selected number pads

After getting the coordinates from each swipe, we can easily check whether it’s intersecting the area surrounding the number pad:

private selectNumber(dot: PixelCoordinates): void {
    this.numbersElement.forEach((number) => {
        if (
        dot.y > number.getBoundingClientRect().top &&
        dot.y < number.getBoundingClientRect().bottom &&
        dot.x > number.getBoundingClientRect().left &&
        dot.x < number.getBoundingClientRect().right
      ) {
            number.classList.add('selected');
            this.patternAttempt.push(parseInt(
                number.innerText)  
            );
        }
    });
}

By adding a selected class to each intersecting element, we can visually represent the selected number pads:

Figure 2.2: Marking the selected number pads

Figure 2.2: Marking the selected number pads

Step 4 – Creating a trail

With the help of the mergeMap operator, we can assemble all swipe events and their coordinates, create a dot in the DOM representing the trail of user action, and, after a certain delay, remove the trail from the DOM. Additionally, a nice performance consideration might be grouping many swipe events into one buffer. We can do this by using bufferCount, an operator that helps us to ensure optimal memory usage and computational efficiency:

private createTrailDot(
    dotCoordinates: PixelCoordinates
): Observable<string[]> {
    const dot = document.createElement('div');
    dot.classList.add('trail-dot');
    dot.style.left = `${dotCoordinates.x}px`;
    dot.style.top = `${dotCoordinates.y}px`;
    this.swipeArea.nativeElement.appendChild(dot);
    return of('').pipe(
        delay(1000),
        bufferCount(100, 50),
        finalize(() => dot.remove())
    );
}

Now, in our browser’s Dev Tools, we can inspect the creation of the trail by looking at the DOM:

Figure 2.3: Swipe trail

Figure 2.3: Swipe trail

Step 5 – Checking the result

Finally, at the end of the stream in the showMessage method, we must check whether the patternAttempt array, which was filled with each selected number pad, matches our pattern for unlocking the phone, which is 1 2 5 8 7.

Pattern matching

Since this is pattern matching and not exact password matching, the phone can be unlocked by inputting those buttons in any order, so long as those numbers in the pattern are included.

See also

  • The fromEvent function: https://rxjs.dev/api/index/function/fromEvent
  • The switchMap operator: https://rxjs.dev/api/operators/switchMap
  • The takeUntil operator: https://rxjs.dev/api/operators/takeUntil
  • The finalize operator: https://rxjs.dev/api/operators/finalize
  • The mergeMap operator: https://rxjs.dev/api/operators/mergeMap
  • The bufferCount operator: https://rxjs.dev/api/operators/bufferCount

Learning indications with the progress bar

Providing feedback to the user while performing actions when using web applications is one of the key aspects of a good user experience. A component like this helps users understand how long they need to wait and reduces uncertainty if the system is working. Progress bars can be also useful for gamification purposes, to make the overall UX more engaging and motivating.

How to do it…

In this recipe, we’ll simulate upload progress to the backend API by implementing a progress indicator that produces a random progress percentage until we get a response. If we still haven’t received a response after we get to the very end of the progress bar, we’ll set its progress to 95% and wait for the request to be completed.

Step 1 – Creating a progress loading stream

Inside our recipes.service.ts service, we’ll start a stream of random numbers at a given interval. This will be stopped after we get a response from the backend:

private complete$ = new Subject<void>();
private randomProgress$ = interval(800).pipe(
    map(() => Number((Math.random() * 25 + 5))), 
    scan((acc, curr) =>
        +Math.min(acc + curr, 95).toFixed(2), 0),
    takeUntil(this.complete$)
);

With the help of the scan operator, we can decide whether we should produce the next increment of a progress percentage or whether we shouldn’t go over 95%.

Step 2 – Merging progress and request streams

Now, we can combine the randomProgress$ stream with the HTTP request and notify the progress indicator component whenever we get either random progress or complete the request:

postRecipe(recipe: Recipe): Observable<number> {
    return merge(
        this.randomProgress$,
        this.httpClient.post<Recipe>(
            '/api/recipes',
            recipe
        ).pipe(
            map(() => 100),
            catchError(() => of(-1)),
            finalize(() => this.unsubscribe$.next())
        )
    )
}

Once we call the postRecipe service method inside a component, we can track the request progress:

Figure 2.4: Progress indicator

Figure 2.4: Progress indicator

See also

Streaming image loading seamlessly with Progressive Image

In the modern web, we must handle resources that are MBs in size. One such resource is images. Large images can harm performance since they have slower load times, something that could lead to a negative user experience and frustration. To address these issues, one of the common patterns to use is the LowQualityImagePlaceholder pattern, also known as Progressive Image, where we load an image in stages. First, we show the lightweight version of an image (placeholder image). Then, in the background, we load the original image.

How to do it…

In this recipe, we’ll learn how to handle the Progressive Image pattern with ease with the help of RxJS magic.

Step 1 – Defining image sources

Inside our pro-img.component.ts file, we must define paths to our local image and a placeholder/blurry version of the same image from our assets folder:

src = 'image.jpg';
placeholderSrc = 'blurry-image.jpeg';
const img = new Image();
img.src = this.src;
const placeholderImg = new Image();
placeholderImg.src = this.placeholderSrc;

Step 2 – Creating a progress stream

While the image is loading, every 100 milliseconds, we’ll increase the progress percentage, until the load event is triggered. This indicates that the image has been fully loaded. If an error occurs, we’ll say that the progress is at –1:

const loadProgress$ = timer(0, 100);
const loadComplete$ = fromEvent(img, 'load')
    .pipe(map(() => 100));
const loadError$ = fromEvent(img, 'error')
    .pipe(map(() => -1));

Now, we can merge these load events and stream them into the Progressive Image load:

loadingProgress$ = new BehaviorSubject<number>(0);
this.imageSrc$ = merge(loadProgress$, loadComplete$,loadError$).pipe(
    tap((progress) => this.loadingProgress$.next(progress)),
    map((progress) => (progress === 100 ?img.src :placeholderImg.src)),
    startWith(placeholderImg.src),
    takeWhile((src) => src === placeholderImg.src, true),
    catchError(() => of(placeholderImg.src)),
    shareReplay({ bufferSize: 1, refCount: true })
);

We’ll use startWith on the placeholder image and show it immediately in the UI while continuously tracking the progress of the original image load. Once we get 100%, we’ll replace the placeholder image source with the original image.

Step 3 – Subscribing to the image stream in the template

Meanwhile, in the component template, pro-img.component.html, we can subscribe to the progress that’s been made while the image is loading in the background:

<div class="pro-img-container">
    @if ((loadingProgress$ | async) !== 100) {
        <div class="progress">
        {{ loadingProgress$ | async }}%
        </div>
    }
    <img
    [src]="imageSrc$ | async"
    alt="Progressive image"
    class="pro-img"
    >
</div>

Finally, if we open our browser, we may see this behavior in action:

Figure 2.5: Progressive Image
Figure 2.5: Progressive Image

Figure 2.5: Progressive Image

Common gotcha

In this recipe, for simplicity, we’ve chosen to artificially increase the download progress of an image. The obvious drawback is that we don’t get the actual progress of the image download. There’s a way to achieve this effect: by converting the request of an image’s responseType into a blob. More details can be found here: https://stackoverflow.com/questions/14218607/javascript-loading-progress-of-an-image.

See also

Optimizing loading tab content

When tabs contain complex data or media-rich content, it’s beneficial to load the content of tabs lazily. By doing so, we aim to minimize initial page load times, conserve bandwidth, and ensure a smooth and responsive interface. So, let’s create a component like that.

How to do it…

In this recipe, we’ll have a simple tab group of two tabs. Only when a tab is selected will we lazy-load the component representing the contents of that tab. Each tab is represented in the URL, so whenever we change tabs, we’re navigating to a separate page.

Step 1 – Defining a tab group and an active tab

In our tabs.component.html file, we’ll use the Angular Material tab to represent a tab group in the UI:

<mat-tab-group
    [selectedIndex]="(activeTab$ | async)?.index"
    (selectedTabChange)="selectTab($event)"
>
    <ng-container *ngFor="let tab of tabs">
        <mat-tab [label]="tab.label"></mat-tab>
    </ng-container>
</mat-tab-group>

Now, inside tabs.component.ts, we need to define the activeTab and loading states, as well as the content of a tab stream that we can subscribe to:

activeTab$ = new BehaviorSubject<TabConfig | null>(null);
activeTabContent$!: Observable<
    typeof TabContentComponent |
    typeof TabContent2Component |
    null
>;
loadingTab$ = new BehaviorSubject<boolean>(false);

Now, we can hook into Angular Router events, filter events when navigation ends, and, based on an active URL, mark the corresponding tab as active:

this.router.events.pipe(
    filter((event) => event instanceof NavigationEnd),
    takeUntil(this.destroy$)
).subscribe({
    next: () => {
        const activeTab = this.tabs.find(
            (tab) => tab.route === this.router.url.slice(1)
        );
    this.activeTab$.next(activeTab || null);
    },
});

Step 2 – Loading tab content

Since we know which tab is active, we can start loading the content of that tab:

private loadTabContent(tab: TabConfig) {
    const content$ = tab.route === 'tab1'
    ? of(TabContentComponent)
    : of(TabContent2Component);
    return content$.pipe(delay(1000));
}
this.activeTabContent$ = this.activeTab$.pipe(
    tap(() => this.loadingTab$.next(true)),
    switchMap((tab) =>
        this.loadTabContent(tab!).pipe(
            startWith(null),
            catchError((error) => {
                this.errors$.next(error);
            return of(null);
            }),
        finalize(() => this.loadingTab$.next(false))
        )
    ),
    shareReplay({ bufferSize: 1, refCount: true })
);

Inside the loadTabContent method, we’ll create an Observable out of the Angular component that’s matched based on the current route. Once we’ve done this, we’re ready to stream into the tab content whenever the active tab changes. We can do this by starting the loading state, switching to the stream that’s loading content, and resetting the loading state once the content has arrived.

Now, all we need to do is represent the content in the UI. Back in our tabs.component.html file, we can simply add the following code:

@if (loadingTab$ | async) {
    <p>Loading...</p>
}
<ng-container
    *ngComponentOutlet="activeTabContent$ | async"
></ng-container>

Now, by going to our browser, we’ll see that the content of a tab will only be loaded when we click on that specific tab:

Figure 2.6: Loading tabs
Figure 2.6: Loading tabs

Figure 2.6: Loading tabs

See also

  • The of function: https://rxjs.dev/api/index/function/of
  • The startWith operator: https://rxjs.dev/api/operators/startWith
  • Angular’s Router’ NavigationEnd event: https://angular.dev/api/router/NavigationEnd
  • The Angular Material tab component: https://material.angular.io/components/tabs/overview

Reacting to drag-and-drop events

Creating a drag-and-drop component for file uploads is quite a common task for a web developer. If you’ve ever worked on such a component, you may already know that it isn’t a trivial task and that there’s a lot of hidden complexity behind a component like this. Luckily for us, we have RxJS to help us streamline the experience of reacting to drag-and-drop events in a reactive and declarative way.

Getting ready

In this recipe, to provide support for tracking image upload progress, we need to run a small Node.js server application located in the server folder. We can run this server application by using the following command:

node index.js

After that, we’re ready to go to the client folder and dive into the reactive drag-and-drop component.

How to do it…

In this recipe, we’ll define a drag-and-drop area for .png images. Then, we’ll add support for multiple uploads to be made at the same time, show the upload progress of each image, and display error messages if the format of the image isn’t correct. We’ll also implement a retry mechanism in case a file upload fails over the network.

Step 1 – Defining a dropzone

In our dnd-file-upload.component.html file, we must place markup for the dropzone area:

<div #dropzoneElement class="drop-zone-element">
    <p>Drag and drop png image into the area below</p>
</div>

After getting the dropzoneElement reference with @ViewChild(), we can start reacting to the drag-and-drop events in the dropzone area:

@ViewChild('dropzoneElement') dropzoneElement!: ElementRef;
ngAfterViewInit(): void {
    const dropzone = this.dropzoneElement.nativeElement;
    const dragenter$ = fromEvent<DragEvent>(
        dropzone,
        'dragenter'
    );
    const dragover$ = fromEvent<DragEvent>(
        dropzone,
        'dragover'
    ).pipe(
        tap((event: DragEvent) => {
            event.preventDefault();
            event.dataTransfer!.dropEffect = 'copy';
            (event.target as Element).classList.add('dragover');
        })
    );
    const dragleave$ = fromEvent<DragEvent>(
        dropzone,
        'dragleave'
    ).pipe(
        tap((event: DragEvent) => {
            (event.target as Element).classList.remove('dragover');
        })
    );
    const drop$ = fromEvent<DragEvent>(
        dropzone,
        'drop'
    ).pipe(
        tap((event: DragEvent) => {
            (event.target as Element).classList.remove('dragover');
        })
    );
    const droppable$ = merge(
        dragenter$.pipe(map(() => true)),
        dragover$.pipe(map(() => true)),
        dragleave$.pipe(map(() => false))
    );
}

While creating these events, we can track when the file(s) have entered the dropzone and when they’re leaving. Based on this, we can style the component by adding the corresponding classes. We’ve also defined all droppable even so that we know when to stop reacting to the stream of new images that’s being dragged over.

Step 2 – Validating files

Now, we can hook into a stream of drop events and validate the format of each image; if the format is OK, we can start uploading each image to the backend API:

drop$.pipe(
    tap((event) => event.preventDefault()),
    switchMap((event: DragEvent) => {
        const files$ = from(Array.from(
            event.dataTransfer!.files));
        return this.fileUploadService.validateFiles$(
            files$);
    }),
  ...the rest of the stream

Back in our FileUploadService service, we have a validation method that checks whether we’ve uploaded a .png image:

validateFiles$(files: Observable<File>): Observable<{
    valid: boolean,
    file: FileWithProgress
}> {
    return files.pipe(
        map((file File) => {
            const newFile: FileWithProgress = new File(
                [file],
                file.name,
                { type: file.type }
            );
            if (file.type === 'image/png') {
                newFile.progress = 0;
            } else {
                newFile.error = 'Invalid file type';
            }
        return newFile;
        }),
        map((file: FileWithProgress) => {
            return of({
                valid: !file.error,
                file
            });
        }),
        mergeAll()
    );
}

Here, we check the file type. If it’s expected, we set the progress to 0 and start the upload. Otherwise, we set the error message for that specific file upload.

Step 3 – Uploading files and tracking progress

Once we’ve validated each file, we can start upload them to the backend:

drop$.pipe(
    // validation steps from Step 1
    map((file: FileWithProgress) =>
        this.fileUploadService.handleFileValidation(file)
    ),
    mergeAll(),
    takeUntil(droppable$
        .pipe(filter((isDroppable) => !isDroppable))
    ),
    repeat()
)
handleFileValidation$(file: FileWithProgress): 
    Observable<FileWithProgress | never> {
        if (!file.valid) {
            this._snackBar.open(
                `Invalid file ${file.name} upload.`,
                'Close',
                { duration: 4000 }
            );
        return EMPTY;
    }
    return this.fileUploadService
        .uploadFileWithProgress$(file);
}

If the file is invalid, we’ll immediately return that file and show the error in the UI:

Figure 2.7: Invalid file format upload

Figure 2.7: Invalid file format upload

If it’s a valid file upload, then we initiate an upload request to our API. In Angular, if we want to track the actual progress of a request, there are a few things we must do:

  1. We need to send the request payload as FormData.
  2. We need to set responseType to 'blob'.
  3. We need to set the reportProgress flag to true.

After applying all these steps, our uploadFiles$ method should look like this:

uploadFile$(file: File): Observable<number> {
    const formData = new FormData();
    formData.append('upload', file);
    const req = new HttpRequest(
        'POST', '/api/recipes/upload', formData, {
            reportProgress: true,
            responseType: 'blob'
        }
    );
    return this.httpClient.request(req).pipe(
        map((event: HttpEvent<Blob>) =>
            this.getFileUploadProgress(event)),
        filter(progress => progress < 100),
    );
}

Now, when we send this request, we’ll get a series of HTTP events that we can react to. If we check the getFileUploadProgress method, we’ll see this in action:

getFileUploadProgress(event: HttpEvent<Blob>): number {
    const { type } = event;
    if (type === HttpEventType.Sent) {
        return 0;
    }
    if (type === HttpEventType.UploadProgress) {
        const percentDone = Math.round(
            100 * event.loaded / event.total!);
        return percentDone;
    }
    if (type === HttpEventType.Response) {
        return 100;
    }
    return 0;
}

With this approach, we know the exact progress of the file upload due to the UploadProgress event.

Finally, we can call the uploadFileWithProgress$ method from our service and return each file with progress information attached to each corresponding file:

uploadFileWithProgress$(file: FileWithProgress):    Observable<FileWithProgress> {
    return this.uploadFile$(file).pipe(
        map((progress: number) =>
            this.createFileWithProgress(file, progress)),
        endWith(this.createFileWithProgress(file, 100))
    );
}

After emitting a progress value, we’ll return the file with information attached about its progress so that we can display it in the UI.

Step 4 – Showing file uploads in the UI

Finally, once we subscribe to this whole stream of file upload events inside of our component, we can show the list of all the files that are being uploaded with corresponding progress bars. This also allows us to show an error message if an error has occurred:

drop$.pipe(
    // validation steps from Step 1
    // file upload steps from Step 2
).subscribe({
    next: (file) => {
        if (file.valid) {
            this.validFiles.set(file.name, file);
            return;
        }
        if (!file.valid) {
            this._snackBar.open(
                'Invalid file upload.',
                'Close',
            {}
            );
        }
    }
});

Once we open our browser and drag multiple valid .png images, we can handle those uploads concurrently and observe their progress:

Figure 2.8: A reactive drag-and-drop file upload

Figure 2.8: A reactive drag-and-drop file upload

Step 5 – Handling file upload errors

Imagine that, in the middle of our image upload, the network fails. One of the key aspects of a component like this is that it must be resilient to these kinds of errors and provide a recovery or retry mechanism. We can do this by catching that network error in the file upload stream and showing a retry button in the UI next to the failed upload. We can extend our service method by adding an error catch mechanism:

uploadFileWithProgress$(file: FileWithProgress): Observable<FileWithProgress> {
    return this.uploadFile$(file).pipe(
        map((progress: number) =>
            this.createFileWithProgress(file, progress)),
        endWith(this.createFileWithProgress(file, 100)),
        catchError(() => {
            const newFile: FileWithProgress =
                this.createFileWithProgress(
                    file,
                    -1,
                    'Upload failed'
                );
            return of(newFile);
        })
    );
}

Back in our component template, dnd-file-upload.component.html, we can add a retry button if the file’s upload progress is at –1, meaning that it failed previously:

@if (file.value.progress !== -1) {
    {{ file.value.progress }}%
} @else {
    <button
        mat-icon-button
        (click)="retryUpload(file.value)"
        >
        <mat-icon aria-hidden="false" fontIcon="redo">
        </mat-icon>
    </button>
}
retryUpload(file: FileWithProgress): void {
    this.recipeService.uploadFileWithProgress$(
        file).subscribe({ next: (file: FileWithProgress) =>
            this.validFiles.set(file.name, file),
            error: (err) => console.error(err),
        });
}

If we open our browser, if an upload error has occurred, we may notice the retry button in the UI. If the network recovers, we can trigger another upload request for the failed uploads:

Figure 2.9: Retry on file upload

Figure 2.9: Retry on file upload

See also

  • The HTML input file: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file
  • The interval function: https://rxjs.dev/api/index/function/interval
  • The repeat operator: https://rxjs.dev/api/operators/repeat
  • The scan operator: https://rxjs.dev/api/operators/scan
  • The finalize operator: https://rxjs.dev/api/operators/finalize
  • The merge operator: https://rxjs.dev/api/operators/merge
  • The mergeAll operator: https://rxjs.dev/api/operators/mergeAll
  • The endWith operator: https://rxjs.dev/api/operators/endWith

Crafting your perfect audio player using flexible RxJS controls

Everybody likes music. Whether you use Spotify, Deezer, YouTube, or something else to listen to your favorite jams, having control over your playlist with a sophisticated audio player is one of the essential conditions for providing an awesome user experience. In this recipe, we’ll create a lightweight RxJS audio player with reactive controls for playing and pausing songs, controlling volume, as well as skipping to the next song in the playlist.

How to do it…

The essential thing to understand in this recipe is the native HTMLAudioElement and, based on that, which events are the most important to react to.

Step 1 – Creating audio player events

In our audio-player.component.html file, we must implement markup for the audio player:

<audio #audio></audio>

Concerning that audio HTML element, in the component audio-player.component.ts file, we’ll define all the key events for that element:

@ViewChild('audio') audioElement!:
    ElementRef<HTMLAudioElement>;
ngAfterViewInit(): void {
    const audio = this.audioElement.nativeElement;
    const duration$ = fromEvent(audio,  
        'loadedmetadata').pipe(map(() => (
            { duration: audio.duration }))
    );
    const playPauseClick$ = fromEvent(audio, 'play').pipe(
        map(() => ({ isPlaying: true }))
    );
    const pauseClick$ = fromEvent(audio, 'pause').pipe(
        map(() => ({ isPlaying: false }))
    );
    const volumeChange$ = fromEvent(audio,
        'volumechange').pipe(
             map(() => ({ volume: audio.volume })),
    );
    const time$ = fromEvent(audio, 'timeupdate').pipe(
        map(() => ({ time: audio.currentTime }))
    );
    const error$ = fromEvent(audio, 'error');
}

Using the audio element, we can react to play, pause, volumechange, and timeupdate events, as well as metadata that holds information about the duration value of a song. Also, in case network interruptions occur when we fetch the audio file or corrupted audio files, we can subscribe to the error event from the audio element.

Now, we can combine all those events and hold the state of a song in a centralized place:

merge(
    duration$,
    playPauseClick$,
    pauseClick$,
    volumeChange$
).subscribe((state) =>
    this.audioService.updateState(state));

Step 2 – Managing song state

In our audio.service.ts file, we’ll store the state of the current song:

public audioState$ = new BehaviorSubject<AudioState>({
    isPlaying: false,
    volume: 0.5,
    currentTrackIndex: 0,
    duration: 0,
    tracks: []
});
updateState(state: Partial<AudioState>): void {
    this.audioState$.next({
     ...this.audioState$.value,
     ...state
    });
}

Now, we can subscribe to all state changes in the component and have reactive audio player controls over user actions.

Step 3 – Playing/pausing a song

Back in our audio-player.component.ts file, whenever play or pause events are being emitted, the state will update, at which point we can subscribe to the state change:

this.audioService.audioState$.subscribe(({ isPlaying }) =>
    this.isPlaying = isPlaying;
);

Now, in the audio-player.component.html file, we can present either a play or pause icon based on the following condition:

<button mat-fab class="play-pause-btn" (click)="playPause()">
    @if (isPlaying) {
        <mat-icon>pause</mat-icon>
    } @else {
        <mat-icon>play_arrow</mat-icon>
    }
</button>

We can also control the audio when playing a song:

playPause(): void {
    if (!this.isPlaying) {
        this.audioElement.nativeElement.play();
    } else {
        this.audioElement.nativeElement.pause();
    }
}

Step 4 – Controlling the song’s volume

By subscribing to the audio player state, we also have information about the volume based on the previously emitted volumechange event:

this.audioService.audioState$.subscribe(({ volume }) => {
    this.volume = volume;
});

We can represent this state in the UI like so:

<div class="volume">
    @if (volume === 0) {
        <mat-icon>volume_off</mat-icon>
    } @else {
        <mat-icon>volume_up</mat-icon>
    }
    <input
        type="range"
        [value]="volume"
        min="0"
        max="1"
        step="0.01"   
        (input)="changeVolume($event)"
    />
</div>

Now, we can emit the same event by changing the volume of the audio player by invoking the changeVolume() method:

changeVolume({ target: { value } }): void {
    this.audioElement.nativeElement.volume = value;
}

This will automatically update the volume state reactively on the audio player element.

Step 5 – Switching songs

Back in our audio.service.ts file, we’ve implemented methods for changing the current song index in the list of tracks:

previousSong(): void {
    let prevIndex =
        this.audioState$.value.currentTrackIndex - 1;
    const tracks = this.audioState$.value.tracks;
    if (prevIndex < 0) {
        prevIndex = tracks.length - 1; // Loop back to the
                                       // end
    }
    this.updateState({
        isPlaying: false,
        currentTrackIndex: prevIndex
    });
}
nextSong(): void {
    let nextIndex =
        this.audioState$.value.currentTrackIndex + 1;
    const tracks = this.audioState$.value.tracks;
    if (nextIndex >= tracks.length) {
        nextIndex = 0; // Loop back to the beginning
    }
    this.updateState({
        isPlaying: false,
        currentTrackIndex: nextIndex
    });
}

Also, when we come to the end of the list, we’ll loop to the beginning of the playlist.

Inside the audio-player.component.ts component, we can subscribe to this state change and change the song using the audio element:

this.audioService.audioState$.subscribe(({
    currentTrackIndex,
    tracks
}) => {
    if (
        tracks[currentTrackIndex].title !==
            this.currentTrack.title
    ) {
        this.audioElement.nativeElement.src =
            tracks[currentTrackIndex].song;
        this.currentTrack = tracks[currentTrackIndex];
    }
});

This means that we have all the information we need about the current song, which means we can display that data in our audio-player.component.html template.

Step 6 – Skipping to the middle of a song

In our audio element, there’s a timeupdate event that lets us track and update the current time of a song:

const time$ = fromEvent(audio, 'timeupdate').pipe(
    map(() => ({ time: audio.currentTime }))
);
time$.subscribe(({ time }) => this.currentTime = time);

In the UI, we can combine this current time information with the previous song metadata, show it in a slider, and watch the song progress:

<p>{{ currentTime | time }}</p>
<audio #audio></audio>
<mat-slider [max]="duration" class="song">
    <input matSliderThumb
    [value]="currentTime"
    (dragEnd)="skip($event)"
    >
</mat-slider>
<p>{{ duration | time }}</p>

Finally, if we open our browser, we can inspect all these features and play our favorite jam:

Figure 2.10: Reactive audio player

Figure 2.10: Reactive audio player

See also

Streamlining real-time updates with RxJS-powered notifications

Notifications are one of the main ways we can prompt users about relevant events or changes within the system. By utilizing Observables and operators, RxJS provides a powerful framework for managing these asynchronous notifications efficiently and effectively.

How to do it…

In this recipe, we’ll have an array of notifications to represent incoming notifications based on a user action, store them by ID, and remove them after a certain period. We’ll also provide support to manually remove notifications from a stack.

Step 1 – Stacking incoming notifications

To streamline the stack of notifications efficiently, inside NotificationService, we’ll use BehaviorSubject to represent all the notifications that may arrive over time asynchronously. We’ll also have a Subject that triggers an event when we want to add a new notification to the stack and another for dismissal:

private notifications$ = new BehaviorSubject<Notification[]>([]);
private addNotification$ = new Subject<Notification>();
private removeNotification$ = new Subject<string>();
addNotification(notification: Notification) {
    this.addNotification$.next(notification);
}
removeNotification(id: string) {
    this.removeNotification$.next(id);
}

So, whenever there’s an ongoing request for posting new data, we’ll combine these two actions with the latest state of the notification stack with the help of the withLatestFrom operator and update its state:

get notifications(): Observable<Notification[]> {
    return merge(
        this.addNotification$,
        this.removeNotification$
    ).pipe(
    withLatestFrom(this.notifications$),
    map(([changedNotification, notifications]) => {
        if (changedNotification instanceof Object) {
            this.notifications$.next([
                ...notifications,
                changedNotification
            ]);
          } else {
              this.notifications$.next(notifications.filter   
                  (notification =>
                       notification.id !== changedNotification)
              );
          }
          return this.notifications$.value;
        })
    )
}

Based on the latest emitted value’s type, we can decide whether a new notification needs to be added or filtered from the stack.

Step 2 – Reacting to a user action and displaying notifications

In our app.component.html file, we have a simple button that will trigger a POST request to add a new random cooking recipe:

<button (click)="sendRequest()">Add recipe</button>

Clicking that button will invoke a function:

sendRequest() {
    this.recipeService.postRecipes();
}

In RecipeService, we must implement the service method for sending the request to the BE API. If we get a successful response, we’ll perform a side effect to add a notification to the stack. If we get an error, we’ll display a notification that’s of the error type:

getRecipes(): void {
    this.httpClient.get<Recipe[]>('/api/recipes').pipe(
        tap(() => {
            this.notificationService.addNotification({
                id: crypto.randomUUID(),
                message: 'Recipe added successfully.',
                type: 'success'
        });
    }),
    catchError((error) => {
        this.notificationService.addNotification({
            id: crypto.randomUUID(),
            message: 'Recipe could not be added.',
            type: 'error'
        });
        return throwError(() =>
            new Error('Recipe could not be added.'));
       }),
    ).subscribe();
}

Finally, in NotificationComponent, we can subscribe to the changes on notifications$ and display notifications:

<div class="container">
    <div
      *ngFor="let notification of notifications | async"  
      class="notification {{ notification.type }}"
    >
      {{ notification.message }}
        <mat-icon
            (click)="removeNotification(notification.id)" 
            class="close">
            Close
        </mat-icon>
    </div>
</div>

Now, when we open our browser, we’ll see incoming notifications stacked on each other:

Figure 2.11: A reactive stack of notifications

Figure 2.11: A reactive stack of notifications

Step 3 – Automatic notification dismissal

Previously, we could manually remove notifications from the stack by clicking the close button. Now, after a certain period, we want a notification to be automatically removed from the stack. Back in NotificationService, when adding a notification to the stack initially, we’ll simply define a timer, after which we’ll call the removeNotification method:

addNotification(
    notification: Notification,
    timeout = 5000
) {
    this.addNotification$.next(notification);
    timer(timeout).subscribe(() =>
        this.removeNotification(notification.id));
}

See also

Fetching data with the Infinite Scroll Timeline component

Imagine going through your favorite cooking web application and getting the latest updates on delicious new recipes. To show this latest news, one of the common UX patterns is to show this recipe news in a timeline component (such as Facebook’s news feed). While you scroll, if there are new recipes, you’ll be updated that there are fresh new recipes so that you can scroll back to the top and start over.

How to do it…

In this recipe, we’re going to build a timeline component that shows the list of your favorite latest cooking recipes. Since there are a lot of delicious recipes out there, this would be a huge list to fetch initially. To increase the performance of the application and to improve the general UX, we can implement an infinite scroll list so that once the user scrolls to the end of a list of 5 initial recipes, we can get a set of 5 new recipes. After some time, we can send a new request to check whether there are new recipes and refresh our timeline of recipe news.

Step 1 – Detecting the end of a list

In our RecipesList component, we’ll create a stream of scroll events. On each emission, we’ll check whether we’re near the end of the list in the UI based on a certain threshold:

private isNearBottom(): boolean {
    const threshold = 100; // Pixels from bottom
    const position = window.innerHeight + window.scrollY;
    const height = document.documentElement.scrollHeight;
    return position > height - threshold;
}
const isNearBottom$ = fromEvent(window, 'scroll').pipe(
    startWith(null),
    auditTime(10), // Prevent excessive event triggering
    observeOn(animationFrameScheduler),
    map(() => this.isNearBottom()),
    distinctUntilChanged(), // Emit only when near-bottom 
                             //state changes
)

As you can imagine, with the scroll event emissions, there’s the potential for performance bottlenecks. We can limit the number of scroll events that are processed by the stream using the auditTime operator. This is especially useful since we want to ensure that we are always processing the latest scroll event, and auditTime will always emit the most recent value within the specified time frame. Also, with observeOn(animationFrameScheduler), we can schedule tasks to be executed just before the browser’s next repaint. This can be beneficial for animations or any updates that cause a repaint as it can help to prevent jank and make the application feel smoother.

auditTime versus throttleTime

You might be wondering why we used auditTime in our scroll stream and not throttleTime. The key difference between these two operators is that auditTime emits the last value in a time window, whereas throttleTime emits the first value in a time window. Common use cases for throttleTime might include rate-limiting API calls, handling button clicks to prevent accidental double clicks, and controlling the frequency of animations.

Once we know we’re getting near the end of a list, we can trigger a loading state and the next request with a new set of data.

Step 2 – Controlling the next page and loading the state of the list

At the top of our RecipesList component, we’ll define the necessary states to control the whole flow and know when we require the next page, when to show the loader, and when we’ve reached the end of the list:

private page = 0;
public loading$ = new BehaviorSubject<boolean>(false);
public noMoreData$ = new Subject<void>();
private destroy$ = new Subject<void>();

Now, we can continue our isNearBottom$ stream, react to the next page, and specify when to show the loader:

isNearBottom$.pipe(  
    filter((isNearBottom) =>
        isNearBottom && !this.loading$.value),
    tap(() => this.loading$.next(true)),
    switchMap(() =>
        this.recipesService.getRecipes(++this.page)
        .pipe(
            tap((recipes) => {
                if (recipes.length === 0)
                this.noMoreData$.next();
            }),
            finalize(() => this.loading$.next(false))
        )
    ),
    takeUntil(merge(this.destroy$, this.noMoreData$))
    )
    .subscribe((recipes) => (
        this.recipes = [...this.recipes, ...recipes])
    );
}

Here’s a breakdown of what we’ve done:

  1. First, we check whether we’re near the bottom of the page or whether there’s already an ongoing request.
  2. We start a new request by showing a loading spinner.
  3. We send a new request with the next page as a parameter.
  4. When we get a successful response, we can check whether there’s no more data or we can continue scrolling down the timeline.
  5. Once the stream has finished, we remove the loading spinner:
Figure 2.12: Reactive infinite scroll

Figure 2.12: Reactive infinite scroll

Step 3 – Checking for new recipes

In our recipes.service.ts file, we’ve implemented a method that will check whether there are new recipes periodically and whether we should scroll to the top of the timeline and refresh it with new data:

checkNumberOfNewRecipes(): Observable<number> {
    return interval(10000).pipe(
        switchMap(() =>
            this.httpClient.get<number>(
                '/api/new-recipes'))
    );
}

Once we receive several new recipes, we can subscribe to that information inside NewRecipesComponent and display it in the UI:

Figure 2.13: Reactive timeline updates

Figure 2.13: Reactive timeline updates

Now, once we click the 2 new recipes button, we can scroll to the top of the timeline and get the newest data.

See also

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Master RxJS observables, operators, and subjects to improve your reactive programming skills
  • Explore advanced concepts like error handling, state management, PWA, real-time, and event-driven systems
  • Enhance reactive skills with modern web programming techniques, best practices, and real-world examples
  • Purchase of the print or Kindle book includes a free PDF eBook

Description

Building modern web applications that are responsive and resilient is essential in this rapidly evolving digital world. Imagine effortlessly managing complex data streams and creating seamless user experiences—this book helps you do just that by adopting RxJS to boost your development skills and transform your approach to reactive programming. Written by a seasoned software engineer and consultant with a decade of industry experience, this book equips you to harness the power of RxJS techniques, patterns, and operators tailored for real-world scenarios. Each chapter is filled with practical recipes designed to tackle a variety of challenges, from managing side effects and ensuring error resiliency in client applications to developing real-time chat applications and event-driven microservices. You’ll learn how to integrate RxJS with popular frameworks, such as Angular and NestJS, gaining insights into modern web development practices that enhance performance and interactivity. By the end of this book, you’ll have mastered reactive programming principles, the RxJS library, and working with Observables, while crafting code that reacts to changes in data and events in a declarative and asynchronous way.

Who is this book for?

This book is ideal for intermediate-to-advanced JavaScript developers who want to adopt reactive programming principles using RxJS. Whether you’re working with Angular or NestJS, you’ll find recipes and real-world examples that help you leverage RxJS for managing asynchronous operations and reactive data flows across both your frontend and backend.

What you will learn

  • Manage error handling, side effects, and event orchestration in your Angular and NestJS applications
  • Use RxJS to build stunning, interactive user interfaces with Angular
  • Apply Angular testing strategies to ensure the reliability of your RxJS-powered applications
  • Optimize the performance of RxJS streams
  • Enhance progressive web app experiences with RxJS and Angular
  • Apply RxJS principles to build state management in Angular
  • Craft reactive and event-driven microservices in NestJS
Estimated delivery fee Deliver to United States

Economy delivery 10 - 13 business days

Free $6.95

Premium delivery 6 - 9 business days

$21.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Mar 28, 2025
Length: 310 pages
Edition : 1st
Language : English
ISBN-13 : 9781788624053
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to United States

Economy delivery 10 - 13 business days

Free $6.95

Premium delivery 6 - 9 business days

$21.95
(Includes tracking information)

Product Details

Publication date : Mar 28, 2025
Length: 310 pages
Edition : 1st
Language : English
ISBN-13 : 9781788624053
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Table of Contents

12 Chapters
Handling Errors and Side Effects in RxJS Chevron down icon Chevron up icon
Building User Interfaces with RxJS Chevron down icon Chevron up icon
Understanding Reactive Animation Systems with RxJS Chevron down icon Chevron up icon
Testing RxJS Applications Chevron down icon Chevron up icon
Performance Optimizations with RxJS Chevron down icon Chevron up icon
Building Reactive State Management Systems with RxJS Chevron down icon Chevron up icon
Building Progressive Web Apps with RxJS Chevron down icon Chevron up icon
Building Offline-First Applications with RxJS Chevron down icon Chevron up icon
Going Real-Time with RxJS Chevron down icon Chevron up icon
Building Reactive NestJS Microservices with RxJS Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the digital copy I get with my Print order? Chevron down icon Chevron up icon

When you buy any Print edition of our Books, you can redeem (for free) the eBook edition of the Print Book you’ve purchased. This gives you instant access to your book when you make an order via PDF, EPUB or our online Reader experience.

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela