Categories
JavaScript Nodejs

Node.js FS Module — Symbolic Links and Timestamps

Manipulating files and directories are basic operations for any program. Since Node.js is a server-side platform and can interact with the computer that it’s running on directly, being able to manipulate files is a basic feature.

Fortunately, Node.js has a fs module built into its library. It has many functions that can help with manipulating files and folders. File and directory operations that are supported include basic ones like manipulating and opening files in directories. Likewise, it can do the same for files.

It can do this both synchronously and asynchronously. It has an asynchronous API that has functions that support promises.

Also, it can show statistics for a file. Almost all the file operations that we can think of can be done with the built-in fs module.

In this article, we will create symbolic links with the symlink family of functions and set timestamps with the utimes family of functions.

Creating Symbolic Links

Symbolic links are files that reference other files in the form of relative or absolute paths of the file. We can create symbolic links in Node.js programs with the symlink function.

The function takes 4 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object.

The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms. The possible values are 'dir' , 'file' , or 'junction' .

If the type argument isn’t set, it will automatically detect the type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used. Windows requires that the symbolic link path to be absolute. When using 'junction' , the target argument will be converted to the absolute path.

The last argument is a callback function that’s called when the symbolic link creation operation ends. It has an err parameter which is null when the operation succeeds and has an object with the error information when it failed.

We can create a symbolic link with the symlink function as follows:

const fs = require("fs");  
const target = "./files/file.txt";  
const path = "./files/symlink";

fs.symlink(target, path, "file", err => {  
  if (err) {  
    throw err;  
  }  
  console.log("Symbolic link creation complete!");  
});

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks something like the following:

File: './files/symlink' -> './files/file.txt'  
  Size: 16              Blocks: 0          IO Block: 512    symbolic link  
Device: eh/14d  Inode: 62487444831945583  Links: 1  
Access: (0777/lrwxrwxrwx)  Uid: ( 1000/hauyeung)   Gid: ( 1000/hauyeung)  
Access: 2019-11-03 11:22:19.787359800 -0800  
Modify: 2019-11-03 11:22:19.787359800 -0800  
Change: 2019-11-03 11:22:19.787359800 -0800  
 Birth: -

This means that are symbolic link has been successfully created.

There’s also a synchronous version of the symlink function that’s called the symlinkSync function. It takes 3 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object. The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms.

The possible values are 'dir' , 'file' , or 'junction' . If the type argument isn’t set, it will automatically detect the type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used. Windows requires that the symbolic link path to be absolute.

When using 'junction' , the target argument will be converted to the absolute path. It returns undefined .

We can use it to create symbolic links like in the following code:

const fs = require("fs");  
const target = "./files/file.txt";  
const path = "./files/symlink";

try {  
  fs.symlinkSync(target, path, "file");  
  console.log("Symbolic link creation complete!");  
} catch (error) {  
  console.error(error);  
}

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks the same as the ones above.

There’s also a promise version of the symlink function. It takes 3 arguments.

The first argument is the target path for our symbolic link which is the file path that we want to reference in our symbolic link. It can be in the form of a string, a buffer object, or an URL object.

The second argument is the path of the symbolic link, it can also be in the form of a string, a buffer object, or an URL object.

The third argument is the type, which is a string. It’s only available on Windows and ignored on other platforms.

The possible values are 'dir' , 'file' , or 'junction' . If the type argument isn’t set, it will automatically detect type of the target and use 'file' or 'dir' . If the target doesn’t exist, then 'file' would be used.

Windows requires that the symbolic link path be absolute. When using 'junction' , the target argument will be converted to the absolute path. It returns a promise that resolves with no arguments when it’s successful.

We can use it to create symbolic links like in the following code:

const fsPromises = require("fs").promises;  
const target = "./files/file.txt";  
const path = "./files/symlink";

(async () => {  
  try {  
    await fsPromises.symlink(target, path, "file");  
    console.log("Symbolic link creation complete!");  
  } catch (error) {  
    console.error(error);  
  }  
})();

After running the code above, when we run stat ./files/symlink on POSIX systems, we should get output that looks the same as the ones above.

The promise version of the symlink function is a much better choice than the symlinkSync function when you want to do multiple things sequentially that includes a call to the symlink function since it doesn’t tie up the whole program waiting for the symbolic link creation operation to complete before continuing to program other parts of the program.

Changing Timestamps of Items Stored on Disk

We can change the timestamp of the last accessed time and the last time a file was modified with the utimes function.

The function takes 4 arguments.

The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object.

If it’s a number, then it should be the UNIX timestamp.

If it’s a string, then it should be a string form of the UNIX timestamp.

If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

The fourth argument is a callback function which takes an err parameter. It’s null when the operation succeeds and has an object that has the error information if it fails.

We can use the utimes function like in the following code:

const fs = require("fs");  
const path = "./files/file.txt";

fs.utimes(  
  path,  
  new Date(2019, 0, 1, 0, 0, 0, 0),  
  new Date(2019, 0, 1, 0, 0, 0, 0),  
  err => {  
    if (err) {  
      throw err;  
    }  
    console.log("Timestamps changed");  
  }  
);

If we run the code above then run stat ./files/file.txt, we should get something like the following output:

File: './files/file.txt'  
  Size: 16              Blocks: 0          IO Block: 512    regular file  
Device: eh/14d  Inode: 22799473115106242  Links: 1  
Access: (0777/-rwxrwxrwx)  Uid: ( 1000/hauyeung)   Gid: ( 1000/hauyeung)  
Access: 2019-01-01 00:00:00.000000000 -0800  
Modify: 2019-01-01 00:00:00.000000000 -0800  
Change: 2019-11-03 11:41:16.155815100 -0800  
 Birth: -

As we can see, the Access and Modify times changed to 2019–01–01 00:00:00.000000000 , so we know that the utimes function has changed the timestamps successfully.

There’s a synchronous version of the utimes function called the utimesSync function.

The function takes 3 arguments.

The first is the path of the object stored on disk. It can be a string, a Buffer object and an URL object. The second argument is the atime which is the time that the object was last accessed.

It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp. If it’s a string, then it should be a string form of the UNIX timestamp. If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

We can use it as in the following code:

const fs = require("fs");  
const path = "./files/file.txt";

try {  
  fs.utimesSync(  
    path,  
    new Date(2019, 0, 1, 0, 0, 0, 0),  
    new Date(2019, 0, 1, 0, 0, 0, 0)  
  );  
  console.log("Timestamps changed");  
} catch (error) {  
  console.error(error);  
}

If we run the code above then run stat ./files/file.txt, we should get the same output as we did above.

There’s also a promise version of the utimes function, which let us run utimes asynchronously while running it in sequence like we do with the utimesSync function.

The function takes 3 arguments. The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp.

If it’s a string, then it should be a string form of the UNIX timestamp. If the value can’t be converted to a number or is NaN, Infinity or -Infinity then an error will be thrown.

The third argument is the mtime , which is the time that the object was last modified. It can be a number, a string or a Date object. It should be in the same format as the atime argument.

We can use it as in the following code:

const fsPromises = require("fs").promises;  
const path = "./files/file.txt";

(async () => {  
  try {  
    await fsPromises.utimes(  
      path,  
      new Date(2019, 0, 1, 0, 0, 0, 0),  
      new Date(2019, 0, 1, 0, 0, 0, 0)  
    );  
    console.log("Timestamps changed");  
  } catch (error) {  
    console.error(error);  
  }  
})();

If we run the code above then run stat ./files/file.txt, we should also get the same output as the regular utimes example.

We can create symbolic links with the symlink family of functions.

It takes 3 arguments. The first is the path of the object stored on disk. It can be a string, a Buffer object, and an URL object.

The second argument is the atime which is the time that the object was last accessed. It can be a number, a string or a Date object. If it’s a number, then it should be the UNIX timestamp, and if it’s a string, then it should be a string form of the UNIX timestamp.

The regular asynchronous version also takes a callback function which runs when it ends. The utimes function changes the timestamp of the access and modified time of an object stored on disk. It takes the path of the entity to apply the change and timestamps for access and modified times.

The regular asynchronous version also takes a callback function which runs when it ends. They are handy for times when we need to do these operations in our Node.js programs.

Categories
Express JavaScript

Guide to the Express Request Object — Methods

The request object lets us get the information about requests made from the client in middlewares and route handlers.

In this article, we’ll look at the properties of Express’s request object methods in detail, including getting headers and query strings.

Methods

The request object has various methods.

req.accepts(types)

The accepts method checks the Accept request header for the value that’s sent with the header. It takes a string with the value of the data type that we want to check if it’s sent with the Accept request header.

It returns the best match, or if nothing matches, false is returned.

If false is returned, then the 406 response code should be returned since the data type isn’t accepted by our route.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.accepts('html'));  
})
app.listen(3000);

Then when we send the Accept header with a value that has html in it, then it’ll return html.

Otherwise, false will be returned.

Other examples include:

req.accepts('text/html')  
req.accepts(['json', 'text'])  
req.accepts('application/json')  
req.accepts('image/png')  
req.accepts('png')  
req.accepts(['html', 'json'])

req.acceptsCharsets(charset [, …]), req.acceptsEncodings(encoding [, …]), req.acceptsLanguages(lang [, …])

The method returns the first accepted charset of the specified character set. The charset is from the request’s Accept-Charset HTTP header field.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.acceptsCharsets('utf8'));  
})
app.listen(3000);

Then we get back the character set in the Accept-Charset HTTP header if it’s set.

acceptsEncodings returns the encoding based on the Accepts-Encoding HTTP header; and acceptsLanguages returns the encoding based on the Accepts-Language HTTP header.

req.get(field)

We can get the header field value with the get method. It takes the key of the header and it’s case-insensitive.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.get('content-type'));  
})
app.listen(3000);

Then when we make a request to the / route with the Content-Type header’s value set to text/cmd, then we get back text/cmd.

req.is(type)

The is method returns the Content-Type header’s value if it matches the type passed in. If the request has no body, the null is returned. Otherwise, false is returned.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.is('html'));  
})
app.listen(3000);

Then we get null since it’s a GET request, which has no request body.

On the other hand, if we have:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.post('/', (req, res) => {  
  res.send(req.is('html'));  
})
app.listen(3000);

When we make a POST request to the/ route with the Content-Type header set to text/html, then we get back html .

If the Content-Type header is set to something without html, then we get back false .

req.param(name [, defaultValue])

req.param gets the value from the query string’s search parameter with the given key.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.post('/', (req, res) => {  
  res.send(req.param('name', 'Joe'));  
})
app.listen(3000);

The code above get the search parameter value with the name key. The default value is set to 'Joe'.

Then when we make a request to /?name=jane, then we get jane. Otherwise, if the name search parameter isn’t specified, then we get Joe.

req.param is deprecated, so we should use req.params, req.body or req.query, as applicable instead.

Lookup is of query string, URL parameter and body values are performed in the following order:

  • req.params
  • req.body
  • req.query

req.range(size[, options])

The range method parses the Range header. The size parameter has the maximum size of the resource. The options parameter is an object which can have the following property:

  • combine — a boolean that specifies if overlapping and adjacent ranges should be combined.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.get('/', (req, res) => {  
  const range = req.range(20000);  
  if (range && range.type === 'bytes') {  
    res.json(range);  
  }  
  else {  
    res.send();  
  }  
})app.listen(3000);

Then when we make a GET request to / with the Range header set to bytes=200–1000, 2000–6576, 19000- , we get:

[  
    {  
        "start": 200,  
        "end": 1000  
    },  
    {  
        "start": 2000,  
        "end": 6576  
    },  
    {  
        "start": 19000,  
        "end": 19999  
    }  
]

since we specified the size parameter to be 20000, so the last end value is 19999.

If we didn’t specify bytes in the value, we get nothing.

Conclusion

The request object has methods to parse query strings and get various headers like the Range, Accepts and Content-Type headers.

We can get the Range header with range, Accepts with accepts and Content-Type with is.

The get method can get any request header field’s value.

Categories
Express JavaScript

Guide to the Express Request Object — Queries and Cookies

The request object lets us get the information about requests made from the client in middlewares and route handlers.

In this article, we’ll look at the properties of Express’s request object in detail, including query strings, URL parameters, and signed cookies.

Request Object

The req parameter we have in the route handlers above is the req object.

It has some properties that we can use to get data about the request that’s made from the client-side. The more important ones are listed below.

req.originalUrl

The originalUrl property has the original request URL.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.get('/', (req, res) => {  
  res.send(req.originalUrl);  
})app.listen(3000);

Then when we have the request with query string /?foo=bar , we get back /?foo=bar .

req.params

params property has the request parameters from the URL.

For example, if we have:

const express = require('express')  
const app = express()app.use(express.json())  
app.use(express.urlencoded({ extended: true }))app.get('/:name/:age', (req, res) => {  
  res.json(req.params)  
})app.listen(3000, () => console.log('server started'));

Then when we pass in /john/1 as the parameter part of the URL, then we get:

{  
    "name": "john",  
    "age": "1"  
}

as the response from the route above.

req.path

The path property has the path part of the request URL.

For instance, if we have:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.get('/:name/:age', (req, res) => {  
  res.send(req.path);  
})app.listen(3000);

and make a request to /foo/1 , then we get back the same thing in the response.

req.protocol

We can get the protocol string with the protocol property.

For example, if we have:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.get('/', (req, res) => {  
  res.send(req.protocol);  
})app.listen(3000);

Then when we make a request through HTTP we get backhttp .

req.query

The query property gets us the query string from the request URL parsed into an object.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.json(req.query)  
})
app.listen(3000, () => console.log('server started'));

Then when we append ?name=john&age=1 to the end of the hostname, then we get back:

{  
    "name": "john",  
    "age": "1"  
}

from the response.

req.route

We get the current matched route with the route property.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();app.get('/', (req, res) => {  
  console.log(req.route);  
  res.json(req.route);  
})app.listen(3000);

Then we get something like the following from the console.log :

Route {  
  path: '/',  
  stack:  
   [ Layer {  
       handle: [Function],  
       name: '<anonymous>',  
       params: undefined,  
       path: undefined,  
       keys: [],  
       regexp: /^/?$/i,  
       method: 'get' } ],  
  methods: { get: true } }

req.secure

A boolean property that indicates if the request is made with via HTTPS.

For example, given that we have:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {    
  res.json(req.secure);  
})
app.listen(3000);

Then we get false if the request is made via HTTP.

req.signedCookies

The signedCookies object has the cookie with the value deciphers from the hashed value.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const cookieParser = require('cookie-parser');  
const app = express();  
app.use(cookieParser('secret'));
app.get('/cookie', (req, res, next) => {  
  res.cookie('name', 'foo', { signed: true })  
  res.send();  
})
app.get('/', (req, res) => {  
  res.send(req.signedCookies.name);  
})
app.listen(3000);

In the code above, we have the /cookie route to send the cookie from our app to the client. Then we can get the value from the client and then make a request to the / route with the Cookie header with name as the key and the signed value from the /cookie route as the value.

For example, it would look something like:

name=s%3Afoo.dzukRpPHVT1u4g9h6l0nV6mk9KRNKEGuTpW1LkzWLbQ

Then we get back foo from the / route after the signed cookie is decoded.

req.stale

stale is the opposite of fresh . See req.fresh for more details.

req.subdomains

We can get an array of subdomains in the domain name of the request with the subdomains property.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.subdomains);  
})
app.listen(3000);

Then when we make a request to the / route, we get something like:

["beautifulanxiousflatassembler--five-nine"]

req.xhr

A boolean property that’s true is X-Requested-With request header field is XMLHttpRequest .

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const app = express();
app.get('/', (req, res) => {  
  res.send(req.xhr);  
})
app.listen(3000);

Then when we make a request to the/ route with the X-Requested-With header set to XMLHttpRequest , we get back true .

Conclusion

There’re many properties in the request object to get many things.

We can get signed cookies with the Cookie Parser middleware. Query strings and URL parameters can access in various form with query , params , and route properties.

Also, we can get parts of the subdomain with the subdomains property.

Categories
GraphQL JavaScript Nodejs

An Introduction to GraphQL

GraphQL is a query language for our API and a server-side runtime for running queries by using a type system for our data.

In this article, we’ll look at how to make basic queries to a GraphQL API.

Defining the API

We define an API by defining the types and fields for those types and provide functions for each field on each type.

For example, if we have the following type:

type Query {  
  person: Person  
}

Then we have to create a function for the corresponding type to return the data:

function Query_person(request) {  
  return request.person;  
}

Making Queries

Once we have a GraphQL service running, we can send GraphQL queries to validate and execute on the server.

For example, we can make a query as follows:

{  
  person {  
    firstName  
  }  
}

Then we may get JSON like the following:

{  
  "person": {  
    "firstName": "Joe"  
  }  
}

Queries and Mutations

Queries are for getting data from the GraphQL server and mutations are used for manipulating data stored on the server.

For example, the following is a query to get a person’s name:

{  
  person {  
    name  
  }  
}

Then we may get the following JSON from the server:

{  
  "data": {  
    "person": {  
      "name": "Joe"  
    }  
  }  
}

The field name returns a String type.

We can change the query as we wish if we want to get more data. For example, if we write the following query:

{  
  person {  
    name  
    friends {  
      name  
    }  
  }  
}

Then we may get something like the following as a response:

{  
  "data": {  
    "person": {  
      "name": "Joe",  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    }  
  }  
}

The example above has friends being an array. They look the same from the query perspectively, but the server knows what to return based on the type specified.

Arguments

We can pass in arguments to queries and mutations. We can do a lot more with queries if we pass in arguments to it.

For example, we can pass in an argument as follows:

{  
  person(id: "1000") {  
    name      
  }  
}

Then we get something like:

{  
  "data": {  
    "person": {  
      "name": "Luke"  
    }  
  }  
}

from the server.

With GraphQL, we can pass in arguments to nested objects. For example, we can write:

{  
  person(id: "1000") {  
    name  
    height(unit: METER)  
  }  
}

Then we may get the following response:

{  
  "data": {  
    "person": {  
      "name": "Luke",  
      "height": 1.9  
    }  
  }  
}

In the example, the height field has a unit which is an enum type that represents a finite set of values.

unit may either be METER or FOOT.

Fragments

We can define fragments to let us reuse complex queries.

For example, we can define a fragment and use it as follows:

{  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends {  
    name  
  }  
}

In the code above, we defined the comparisonFields fragment which has the list of fields we want to include in each query.

Then we have the leftComparison and rightComparison queries which include the fields of the comparisonFields fragment by using the ... operator.

Then we get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

Using variables inside fragments

We can pass in variables into fragments as follows:

query PersonComparison($first: Int = 3){  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends(first: $first) {  
    name  
  }  
}

Then we may get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

as a response.

The operation type may either be a query, mutation, or subscription and describes what operator we’re intending to do. It’s required unless we’re using the query shorthand syntax. In that case, we can’t supply a name or variable definition for our operation.

The operation name is a meaningful and explicit name for our operation. It’s required in multi-operation documents. But its use is encouraged because it’s helpful for debugging and server-side logging.

It’s easy to identify the operation with a name.

Conclusion

GraphQL is a query language that lets us send requests to a server in a clear way. It works by sending nested objects with operation type and name along with any variables to the server.

Then the server will return us the response that we’re looking for.

Operation types include queries for getting data and mutations for making changes to data on the server.

Categories
JavaScript Rxjs

More Rxjs Transformation Operators — Group and Map

Rxjs is a library for doing reactive programming. Creation operators are useful for generating data from various data sources to be subscribed to by Observers.

In this article, we’ll look at some transformation operators like groupBy , map , mapTo and mergeMap .

groupBy

The groupBy operator takes values emitted by the source Observable and then group them by the criteria that we set for them.

It takes 4 arguments. The first is the keySelector , which is a function that extracts the key for each item.

The second is an optional argument for the elementSelector , which is a function that extracts the return element for each item.

The 3rd argument is the durationSelector , which is an optional function that returns an Observable to determine how long each group should exist.

Finally, the last argument is the subjectSelector , which is an optional function that returns a Subject.

For example, we can use it as follows:

import { of } from "rxjs";  
import { groupBy, reduce, mergeMap } from "rxjs/operators";
const observable = of(  
  { id: 1, name: "John" },  
  { id: 2, name: "Jane" },  
  { id: 2, name: "Mary" },  
  { id: 1, name: "Joe" },  
  { id: 3, name: "Don" }  
).pipe(  
  groupBy(p => p.id),  
  mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [])))  
);

observable.subscribe(val => console.log(val));

In the code above, we called groupBy(p => p.id) to group the items emitted from the of Observable by id .

Then we have:

mergeMap(group$ => group$.pipe(reduce((acc, cur) => [...acc, cur], [])))

to get the grouped items together to be emitted by one Observable.

map

The map operator lets us map the values emitted by the source Observable to the other values and emits the resulting values as an Observable.

It takes up to 2 arguments. The first argument is the project function, which is required. The function takes the emitted value of the source Observable and the index of it and then returns what we want by manipulating those.

The second argument is optional. It’s the thisArg , which is used to define the this value for the project function in the first argument.

For example, we can use it as follows:

import { of } from "rxjs";  
import { map } from "rxjs/operators";
const observable = of(1, 2, 3);  
const newObservable = observable.pipe(map(val => val ** 2));  
newObservable.subscribe(x => console.log(x));

The code above takes the emitted values from observable , then pass it to the map operator via the pipe operator. In the callback, we passed into the map operator, we exponentiate the originally emitted value to the power of 2.

Then we can subscribe to an Observable that emits the new values and we get:

1  
4  
9

logged.

mapTo

The mapTo operator emits the given constant value for any source Observable’s emitted value.

It takes one argument, which is the value to emit.

For example, we can use it as follows:

import { of } from "rxjs";  
import { mapTo } from "rxjs/operators";
const observable = of(1, 2, 3);  
const newObservable = observable.pipe(mapTo("foo"));  
newObservable.subscribe(x => console.log(x));

The code above maps all the values from observable to the value 'foo' , so we get 'foo' 3 times instead of 1, 2 and 3.

mergeMap

The mergeMap operator takes the values emitted from a source Observable and then lets us combine it with the values of another Observable.

It takes up to 3 arguments. The first is a project function to project the values and return a new Observable.

The second argument is an optional argument that takes an resultSelector . We can pass in a function to select the values to emit from the result.

The third argument is the concurrency , which is an optional argument that specifies the maximum number of input Observables to be subscribed to concurrently. The default is Number.POSITIVE_INFINITY .

For example, we can use it as follows:

import { of } from "rxjs";  
import { mergeMap, map } from "rxjs/operators";
const nums = of(1, 2, 3);  
const result = nums.pipe(mergeMap(x => of(4, 5, 6).pipe(map(i => x + i))));  
result.subscribe(x => console.log(x));

The code above will get the values from the 3 values emitted from the nums Observable, then pass the values to the mergeMap ‘s callback function via the pipe operator. x will have the values from nums .

Then in the callback, we have the of(4, 5, 6) Observable, which have the values from combined from the nums Observable. i has the values from the of(4, 5, 6) Observable, so we the values from both Observables added together. We get 1 + 4, 1 + 5, 1 + 6, 2 + 4, 2 + 5, 2 + 6 and so on.

In the end, we should get the following output:

5  
6  
7  
6  
7  
8  
7  
8  
9

The groupBy operator takes values emitted by the source Observable and then group them by the criteria that we set for them. We can use it in conjunction with the mergeMap to combined the results grouped by the groupBy operator into one Observable.

The map operator lets us map the values emitted by the source Observable to the other values and emits the resulting values as an Observable.

The mapTo operator emits the given constant value for any source Observable’s emitted value.

Finally mergeMap operator takes the values emitted from a source Observable and then lets us combine it with the values of another Observable. Then we get an Observable with the values of both Observables combined together.