Building the KNN algorithm With JavaScript
k-Nearest Neighbor (KNN)
The KNN is a simple, fast, and straightforward classification algorithm. It is very useful for categorized numerical datasets, where the data is naturally clustered. It will feel similar in some ways to the k-means clustering algorithm; with the major distinction being that k-means is an unsupervised algorithm while KNN is a supervised learning algorithm.
If you wish to perform a KNN analysis manually, here’s how it should go: first, plot all your training data on a graph and label each point with its category or label. When you wish to classify a new, unknown point, put it on the graph and find the k closest points to it (the nearest neighbors).
The number k should be an odd number in order to avoid ties; three is a good starting point, but some applications will need more and some can get away with one. Report whatever the majority of the k nearest neighbors is classified, as that will be the result of the algorithm.
Finding the k nearest neighbors to a test point is straightforward, but you can use some optimizations if your training data is very large. Typically, when evaluating a new point, you would calculate the Euclidean distance (the typical, high school geometry distance measure) between your test point and every other training point, and sort them by distance. This algorithm is quite fast because the training data is generally not more than 10,000 points or so.
If you have many training examples (in the order of millions) or you really need the algorithm to be lightning-fast, there are two optimizations you can make. The first is to skip the square root operation in the distance measure and use the squared distance instead. While modern CPUs are very fast, the square root operation is still much slower than multiplication and addition, so you can save a few milliseconds by avoiding the square root.
The second optimization is to only consider points within some bounding rectangle of distance to your test point; for instance, only consider points within +/- 5 units in each dimension from the test point’s location. If your training data is dense, this optimization will not affect the results but will speed up the algorithm because it will avoid calculating distances for many points.
The following is the KNN algorithm as a high-level description:
- Record all training data and their labels
- Given a new point to evaluate, generate a list of its distances to all training points
- Sort the list of distances in the order of closest to farthest
- Throw out all but the knearest distances
- Determine which label represents the majority of your knearest neighbors; this is the result of the algorithm
A more efficient version avoids maintaining a large list of distances that need to be sorted by limiting the list of distances to k items. Now get started with your implementation of the KNN algorithm.
Building the KNN algorithm
Since the KNN algorithm is quite simple, you can build your own implementation:
- Create a new folder and name it Ch5-knn.
- Add the following jsonfile to the folder. Here, you have added a dependency for the jimp library, which is an image processing library:
{ "name": "Ch5-knn", "version": "1.0.0", "description": "ML in JS Example for Chapter 5 - k-nearest-neighbor", "main": "src/index.js", "author": "Burak Kanber", "license": "MIT", "scripts": { "build-web": "browserify src/index.js -o dist/index.js -t [ babelify --presets [ env ] ]", "build-cli": "browserify src/index.js --node -o dist/index.js -t [ babelify --presets [ env ] ]", "start": "yarn build-cli && node dist/index.js" }, "dependencies": { "babel-core": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.1", "babelify": "^8.0.0", "browserify": "^15.1.0", "jimp": "^0.2.28" } }
- Run the yarn installcommand to download and install all the dependencies and then create subfolders called src, dist, and files.
- Inside the srcfolder, create an js file and a knn.js file.
You will also need a data.js file. For these examples, a larger dataset has been used which is difficult to be printed here, so you should take a minute to download the Ch5-knn/src/data.js file from GitHub. You can also find the complete code for this article at https://github.com/PacktPublishing/Hands-On-Machine-Learning-with-JavaScript/tree/master/Chapter05/Ch5-knn.
- Start with the jsfile. You’ll need a distance-measuring function. Add the following to the beginning of knn.js:
/** * Calculate the distance between two points. * Points must be given as arrays or objects with equivalent keys. * @param {Array.<number>} a * @param {Array.<number>} b * @return {number} */ const distance = (a, b) => Math.sqrt( a.map((aPoint, i) => b[i] - aPoint) .reduce((sumOfSquares, diff) => sumOfSquares + (diff*diff), 0) );
If you really need a performance optimization for your KNN implementation, this is where you might omit the Math.sqrt operation and return just the squared distance. However, since this is such a fast algorithm by nature, you need to do this only if you’re working on an extreme problem with a lot of data or with very strict speed requirements.
- Next, add the stub of your KNN class. Add the following to js, beneath the distance function:
class KNN { constructor(k = 1, data, labels) { this.k = k; this.data = data; this.labels = labels; } } export default KNN;
The constructor accepts three arguments: the k or the number of neighbors to consider when classifying your new point, the training data split up into the data points alone, and a corresponding array of their labels.
- Next, you need to add an internal method that considers a test point and calculates a sorted list of distances from the test point to the training points. You can call this a distance map. Add the following to the body of the KNN class:
generateDistanceMap(point) { const map = []; let maxDistanceInMap; for (let index = 0, len = this.data.length; index < len; index++) { const otherPoint = this.data[index]; const otherPointLabel = this.labels[index]; const thisDistance = distance(point, otherPoint); /** * Keep at most k items in the map. * Much more efficient for large sets, because this * avoids storing and then sorting a million-item map. * This adds many more sort operations, but hopefully k is small. */ if (!maxDistanceInMap || thisDistance < maxDistanceInMap) { // Only add an item if it's closer than the farthest of the candidates map.push({ index, distance: thisDistance, label: otherPointLabel }); // Sort the map so the closest is first map.sort((a, b) => a.distance < b.distance ? -1 : 1); // If the map became too long, drop the farthest item if (map.length > this.k) { map.pop(); } // Update this value for the next comparison maxDistanceInMap = map[map.length - 1].distance; } } return map; }
This method could be easier to read, but the simpler version is not efficient for very large training sets. What you’re doing here is maintaining a list of points that might be the KNNs and storing them in map.
By maintaining a variable called maxDistanceInMap, you can loop over every training point and make a simple comparison to see whether the point should be added to your candidates’ list. If the point you’re iterating over is closer than the farthest of your candidates, you can add the point to the list, re-sort the list, remove the farthest point to keep the list small, and then update mapDistanceInMap.
If that sounds like a lot of work, a simpler version might loop overall points, add each one with its distance measurement to the map, sort the map, and then return the first k items. The downside of this implementation is that for a dataset of a million points, you’d need to build a distance map of a million points and then sort that giant list in memory.
In your version, you only ever hold k items as candidates, so you never need to store a separate million-point map. Your version does require a call to Array.sort whenever an item is added to the map. This is inefficient in its own way, as the sort function is called for each addition to the map. Fortunately, the sort operation is only for k items, where k might be something like 3 or 5.
The computational complexity of the sorting algorithm is most likely O(n log n) (for a quicksort or mergesort implementation), so it only takes about 30 data points for the sophisticated version to be more efficient than the simple version when k = 3, and for k = 5, this happens at around 3,000 data points. However, both versions are so fast that for a dataset smaller than 3,000 points, you won’t notice the difference.
- Finally, tie the algorithm together with the predict The predictmethod must accept a test point, and at the very least, return the determined label for the point. You can also add some additional output to the method and report the labels of the k nearest neighbors as well as the number of votes each label contributed. Add the following to the body of the KNN class:
predict(point) { const map = this.generateDistanceMap(point); const votes = map.slice(0, this.k); const voteCounts = votes // Reduces into an object like {label: voteCount} .reduce((obj, vote) => Object.assign({}, obj, {[vote.label]: (obj[vote.label] || 0) + 1}), {}) ; const sortedVotes = Object.keys(voteCounts) .map(label => ({label, count: voteCounts[label]})) .sort((a, b) => a.count > b.count ? -1 : 1) ; return { label: sortedVotes[0].label, voteCounts, votes }; }
This method requires a little bit of datatype juggling in JavaScript but is simple in concept. First, generate your distance map using the method you just implemented. Then, remove all data except for the k nearest points and store that in a votes variable. If you’re using 3 as k, then votes will be an array of length three.
Now that you have your k nearest neighbors, you need to figure out which label represents the majority of the neighbors. You can do this by reducing your votes array into an object called voteCounts. To get a picture of what you want voteCounts to look like, imagine that you’re looking for the three nearest neighbors and the possible categories are Male or Female. The voteCounts variable might look like this: {“Female”: 2, “Male”: 1}.
The job is still not done, however—after reducing your votes into a vote-count object, you still need to sort that and determine the majority label. You can do this by mapping the vote counts object back into an array and then sorting the array based on vote counts.
There are other ways to approach this problem of tallying votes; any method you can think of will work, as long as you can return the majority vote at the end of the day. That’s all you need to do in the knn.js file. The algorithm is complete, requiring fewer than 70 lines of code.
Now set up your index.js file and get ready to run some examples. Remember that you need to download the data.js file first. You can do this by downloading the file from https://github.com/bkanber/MLinJSBook. Now add the following to the top of index.js:
import KNN from'./knn.js'; import {weight_height} from'./data.js';
You can now try out the algorithm using a simple example.
Example – Height, weight, and gender
KNN, like k-means, can work on high-dimensional data—but, like k-means, you can only graph example data in a two-dimensional plane, so keep your example simple. The first question you’ll tackle is: can you predict a person’s biological sex given only their height and weight?
The data for this example has been downloaded from a national longitudinal survey on people’s perception of their weight. Included in the data are the respondents’ height, weight, and gender. This is what the data looks like when graphed:
Just by looking at the preceding charted data, you can get a sense as to why KNN is so effective at evaluating clustered data. It’s true that there’s no neat boundary between male and female, but if you were to evaluate a new data point of a 200 pound, 72 inches-tall person, it’s clear that all the training data around that point is male and it’s likely your new point is male, too.
Conversely, a new respondent at 125 pounds and a height of 62 inches is well into the female area of the graph, though there are a couple of males with those characteristics as well. The middle of the graph, around 145 pounds and 65 inches tall, is the most ambiguous, with an even split of male and female training points. Expect the algorithm to be uncertain about the new points in that area. As there is no clear dividing line in this dataset, you would need more features or more dimensions to get a better resolution of the boundaries.
In any case, try out a few examples. Pick five points that you may expect to be definitely male, definitely female, probably male, probably female, and indeterminable. Add the following code to index.js, beneath the two import lines:
console.log("Testing height and weight with k=5"); console.log("=========================="); constsolver1 = new KNN(5, weight_height.data, weight_height.labels); console.log("Testing a 'definitely male' point:"); console.log(solver1.predict([200, 75])); console.log("\nTesting a 'probably male' point:"); console.log(solver1.predict([170, 70])); console.log("\nTesting a 'totally uncertain' point:"); console.log(solver1.predict([140, 64])); console.log("\nTesting a 'probably female' point:"); console.log(solver1.predict([130, 63])); console.log("\nTesting a 'definitely female' point:"); console.log(solver1.predict([120, 60]));
Run yarn start from the command line and you should see the following output. Since the KNN is not stochastic that is it does not use any random conditions in its evaluation, you should see exactly the same output with the possible exception of the ordering of votes and their indexes, if two votes have the same distance.
If you get an error when you run yarn start, make sure that your data.js file has been correctly downloaded and installed.
Here’s the output from the preceding code:
Testing heightand weight withk=5 ====================================================================== Testing a'definitely male' point: { label: 'Male', voteCounts: { Male: 5 }, votes: [ { index: 372, distance: 0, label: 'Male' }, { index: 256, distance: 1, label: 'Male' }, { index: 291, distance: 1, label: 'Male' }, { index: 236, distance: 2.8284271247461903, label: 'Male' }, { index: 310, distance: 3, label: 'Male' } ] } Testing a'probably male' point: { label: 'Male', voteCounts: { Male: 5 }, votes: [ { index: 463, distance: 0, label: 'Male' }, { index: 311, distance: 0, label: 'Male' }, { index: 247, distance: 1, label: 'Male' }, { index: 437, distance: 1, label: 'Male' }, { index: 435, distance: 1, label: 'Male' } ] } Testing a'totally uncertain' point: { label: 'Male', voteCounts: { Male: 3, Female: 2 }, votes: [ { index: 329, distance: 0, label: 'Male' }, { index: 465, distance: 0, label: 'Male' }, { index: 386, distance: 0, label: 'Male' }, { index: 126, distance: 0, label: 'Female' }, { index: 174, distance: 1, label: 'Female' } ] } Testing a'probably female' point: { label: 'Female', voteCounts: { Female: 4, Male: 1 }, votes: [ { index: 186, distance: 0, label: 'Female' }, { index: 90, distance: 0, label: 'Female' }, { index: 330, distance: 0, label: 'Male' }, { index: 51, distance: 1, label: 'Female' }, { index: 96, distance: 1, label: 'Female' } ] } Testing a'definitely female' point: { label: 'Female', voteCounts: { Female: 5 }, votes: [ { index: 200, distance: 0, label: 'Female' }, { index: 150, distance: 0, label: 'Female' }, { index: 198, distance: 1, label: 'Female' }, { index: 147, distance: 1, label: 'Female' }, { index: 157, distance: 1, label: 'Female' } ] }
The algorithm has determined genders just as you would have done, visually, by looking at the chart. Feel free to play with this example more and experiment with different values of k to see how results might differ for any given test point.
If you found this article interesting, you can explore Burak Kanber’s Hands-on Machine Learning with JavaScript to gain hands-on knowledge on evaluating and implementing the right model, along with choosing from different JS libraries, such as NaturalNode, brain, harthur, and classifier to design smarter applications. This book is a definitive guide to creating an intelligent web application with the best of machine learning and JavaScript.
HTTP server applications with Node.js
In this tutorial we’ll learn about HTTP server applications and HTTP sniffing by David Herron, a software engineer in Silicon Valley, who has worked on various enterprise web application projects.
Launching a server with Node.js
Many scripts that you’ll run are server processes. Before you get started, you need to launch a simple HTTP server with Node.js. Borrow the simple server script on the Node.js home page (http://nodejs.org), and create a file named app.js containing the following:
const http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World!\n'); }).listen(8124, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8124');
Run it as follows:
$ node app.js Server running at http://127.0.0.1:8124
This is the simplest of web servers you can build with Node.js. Now, visit http://127.0.0.1:8124 in your browser to see the Hello, World! message:
HTTP server applications
The HTTP server object is the foundation of all Node.js web applications. The object itself is very close to the HTTP protocol, and its use requires knowledge of that protocol. In most cases, you’ll be able to use an application framework such as Express that hides the HTTP protocol details, allowing you to focus on business logic.
The http.createServer function will create an http.Server object because it is an EventEmitter; this can be written in another way to make that fact explicit:
const http = require('http'); const server = http.createServer(); server.on('request', (req, res) => { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World!\n'); }); server.listen(8124, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8124');
The request event takes a function, which receives request and response objects. The request object has data from the web browser, while the response object is used to gather the data to be sent in the response. The listen function causes the server to start listening and arranging to dispatch an event for every request arriving from a web browser.
Creating a Node.Js Server
Now, here is something more interesting with different actions based on the URL. Create a new file, named server.js, containing the following code:
const http = require('http'); const util = require('util'); const url = require('url'); const os = require('os'); const server = http.createServer(); server.on('request', (req, res) => { var requrl = url.parse(req.url, true); if (requrl.pathname === '/') { res.writeHead(200, {'Content-Type': 'text/html'}); res.end( `<html><head><title>Hello, world!</title></head> <body><h1>Hello, world!</h1> <p><a href='/osinfo'>OS Info</a></p> </body></html>`); } else if (requrl.pathname === "/osinfo") { res.writeHead(200, {'Content-Type': 'text/html'}); res.end( `<html><head><title>Operating System Info</title></head> <body><h1>Operating System Info</h1> <table> <tr><th>TMP Dir</th><td>${os.tmpdir()}</td></tr> <tr><th>Host Name</th><td>${os.hostname()}</td></tr> <tr><th>OS Type</th><td>${os.type()} ${os.platform()} ${os.arch()} ${os.release()}</td></tr> <tr><th>Uptime</th><td>${os.uptime()} ${util.inspect(os.loadavg())}</td></tr> <tr><th>Memory</th><td>total: ${os.totalmem()} free: ${os.freemem()}</td></tr> <tr><th>CPU's</th><td><pre>${util.inspect(os.cpus())}</pre></td></tr> <tr><th>Network</th><td><pre>${util.inspect(os.networkInterfaces())}</pre></td></tr> </table> </body></html>`); } else { res.writeHead(404, {'Content-Type': 'text/plain'}); res.end("bad URL "+ req.url); } }); server.listen(8124); console.log('listening to http://localhost:8124');
To run it, type the following command:
$ node server.js listening to http://localhost:8124
This application is meant to be similar to PHP’s sysinfo function. Node’s os module is consulted to provide information about the server. This example can easily be extended to gather other pieces of data about the server:
A central part of any web application is the method of routing requests to request handlers. The request object has several pieces of data attached to it, two of which are used for routing requests—the request.url and request.method fields.
In server.js, you can consult the request.url data to determine which page to show, after parsing (using url.parse) to ease the digestion process. In this case, you can do a simple comparison of the pathname to determine which handler method to use.
Some web applications care about the HTTP verb (GET, DELETE, POST, and so on) used and must consult the request.method field of the request object. For example, POST is frequently used for FORM submissions.
The pathname portion of the request URL is used to dispatch the request to the correct handler. While this routing method, based on simple string comparison, will work for a small application, it’ll quickly become unwieldy. Larger applications will use pattern matching to use part of the request URL to select the request handler function and other parts to extract request data out of the URL.
A search for a URL match in the npm repository turns up several promising packages that could be used to implement request matching and routing. A framework like Express has this capability already baked in and tested.
If the request URL is not recognized, the server sends back an error page using a 404 result code. The result code informs the browser about the status of the request, where a 200 code means everything is fine, and a 404 code means that the requested page doesn’t exist. There are, of course, many other HTTP response codes, each with their own meaning.
HTTP Sniffer – listening to the HTTP conversation
The events emitted by the HTTPServer object can be used for additional purposes beyond the immediate task of delivering a web application. The following code demonstrates a useful module that listens to all the HTTPServer events. It could be a useful debugging tool, which also demonstrates how HTTPServer objects operate.
Node.js’s HTTPServer object is an EventEmitter and the HTTP Sniffer simply listens to every server event, printing out information pertinent to each event. What you’re about to do is:
- Create a module, httpsniffer that prints information about HTTP requests.
- Add that module to the jsscript you just created.
- Rerun that server to view a trace of HTTP activity.
Create a file named httpsniffer.js containing the following code:
const util = require('util'); const url = require('url'); const timestamp = () => { return new Date().toISOString(); } exports.sniffOn = function(server) { server.on('request', (req, res) => { console.log(`${timestamp()} e_request`); console.log(`${timestamp()} ${reqToString(req)}`); }); server.on('close', errno => { console.log(`${timestamp()} e_close ${errno}`); }); server.on('checkContinue', (req, res) => { console.log(`${timestamp()} e_checkContinue`); console.log(`${timestamp()} ${reqToString(req)}`); res.writeContinue(); }); server.on('upgrade', (req, socket, head) => { console.log(`${timestamp()} e_upgrade`); console.log(`${timestamp()} ${reqToString(req)}`); }); server.on('clientError', () => { console.log(`${timestamp()} e_clientError`); }); }; const reqToString = exports.reqToString = (req) => { var ret=`req ${req.method} ${req.httpVersion} ${req.url}` +'\n'; ret += JSON.stringify(url.parse(req.url, true)) +'\n'; var keys = Object.keys(req.headers); for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; ret += `${i} ${key}: ${req.headers[key]}` +'\n'; } if (req.trailers) ret += util.inspect(req.trailers) +'\n'; return ret; };
Wow! That was a lot of code! However, the key to it is the sniffOn function. When given an HTTP Server object, it uses the .on function to attach listener functions that print data about each emitted event. It gives a fairly detailed trace of HTTP traffic on an application.
In order to use it, simply insert this code just before the listen function in server.js:
require('./httpsniffer').sniffOn(server); server.listen(8124); console.log('listening to http://localhost:8124');
With this in place, run the server you launched earlier. You can visit http://localhost:8124/ in your browser and see the following console output:
$ node server.js listening to http://localhost:8124 2017-12-03T19:21:33.162Z request 2017-12-03T19:21:33.162Z request GET 1.1 / {"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname":null,"hash":null,"search":"","query":{},"pathname":"/","path":"/","href":"/"} 0 host: localhost:8124 1 upgrade-insecure-requests: 1 2 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 3 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5 4 accept-language: en-us 5 accept-encoding: gzip, deflate 6 connection: keep-alive {} 2017-12-03T19:21:42.154Z request 2017-12-03T19:21:42.154Z request GET 1.1 /osinfo {"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname":null,"hash":null,"search":"","query":{},"pathname":"/osinfo","path":"/osinfo","href":"/osinfo"} 0 host: localhost:8124 1 connection: keep-alive 2 upgrade-insecure-requests: 1 3 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 4 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5 5 referer: http://localhost:8124/ 6 accept-language: en-us 7 accept-encoding: gzip, deflate {}
You now have a tool for snooping on HTTPServer events. This simple technique prints a detailed log of the event data. The pattern can be used for any EventEmitter object. You can use this technique as a way to inspect the actual behavior of EventEmitter objects in your program.
If you found this article helpful, you can explore David Herron’s Node.js Web Development – Fourth Edition to create real-time applications using Node.js 10, Docker, MySQL, MongoDB, and Socket.IO. With this practical guide, you can go beyond the developer’s laptop to cover live deployment, including HTTPS and hardened security.
How to Troubleshoot Common Angular Errors
Learn how to optimize your troubleshooting tools and became aware of the common Angular errors you might encounter while developing applications.
Debugging Tools
In this article, you will intentionally introduce an easy-to-make mistake so that you can become familiar with real-life errors that can happen while developing your applications and gain a solid understanding of the tool that makes make you an effective developer.
Now pretend that you have made an innocent mistake when copying and pasting the URL from the API documentation page on OpenWeatherMap.org and forgot to add http:// in front of it. This is an easy mistake to make:
src/app/weather/weather.service.ts ... return this.httpClient .get<ICurrentWeatherData>( `api.openweathermap.org/data/2.5/weather?q=${city},${country}&appid=${environment.appId}` ).pipe(map(data => this.transformToICurrentWeather(data))) ...
Your app will compile successfully, but when you inspect the results in the browser, you won’t see any weather data. In fact, it seems like the Current Weather component is not rendering at all, as you can see in the image below:
Current Weather Does Not Render
There are many ways to debug angular errors for application or any JavaScript application here are some of this:
Debugging with Chrome Developer Tools
You can use the Google Chrome browser because of its cross-platform and consistent developer tools with helpful extensions.
Open Chrome Developer Tools (dev tools) on macOS by pressing option + ⌘ + I or on Windows by pressing F12 or Ctrl+ Shift + I.
As a best practice, you write code with VS Code and the browser open side by side, while the dev tools are also open in the browser. There are several good reasons for practicing side-by-side development:
- Fast feedback loops: With live-reloading, you see the end result of your changes very quickly
- Laptops: A lot of developers do most of their development on a laptop and a second monitor is a luxury
- Attention to responsive design: As you may have limited space to work with, you can constantly pay attention to mobile-first development, fixing desktop layout issues after the fact
- Awareness of network activity: To enable you to quickly see any API call errors and also ensure that the amount of data that is being requested remains in line with your expectations
- Awareness of console errors: To enable you to quickly react and troubleshoot when new errors are introduced
Observe how side-by-side development looks like:
Side-by-side development with live-reloading running
Note that ultimately, you should do what works best for you. With the side-by-side setup, you will frequently find yourself toggling VS Code’s Explorer on and off and resizing the dev tools pane to a larger or smaller size depending on the specific task at hand. To toggle VS Code’s Explorer, click on the Explorer icon circled in the preceding screenshot.
Just as you can do side-by-side development with live-reloading using npm start to solve angular errors. You can get the same kind of fast feedback loops for unit testing using npm test:
Side-by-side development with unit testing
With the side-by-side unit testing setup, you can become very effective in developing unit tests.
Optimizing Chrome Dev Tools
For the side-by-side development with live-reloading to work well, you need to optimize the default dev tools experience:
Optimized Chrome Developer Tools
Looking at the preceding figure, you will note that numerous settings and information radiators are highlighted:
- Have the Network tab open by default so that you can see network traffic flowing.
- Open the dev tools settings by clicking on the
- Click on the right-hand side icon so that dev tools dock on the right-hand side of Chrome. This layout gives more vertical space, so you can see more network traffic and console events at once. As a side benefit, the left-hand side takes the rough size and shape of a mobile device.
- Toggle on large request rows and toggle off overview to see more of the URL and parameters for each request and gain more vertical space.
- Check the option Disable cache, which will force reload every resource when you refresh a page while the dev tools are open. This prevents bizarre caching errors from ruining your day.
- You will mostly be interested in seeing XHR calls to various APIs, so click on XHR to filter results.
- Note that you can glance at the number of console errors in the upper-right corner as 12. The ideal number of console errors should be 0 at all times.
- Note that the top item in the request row indicates that there’s an error with status code 404 Not Found.
- Since you’re debugging an Angular application, the Augury extension has been loaded.
With your optimized dev tools environment, you can now effectively troubleshoot and resolve the application error from earlier.
Troubleshooting network issues
There are three visible issues with the app at this state:
- The component details aren’t displaying
- There are numerous console errors
- The API call is returning a 404 not found error
Begin by inspecting any network errors, since network errors usually cause knock-on effects:
- Click on the failing URL in the Network
- In the Details pane that opens to the right of the URL, click on the Preview
- You should see this:
Cannot GET /api.openweathermap.org/data/2.5/weather
By just observing this error message, you will likely miss the fact that you forgot to add the http:// prefix to the URL. The bug is subtle and certainly not glaringly obvious.
- Hover over the URL and observe the full URL, as shown:
Inspecting Network Errors
As you can see, now the bug is glaringly obvious. In this view, you will get to see the full URL, and it becomes clear that the URL defined in weather.service.ts is not fully qualified, so Angular is attempting to load the resource from its parent server, hosted on localhost:5000, instead of going over the web to the right server.
Investigating console errors
Before you fix this issue, it is worthwhile to understand the knock-on effects of the failing API call:
- Observe the console errors:
Dev Tools Console Error Context
The first element of note here is the ERROR CONTEXT object, which has a property named DebugContext_. The DebugContext_ contains a detailed snapshot of the current state of your Angular application when the error happened. The information contained within DebugContext_ is light years ahead of the amount of mostly unhelpful error messages AngularJS generates.
Note that properties that have the value (…) are property getters, and you must click on them to load their details. For example, if you click on the ellipsis for componentRenderElement, it will be populated with the app-current-weather element. You can expand the element to inspect the runtime condition of the component.
- Now scroll to the top of the console.
- Observe the first error:
ERROR TypeError: Cannot read property 'city' of undefined
You have probably encountered the TypeError before. This error is caused by trying to access the property of an object that is not defined. In this case, CurrentWeatherComponent.current is not assigned to an object, because the http call is failing. Since current is not initialized and the template blindly tries to bind to its properties like {{current.city}}, you will get a message saying property ‘city’ of undefined cannot be read. This is the kind of knock-on effect that can create many unpredictable side-effects in your application. You must proactively code to prevent this condition.
Karma, Jasmine, and Unit Testing errors
When running tests with the ng test command, you will encounter some high-level errors that can mask the root cause of the actual underlying errors.
The general approach to resolving errors should be inside out, resolving child component issues first and leaving parent and root components for last.
Network Error
Network errors can be caused by a multitude of underlying issues:
NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///DynamicTestModule/AppComponent.ngfactory.js'.
Working inside out, you should implement test doubles of services and provide the fakes to the appropriate components. However, in parent components, you may still encounter errors even if you correctly provided fakes.
Generic ErrorEvents
Error events are generic errors that hide the underlying cause:
[object ErrorEvent] thrown
To expose the root cause of a generic error, implement a new test:debug script:
- Implement test:debug, as shown, in json:
package.json ... "scripts": { ... "test:debug": "ng test --sourcemaps=false", ... }
- Execute npm run test:debug.
- Now the Karma runner will likely reveal the underlying issue.
- If necessary, follow the stack trace to find the child component that may be causing the issue.
Note that if this strategy is not helpful, you may be able to glean more information on what’s going wrong by breakpoint debugging your unit tests.
Debugging with Visual Studio Code
You can also debug your Angular application, Karma, and Protractor tests from directly within Visual Studio Code. First, you need to configure the debugger to work with a Chrome debugging environment, as illustrated:
VS Code Debugging Setup
- Click on the Debug
- Expand the No Configurations drop-down and click on Add Configuration….
- In the Select Environment select box, select Chrome.
This will create a default configuration in the .vscode/launch.json file. You can modify this file to add three separate configurations.
- Replace the contents of jsonwith the following configuration:
.vscode/launch.json { "version": "0.2.0", "configurations": [ { "name": "npm start", "type": "chrome", "request": "launch", "url": "http://localhost:5000/#", "webRoot": "${workspaceRoot}", "runtimeArgs": [ "--remote-debugging-port=9222" ], "sourceMaps": true }, { "name": "npm test", "type": "chrome", "request": "launch", "url": "http://localhost:9876/debug.html", "webRoot": "${workspaceRoot}", "runtimeArgs": [ "--remote-debugging-port=9222" ], "sourceMaps": true }, { "name": "npm run e2e", "type": "node", "request": "launch", "program": "${workspaceRoot}/node_modules/protractor/bin/protractor", "protocol": "inspector", "args": ["${workspaceRoot}/protractor.conf.js"] } ] }
- Execute the relevant CLI command like npm start, npm test, or npm run e2ebefore you start the debugger.
- On the Debugpage, in the Debug drop-down, select npm start and click on the green play icon.
- Observe that a Chrome instance has launched.
- Set a breakpoint on a .ts
- Perform the action in the app to trigger the
- If all goes well, Chrome will report that the code has been Paused in Visual Studio Code.
Note that this method of debugging doesn’t reliably work. You may have to manually set a breakpoint in Chrome Dev Tools | Sources tab, finding the same .ts file under the webpack://. folder, which will then correctly trigger the breakpoint in VS Code. However, this renders the entire benefit of using VS Code to debug code useless. For more information, follow the Angular CLI section on VS Code Recipes on GitHub at https://github.com/Microsoft/vscode-recipes.
If you liked this article, you can learn more about Angular 6 in Doguhan Uluca’s Angular 6 for Enterprise-Ready Web Applications to follow a hands-on and minimalist approach demonstrating how to design and architect high-quality apps.
Customizing a Simple Social Media Application Using MERN
Learn how to use the MERN stack to build simple social media application features in this tutorial by Shama Hoque.
MERN Social
MERN Social is a sample social media application, used for the purpose of this article, with rudimentary features inspired by existing social media platforms such as Facebook and Twitter. In this article, you’ll learn how to update a user profile in MERN Social that can display a user uploaded profile photo and an about description.
The main purpose of this application is to demonstrate how to use the MERN stack technologies to implement features that allow users to connect and interact over content. You can extend these implementations further, as desired, for more complex features.
The code for the complete MERN Social application is available on GitHub in the repository at github.com/shamahoque/mern-social. You can clone this code and run the application as you go through the code explanations. Note that you will need to create a MERN skeleton application by adding a working React frontend, including frontend routing and basic server-side rendering of the React views for following these steps.
The views needed for the MERN Social application can be developed by extending and modifying the existing React components in the MERN skeleton application. The following component tree shows all the custom React components that make up the MERN Social frontend and also exposes the composition structure that you can use to build out extended views:
Updating the user profile
The skeleton application only has support for a user’s name, email, and password. However, in MERN Social, you can allow users to add their description and also upload a profile photo while editing the profile after signing up:
Adding an about description
In order to store the description entered in the about field by a user, you need to add an about field to the user model in server/models/user.model.js:
about: { type: String, trim: true }
Then, to get the description as input from the user, you can add a multiline TextField to the EditProfile form in mern-social/client/user/EditProfile.js:
<TextField id="multiline-flexible" label="About" multiline rows="2" value={this.state.about} onChange={this.handleChange('about')} />
Finally, to show the description text added to the about field on the user profile page, you can add it to the existing profile view in mern-social/client/user/Profile.js:
<ListItem> <ListItemText primary={this.state.user.about}/> </ListItem>
With this modification to the user feature in the MERN skeleton code, users can now add and update a description about them to be displayed on their profiles.
Uploading a profile photo
Allowing a user to upload a profile photo will require that you store the uploaded image file and retrieve it on request to load in the view. For MERN Social, you need to assume that the photo files uploaded by the user will be of small sizes and demonstrate how to store these files in MongoDB for the profile photo upload feature.
There are multiple ways of implementing this upload feature considering the different file storage options:
- Server filesystem: Upload and save files to a server filesystem and store the URL to MongoDB
- External file storage: Save files to an external storage such as Amazon S3 and store the URL in MongoDB
- Store as data in MongoDB: Save files of a small size (less than 16 MB) to MongoDB as data of type Buffer
Updating the user model to store a photo in MongoDB
In order to store the uploaded profile photo directly in the database, you can update the user model to add a photo field that stores the file as data of type Buffer, along with its contentType in mern-social/server/models/user.model.js:
photo: { data: Buffer, contentType: String }
Uploading a photo from the edit form
Users should be able to upload an image file from their local files when editing the profile. You can update the EditProfile component in client/user/EditProfile.js with an upload photo option and then attach the user selected file in the form data submitted to the server.
File input with Material-UI
You can use the HTML5 file input type to let the user select an image from their local files. The file input will return the filename in the change event when the user selects a file in mern-social/client/user/EditProfile.js:
<input accept="image/*" type="file" onChange={this.handleChange('photo')} style={{display:'none'}} id="icon-button-file" />
To integrate this file input with Material-UI components, you can apply display:none to hide the input element from view, then add a Material-UI button inside the label for this file input. This way, the view displays the Material-UI button instead of the HTML5 file input element in mern-social/client/user/EditProfile.js:
<label htmlFor="icon-button-file"> <Button variant="raised" color="default" component="span"> Upload <FileUpload/> </Button> </label>
With the Button‘s component prop set to span, the Button component renders as a span element inside the label element. A click on the Upload span or label is registered by the file input with the same ID as the label, and as a result, the file select dialog is opened. Once the user selects a file, you can set it to state in the call to handleChange(…) and display the name in the view in mern-social/client/user/EditProfile.js:
<span className={classes.filename}> {this.state.photo ? this.state.photo.name : ''} </span>
Form submission with the file attached
Uploading files to the server with a form requires a multipart form submission. You can modify the EditProfile component to use the FormData API to store the form data in the format needed for encoding type multipart/form-data.
First, you need to initialize FormData in componentDidMount() in mern-social/client/user/EditProfile.js:
this.userData = new FormData()
Next, you need to update the input handleChange function to store input values for both the text fields and the file input in FormData in mern-social/client/user/EditProfile.js:
handleChange = name => event => { const value = name === 'photo' ? event.target.files[0] : event.target.value this.userData.set(name, value) this.setState({ [name]: value }) }
Then, on clicking submit, this.userData is sent with the fetch API call to update the user. As the content type of the data sent to the server is no longer ‘application/json’, you’ll also need to modify the update fetch method in api-user.js to remove Content-Type from the headers in the fetch call in mern-social/client/user/api-user.js:
const update = (params, credentials, user) => { return fetch('/api/users/' + params.userId, { method: 'PUT', headers: { 'Accept': 'application/json', 'Authorization': 'Bearer ' + credentials.t }, body: user }).then((response) => { return response.json() }).catch((e) => { console.log(e) }) }
Now if the user chooses to upload a profile photo when editing profile, the server will receive a request with the file attached along with the other field values.
Processing a request containing a file upload
On the server, to process the request to the update API that may now contain a file, you need to use the formidable npm module:
npm install --save formidable
Formidable will allow you to read the multipart form data, giving access to the fields and the file, if any. If there is a file, formidable will store it temporarily in the filesystem. You need to read it from the filesystem, using the fs module to retrieve the file type and data and store it in the photo field in the user model. The formidable code will go in the update controller in mern-social/server/controllers/user.controller.js as follows:
import formidable from 'formidable' import fs from 'fs' const update = (req, res, next) => { let form = new formidable.IncomingForm() form.keepExtensions = true form.parse(req, (err, fields, files) => { if (err) { return res.status(400).json({ error: "Photo could not be uploaded" }) } let user = req.profile user = _.extend(user, fields) user.updated = Date.now() if(files.photo){ user.photo.data = fs.readFileSync(files.photo.path) user.photo.contentType = files.photo.type } user.save((err, result) => { if (err) { return res.status(400).json({ error: errorHandler.getErrorMessage(err) }) } user.hashed_password = undefined user.salt = undefined res.json(user) }) }) }
This will store the uploaded file as data in the database. Next, you’ll need to set up file retrieval to be able to access and display the photo uploaded by the user in the frontend views.
Retrieving a profile photo
The simplest option to retrieve the file stored in the database and show it in a view is to set up a route that will fetch the data and return it as an image file to the requesting client.
Profile photo URL
You need to set up a route to the photo stored in the database for each user and also add another route that will fetch a default photo if the given user has not uploaded a profile photo in mern-social/server/routes/user.routes.js:
router.route('/api/users/photo/:userId') .get(userCtrl.photo, userCtrl.defaultPhoto) router.route('/api/users/defaultphoto') .get(userCtrl.defaultPhoto)
You need to look for the photo in the photo controller method and if found, send it in the response to the request at the photo route; otherwise, you’ll need to call next() to return the default photo in mern-social/server/controllers/user.controller.js:
const photo = (req, res, next) => { if(req.profile.photo.data){ res.set("Content-Type", req.profile.photo.contentType) return res.send(req.profile.photo.data) } next() }
The default photo is retrieved and sent from the server’s file system in mern-social/server/controllers/user.controller.js:
import profileImage from './../../client/assets/images/profile-pic.png' const defaultPhoto = (req, res) => { return res.sendFile(process.cwd()+profileImage) }
Showing a photo in a view
With the photo URL routes set up to retrieve the photo, you can simply use these in the img element’s src attribute to load the photo in the view in mern-social/client/user/Profile.js. For example, in the Profile component, you’ll get the user ID from state and use it to construct the photo URL.
const photoUrl = this.state.user._id ? `/api/users/photo/${this.state.user._id}?${new Date().getTime()}` : '/api/users/defaultphoto'
To ensure that the img element reloads in the Profile view after the photo is updated in the edit, you need to also add a time value to the photo URL to bypass the browser’s default image caching behavior.
Then, you can set the photoUrl to the Material-UI Avatar component, which renders the linked image in the view:
<Avatar src={photoUrl}/>
The updated user profile in MERN Social can now display a user uploaded profile photo and an about description:
If you found this article helpful, you can explore Shama Hoque’s Full-Stack React Projects to unleash the power of MERN stack. This book guides you through MERN stack-based web development, for creating a basic skeleton application and extending it to build four different web applications.
Angular 4 integration with Liferay DXP and Firebase
Angular and Firebase seed
A simple starter project demonstrating the basic concepts of Angular and Firebase integration.
Firebase
Firebase helps to build better Realtime Web Application and mobile apps for all the platforms to grow your business.
It provides the functionality like Realtime databases, Analytic, Cloud Messaging and Crash Reporting so we can move quickly and focus on our users. We can also test and deploy our application on Firebase Hosting With Free SSL Certificate. We can use Firebase on client-side app developers (both web & mobile)
Before installing the Firebase, First of all, we need to make sure that below mention software installed properly with appropriate version as mentioned below.
Getting Started
- Make sure you have node.js installed version 6+
- Make sure you have NPM installed version 3.9+
- Deploy using gradle or Eclipse Liferay tools
Install
Run this command in same project directory
npm install
Setting up the Project
- Create a project in the Console.
- Click to Create New Project.
- If you already have a project, click Add App from the project overview page.
- Click to “Add Firebase” to your web app.
- Note the initialization code snippet, which you will need to replace in environment.ts file.
src\main\resources\META-INF\resources\enviroments – sample code
// Initialize
// TODO: Replace with your project's customized code snippet
production: false,
firebase : {
apiKey: "<API_KEY>",
authDomain: "<PROJECT_ID>.firebaseapp.com",
databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
storageBucket: "<BUCKET>.appspot.com",
messagingSenderId: "<SENDER_ID>",
};
firebase.initializeApp(config);