Bringing in manageable RxJS operators

RxJS 5.5 has undergone a significant change and has introduced lettable operators to replace the traditional operators we were using previously.

In the article, there is a note that states:

Lettable operators can now be imported from rxjs/operators, but doing so without changing your build process can result in a larger application bundle. This is because rxjs/operators will resolve to the CommonJS output of rxjs by default.

This statement can be validated easily with a new AngularCLI-generated app.

For instance, when we have an application that doesn't import anything from RxJS:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users')
      .subscribe(response => {
        console.log(response);
      });
  }
}

Upon building the application using ng build --prod, the bundle sizes remain the same.

However, when we import a traditional RxJS operator and use it:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import "rxjs/add/operator/map";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users')
      .map((u: any) => 1)
      .subscribe(response => {
        console.log(response);
      });
  }
}

There is still no difference in the bundle sizes.

On the other hand, when we try to import and use the lettable operator as recommended without modifying the build process:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { map } from "rxjs/operators";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  public title = 'app';

  constructor(private readonly http: HttpClient) {
  }

  public ngOnInit(): void {
    this.http.get('https://api.github.com/users').pipe(
        map((u: any) => 1))
      .subscribe(response => {
        console.log(response);
      });
  }
}

In this case, the vendor bundle size increases, indicating that RxJS hasn't been tree-shaked:

chunk {0} polyfills.e1f97a0070e18e96a6be.bundle.js (polyfills) 61.4 kB {4} [initial] [rendered]
chunk {1} main.450c741a106157402dcd.bundle.js (main) 4.97 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
chunk {3} vendor.3f53f0e2283f4c44ec38.bundle.js (vendor) 344 kB [initial] [rendered]
chunk {4} inline.2d973ef5a10aa806b082.bundle.js (inline) 1.45 kB [entry] [rendered]

Attempting to import the lettable operator as recommended in the article results in a build error:

./src/app/app.component.ts
Module not found: Error: Can't resolve 'rxjs/operators/map' in 'c:\Projects\Angular\src\app'
 @ ./src/app/app.component.ts 14:0-41
 @ ./src/app/app.module.ts
 @ ./src/main.ts
 @ multi webpack-dev-server/client?http://localhost:4200 ./src/main.ts
  1. What could be the issue here?
  2. How can we import the new RxJS lettable operators in an Angular CLI app while ensuring that RxJS is still tree-shaked?

UPDATE: Package versions are as follows:

rxjs: 5.5.0
@angular/cli: 1.4.9
node: 8.6.0
os: win32 x64
@angular/animations: 4.4.6
@angular/common: 4.4.6
@angular/compiler: 4.4.6
@angular/core: 4.4.6
@angular/forms: 4.4.6
@angular/http: 4.4.6
@angular/platform-browser: 4.4.6
@angular/platform-browser-dynamic: 4.4.6
@angular/router: 4.4.6
@angular/cli: 1.4.9
@angular/compiler-cli: 4.4.6
@angular/language-service: 4.4.6
typescript: 2.3.4

Answer №1

To ensure completeness, when using @angular/[email protected], the following code snippet is added in the file models/webpack-configs/common.js

// To decide whether to prioritize ES2015 modules based on the tsconfig.
// Incorporate rxjs path aliases.
// Reference: https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#build-and-treeshaking
let alias = {};
try {
    const rxjsPathMappingImport = 'rxjs/_esm5/path-mapping';
    const rxPaths = require_project_module_1.requireProjectModule(projectRoot, rxjsPathMappingImport);
    alias = rxPaths(nodeModules);
}
catch (e) { }

    resolve: {
        extensions: ['.ts', '.js'],
        modules: ['node_modules', nodeModules],
        symlinks: !buildOptions.preserveSymlinks,
        alias
    },

Additionally, in rxjs/_esm5/path-mapping.js, these two entries are present

"rxjs/operators": path.resolve(PATH_REPLACEMENT, "rxjs/_esm5/operators/index.js"),
...
"rxjs/operators/map": path.resolve(PATH_REPLACEMENT, "rxjs/_esm5/operators/map.js"),

The crucial part of the error message highlights

aliased with mapping 'rxjs/operators': 
  'C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js' to 
  'C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js/map'
...
C:\Dev\Projects\rx55\node_modules\rxjs\_esm5\operators\index.js\map doesn't exist

This conflict arises because the first mapping overrides the second one.

Reordering the mappings resolves the build issue, indicating a potential flaw in rxjs v5.5.

For now, following Alexander's workaround is recommended until a permanent solution is implemented.

Answer №2

  1. Ensure that you are using at least version 5.5.0. Verify the existence of the file

    node_modules/rxjs/operators/map.js
    , as it should be present. Additionally, when using
    import { map } from "rxjs/operators";
    , the same file is being imported underneath, indicating a potential issue with the build system.

  2. To correctly utilize operators, import them from rxjs/operators/* (e.g.

    import { map } from "rxjs/operators/map";
    ).

    Importing from rxjs/operators is equivalent to importing from rxjs in RxJS versions prior to 5.5.0, as it essentially imports rxjs/operators/index. You can refer to https://github.com/ReactiveX/rxjs/blob/master/src/operators/index.ts for more information.

    This is why "tree-shaking" did not occur, as it imported all operators listed in index.ts.

Answer №3

It was discovered (thanks to @RichardMatsen) that there is a bug in angular-cli 1.4.9.

When attempting deep imports (such as

import { map } from "rxjs/operators/map";
) with angular-cli 1.4.8, no build errors occur and the bundle sizes are as follows:

chunk {0} polyfills.e1f97a0070e18e96a6be.bundle.js (polyfills) 61.4 kB {4} [initial] [rendered]
chunk {1} main.d36cf6834f640163ff35.bundle.js (main) 4.97 kB {3} [initial] [rendered]
chunk {2} styles.d41d8cd98f00b204e980.bundle.css (styles) 0 bytes {4} [initial] [rendered]
chunk {3} vendor.658e9efd9845db281b29.bundle.js (vendor) 241 kB [initial] [rendered]
chunk {4} inline.c9d245ca6c859aaeef69.bundle.js (inline) 1.45 kB [entry] [rendered]

This results in only a 5 kB improvement compared to the version that does not utilize RxJS operators at all.

For now, a workaround is to stick with angular-cli 1.4.8 and import lettable operators using deep imports like the following:

import { map } from "rxjs/operators/map";
import { filter } from "rxjs/operators/filter";

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I'm looking to develop a custom CKEditor plug-in that will allow users to easily drag and drop elements within the editor interface

Upon observing that dragging and dropping text or images within a CKEditor editor instance does not trigger the "change" event, I devised a temporary solution by implementing code triggered by the "dragend" event. However, my ultimate goal is to create a C ...

The element.find() function in Angular struggles to locate input elements nested within other elements

I have been working on a directive that has the following template (simplified): <table> <tr> <td> <input type="text"/> </td> <td> <inpu ...

Refresh the Vuex store using the main process of Electron

Is there a way to update a vuex store by committing changes from the main process? Here is an example: In the main thread: import store from '../store' ipc.on('someevent', (event, args) => { // do something with args store ...

What is the best way to include a character in a string if there are no instances of the same character already present?

Looking for help with a function that can determine if a string contains a period and add one if it doesn't. So far, I'm struggling to make it either continuously add periods or not add any at all. function checkPeriod() { if (inputString.indexO ...

FullPage.js combines traditional scrolling with a unique horizontal slider feature

Visit this link for the example. Hello, I have a question regarding this example: Is there a way to disable the automatic scrolling that occurs when reaching the middle of a page? Also, I am having trouble with the horizontal slider not responding to ...

Guide on how to use Vue's watch feature to monitor a particular property within an array

I am interested in observing the "clientFilter" within an array TableProduit: [ { nr_commande: 0, date_creation: "", id_delegue: "1", clientFilter: "" } ], ...

A guide to transferring modules between component files in JavaScript

My query pertains to the handling of imports in web pages. When a file is imported into another, do the declarations and imports from the imported file become available in the file where it is being imported? A suggestion was made for me to import a compo ...

When the affirmative button is clicked, apply a border and background color

I am in the process of creating custom text boxes for user comments. I need help with adding borders and background colors to a box when the user clicks on the YES button, in order to visually indicate which box the click originated from. Can anyone assis ...

Encountering installation issues with Next.js and experiencing a package failure with react.js during the installation process

Issue: Error encountered during Next.js installation, multiple installation failures reported including missing packages peema@DESKTOP-6UGCO8V MINGW64 ~/Documents/alert/peeapp $ next build The module 'react' was not found. Next.js requires that ...

The React-Chartjs chart is displaying without any color rendering

I attempted to create a radar chart using react-chartjs library, but I am facing an issue where the colors are not appearing when it renders. https://i.stack.imgur.com/tjAsW.png What could be causing this problem? I followed a significant portion of the ...

Double trouble: Knockout validation errors displayed twice

Currently, I am using the knockout validation plugin to validate a basic form field. The validation functionality is working as expected, however, it seems to be displaying the same error message twice under the text box. The code snippet that I am using ...

What is the best way to trigger a re-render of a Vue component after a change in Vuex

I'm interested in developing a custom component to modify the basemap of a leaflet map without relying on L.control.layers. To achieve this, I created a sidebar component that offers options to switch between different basemaps. In my implementation, ...

Encountering problem when trying to upload several images at once using a single input in CodeIgniter with

I'm attempting to use CodeIgniter and AJAX to upload multiple images using a single input field. Here's the code I have so far: HTML: <input type="file" name="files[]" id="file" multiple /> AJAX: $("#addItems").on("submit",function(e) { ...

The basic Node.js API for greeting the world encountered a connection failure

I'm currently working on setting up a basic hello world route using nodejs and express. After running node index.js, I see listening on port 3000 in the console, but when I attempt to access http://localhost:3000/helloworld, it just keeps trying to co ...

Implementing TypeScript/Angular client generation using Swagger/OpenAPI in the build pipeline

Generating Java/Spring Server-Stubs with swagger-codegen-maven-plugin In my Spring Boot Java project, I utilize the swagger-codegen-maven-plugin to automatically generate the server stubs for Spring MVC controller interfaces from my Swagger 2.0 api.yml fi ...

What is the proper way to set up @Input?

I attempted to accomplish this in the following manner: @Input() data: any[] = []; Upon entering the ngOnInit lifecycle method, I notice that this.data is undefined: ngOnInit() { console.log(this.data); } Consequently, when trying to access th ...

Create a roster of numbers that are multiples using a systematic approach

Is the following code a functional way to return multiples of 5? function Mul(start,array,Arr) { Arr[start]=array[start]*5; if(start>array.length-2){ return Arr; } return Mul(start+1,array,Arr); } var numbers =[1,2,3,4,5,6 ...

Cross-Origin Request Blocked in Browser (Angular Front-end, Quarkus Back-end)

application.properties #WEB quarkus.tls.trust-all=true quarkus.http.cors=true Controller @POST @Path("/result") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Display Ev ...

Ensure the detection of 404 error status using jQuery

My current approach involves using this code snippet to retrieve data from Twitter through its API: $.ajax({ url : apiUrl, cache : false, crossDomain: true, dataType: "jsonp", success : f ...

What is the best way to show inline icons within menu items?

Issue Upon selecting an option from the menu, I encounter a problem where the icon (represented by a question mark inside a speech bubble) appears on a different line than the text ("Question"). Fig. 1. Display of menu after selection with the icon and t ...