This comes with a noticeable caveat: it complicates your template code. Generally, it’s considered good practice to minimize the amount of logic that happens in an html template. If you need to do many things on a single ng-click, you should consider writing (or refactoring) a method in your controller to handle them with just one method call.
Nonetheless, this odd bit of Angular syntax can be useful, even if it never makes it to production. In my case, I needed to modify $parent.someVar on click, which was (at the time) outside of the button’s controller. Ultimately, this code was refactored so that someVar could be modified from within selectTab(), but when I needed a quick and dirty implementation to demo something, chaining functions on a single ng-click got the job done.
Wait, what does $parent.someVar do? What is $parent?
$parent allows code within a child controller to access something contained within the parent scope.
someVar was contained within PageCtrl (the parent scope), but I needed to manipulate it from a button inside SectionCtrl (the child scope). Using $parent, the SectionCtrl code could “look up” into the parent and find someVar. This Stack Overflow Q&A explains $parent with more examples.
Today I learned… there are [at least] two ways to inject a service into Mocha unit tests using the Chai assertion library and Angular mocks. This is just a little thing, but I’ve seen this difference in a few unit testing tutorials and it confused me the first time I came across it.
In my project I have a service called mealsServer. No need to worry about what it does, for now we’re just testing that it gets injected successfully (in other words, exists).
Service Injection Technique #1:
Here I am declaring mealsServer as a variable and then injecting _mealsServer_ using beforeEach:
var mealsServer;
beforeEach(inject(function(_mealsServer_) {
mealsServer = _mealsServer_;
}));
The underscores are an oddity. The underscores are a little syntax trick that make it possible to use the same name for the injection as we use for the variable. In other words, if we didn’t inject _mealsServer_ wrapped in underscores, then var mealsServer would need a different name. I’m all for keeping names consistent whenever possible, so I’m glad I learned about this.
Service Injection Technique #2:
And here’s an alternative: here I am injecting the mealsServer service as part of the it block:
it('should have a working meals-server service', inject(function(mealsServer) {
expect(mealsServer).to.exist;
}));
I’m still learning the ropes of unit testing, so I’m sure there are advantages/disadvantages to each of these approaches. I’m relying a lot on this tutorial: Testing AngularJS Apps Using Karma to get me started.
Personally, I like injecting the service in the same line of code that relies upon it being there. I think this is neater and will hold up better as this file becomes longer.
For reference’s sake, here’s the complete meals-test.js file below. It’s small right now, but just getting to the point of having (any!) tests run successfully was a several hour endeavor. In this version, I am just testing that my services exist and I’m using technique #2 from above.
This tutorial is about a neat trick you can use with ng-repeat and inputs using AngularJS. This is just one tiny part of a larger AngularJS project of mine you can explore here: Chicken Breast Meals on GitHub.
Let’s say you are building a user input form that lets the user input series of items in a list, such as ingredients in a recipe. You could have the user click a link to add a new input field before typing in each ingredient, but that’s an extra (and annoying) step nowadays for users.
What you really want is a list of inputs that grows itself, offering a new blank input in response to each addition the user makes:
Infinitely-expanding list grows as the user adds to it
<h2>Ingredients</h2>
<ol class="ingredients-list">
<!-- loop through and display existing ingredients -->
<li data-ng-repeat="ingredient in formMeal.ingredients track by $index">
<textarea name="ingredientLines"
type="text"
data-ng-model="formMeal.ingredients[$index].name"
placeholder="Add ingredient"
data-ng-change="changeIngredient($index)">
</textarea>
<!-- trash can button -->
<a href="" data-ng-show="ingredient"
data-ng-click="formMeal.ingredients.splice($index,1)">
<img src="/assets/delete.png"/></a>
</li>
</ol>
When the user selects a recipe to edit in the admin page, that selected recipe is represented by an object called formMeal. Inside formMeal are properties like:
name (which is saved as a String)
yield (saved as a Number)
cookTime (another Number)
ingredients (an Array of Objects)
On the <li>
The ng-repeat directive builds the list of ingredients by creating a <li> and a <textarea> for each ingredient already found in the saved recipe data. Each ingredient has an index in the ingredients array, so we grab its name out of the array of ingredient objects like so:
formMeal.ingredients[$index].name
Immediately following the ng-repeat directive is $track by index. This bit of code is easy to overlook but it’s very important: it’s what keeps the user’s current textarea in focus while the user edits it. Without $track by index, the app kicks the user out of that text box after the first typed letter. (Ask me how much fun I had debugging this lose-focus problem…)
In the <textarea>
Each ingredient is represented by a <textarea>, and each one has its own ng-model directive pairing it with that particular index in the array.
data-ng-model="formMeal.ingredients[$index].name"
This lets us edit an existing ingredient anywhere in the list by that ingredient’s index. Since ingredients is an array, we need to pass it the index of the ingredient we’re editing via the <textarea>. (You can read more about ng-repeat and $index here in the Angular documentation.) This placeholder part is straightforward:
placeholder="Add ingredient"
This is what puts the default text into each <textarea> when the user hasn’t entered anything yet. It’s just a nice UX touch.
Finally, we have an ng-change directive. You can read more about ng-change here, basically all it does is call the method (or do the thing) you tell it to do any time there’s a change in the <textarea> it’s associated with.
data-ng-change="changeIngredient($index)"
A change to the <textarea> (ie: user typing) causes the method changeIngredient() to run with each change.
We already saw that whenever the user updates text inside one of those <textarea> regions, this method gets called. (If you were to put a console log inside changeIngredient(), you would see it called every time you typed a letter into the textarea.)
changeIngredient(index) checks the indexthat’s been passed in:
if that index is at the end of the array (ie: its index number is one less than the array’s length), then we are editing the last ingredient in the list and we need to push an empty ingredient (”) to the ingredients array to make the empty box appear at the end
if that index is not at the end of the array, we just update whatever’s at this index since it’s an ingredient that already exists. This is why you don’t see an empty box get added to the end of the list if you’re editing a field that’s not at the end.
It’s important to observe that this method works by checking that the user is editing the last index (which is always the empty <textarea>). This is how we don’t spawn new, empty textareas for editing earlier ingredients in the list.
When you initialize your data or your app, you’ll need to include something like:
$scope.formMeal.ingredients = [''];
or
$scope.ingredients.push('');
so that the ingredients list has an empty one in it by default. Your implementation needs will vary, of course, but hopefully this little guide gave you enough of a start to build this “infinity list” into your own AngularJS form!
Don’t miss the Plunker demo of a simplified version of this feature that you can play with and adapt to your own project.
The time had come at last to deploy Chicken Breast Meals to an external server so that it could be enjoyed by a larger audience. I chose Heroku because it’s a friendly “my-first-deployment” kind of technology that handles a lot of the nitty-gritty details that Amazon Web Services and others leave up to you. For a simple MEAN stack app deployment, Heroku has been sufficient for my needs so far.
However, despite Heroku’s fairly straightforwardness, I still encountered a number of problems along the way. This post is about all the steps I took to get my MEAN app from GitHub to Heroku.
For clarity’s sake, these are the technologies I used in the project and in my development environment:
And unlike Heroku’s tutorial, this tutorial assumes you already have a git repo on your hard drive and it’s already full of your project files.
Step 1: Open a Heroku Account and Add a New App to your Dashboard
Hopefully, Heroku’s site can walk you through this sufficiently well.
Once you have an account, add a new app via the dashboard. On the current version of the Heroku dashboard, adding a new app is done with the + button.
Heroku’s “add new app” button is easy to miss.
Step 2: Get the Heroku Toolbelt
Heroku’s own site will tell you to do this, too. Go to https://toolbelt.heroku.com/ and install the toolbelt appropriate to your environment. The toolbelt allows you to use the heroku command from your shell.
Step 3: Enter your credentials
Heroku’s toolbelt site walks you through these steps, too, but just in case you’re following along here:
$ heroku login
Enter your Heroku credentials.
Email: myaddress@gmail.com
Password (typing will be hidden)
Authentication successful.
You may get a response like this:
Your Heroku account does not have a public ssh key uploaded.
Could not find an existing public key at ~/.ssh/id_rsa.pub
Would you like to generate one? [Yn] Y
Generating new SSH public key.
Uploading SSH public key /home/jim/.ssh/id_rsa.pub... done
If this happens, choose Y and continue.
Since you already made a new Heroku app in step 1 you should skip the “heroku create” step.
Step 4: Add your Heroku app as a remote to your existing git clone’d repo
If you’re like me and you already have your git repo as a folder on your hard drive, you don’t need to make a new repo, you just need to add Heroku as a remote for it.
Navigate to your app’s root folder with cd and then use heroku git:remote -a yourappnamehere to add your remote.
If you follow these steps on Heroku’s own site, it will suggest using git init here (which you shouldn’t do since you already have a repo set up) and it will fill in your chosen app name where mine says chickenbreastmeals.
These are the steps I used to add my Heroku app as a remote to my existing GitHub repo:
$ cd /your/project/location
$ heroku git:remote -a chickenbreastmeals
Step 5: Attempt to push to Heroku – Permission Denied!
Pushing your repo to Heroku is done with just one line:
$ git push heroku master
…But if you’re like I was originally, you’ll get a permission denied (publickey) error.
(If you don’t get this error, hooray – you’re probably good to go. Or you’re stuck on a new problem that I didn’t encounter. Good luck.)
$ git push heroku master
Permission denied (publickey).
fatal: Could not read from remote repository.
Oh, snap. I Googled the “git push heroku master permission denied (publickey)” error and landed on this helpful Stack Overflow question. The first reply suggested a series of steps starting with heroku keys: add ~/.ssh/id_rsa.pub
heroku keys:add ~/.ssh/id_rsa.pub // or just heroku keys:add and it will prompt you to pick one of your keys
Alas, in my case, this didn’t work. Here’s what I got:
Uploading SSH public key c:/Users/Mandi/.ssh/id_rsa.pub... failed! Could not upload SSH public key: key file 'c:/Users/Mandi/.ssh/id_rsa.pub' does not exist
Well, that’s just super: I didn’t have an id_rsa.pub file yet. I needed to generate a new set of SSH keys, as detailed in my next step.
Step 6: Generate SSH keys
Fortunately, GitHub has an excellent guide on generating ssh keys, which will get you most of the way there. I encountered some problems along the way, which I’ve explained in this section.
The first step in GitHub’s instructions failed for me, of course, since I had no SSH keys.
All I got was:
ls -al ~/.ssh
total 7
drwxr-xr-x 1 Mandi Administ 0 Nov 10 16:04 .
drwxr-xr-x 48 Mandi Administ 12288 Nov 10 16:04 ..
-rw-r--r-- 1 Mandi Administ 405 Nov 10 16:04 known_hosts
If you also have no SSH keys (files with names like id_dsa.pub, id_ecdsa.pub, id_rsa.pub, etc) you’ll need to move right along to GitHub’s second step and generate a new SSH key:
ssh-keygen -t rsa -C "your_email@example.com"# Creates a new ssh key, using the provided email as a label# Generating public/private rsa key pair.# Enter file in which to save the key (/c/Users/you/.ssh/id_rsa): [Press enter]
Just press enter when it prompts for a file location – you want the default. You’ll enter a passphrase twice (remember what you type here!):
Enter passphrase (empty for no passphrase): [Type a passphrase]# Enter same passphrase again: [Type passphrase again]
And then you’ll get something like this, telling you where your identification and public key were saved as well as your key fingerprint and a random ascii art image for your viewing pleasure.
Your identification has been saved in /c/Users/you/.ssh/id_rsa.# Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.# The key fingerprint is:# 01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com
Caveat: I’m on Windows 7 64-bit using msysgit bash, so your experience may differ from mine. Responses to this answer suggest the problem is not unique to the Windows environment.
Anyway, now that the authentication agent is running I can properly complete the ssh-add step:
$ ssh-add ~/.ssh/id_rsa
Enter passphrase for /c/Users/Mandi/.ssh/id_rsa:
Identity added: /c/Users/Mandi/.ssh/id_rsa (/c/Users/Mandi/.ssh/id_rsa)
Phew! Onwards to the GitHub step.
Step 7: Add new key to GitHub account
Following GitHub guide to generating SSH keys still, the next step is to copy the contents of your id_rsa.pub file to your clipboard. This is easily done with clip, like so:
clip < ~/.ssh/id_rsa.pub
Go to GitHub and click the “Settings” gear icon in the upper right.
Click “Add SSH Key”
Give your key a title (I named mine after my computer)
Paste the contents of clipboard into the large field
The authenticity of host 'github.com (207.97.227.239)' can't be established.# RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.# Are you sure you want to continue connecting (yes/no)?
Type “yes” and if everything goes okay, you’ll get:
Hi username! You've successfully authenticated, but GitHub does not provide shell access.
Oh, yeah – I just remembered what I was trying to do before I went down the SSH error rabbithole: I was trying to push my GitHub repo to Heroku!
First, add that same .ssh file to with heroku:keys add
$ heroku keys:add ~/.ssh/id_rsa.pub
Uploading SSH public key c:/Users/Mandi/.ssh/id_rsa.pub... done
Phew, success! Now I was able to run heroku push.
$ git push heroku master
Warning: Permanently added the RSA host key for IP address '50.19.85.132' to the
list of known hosts.
Initializing repository, done.
Counting objects: 801, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (705/705), done.
Writing objects: 100% (801/801), 994.30 KiB | 519.00 KiB/s, done.
Total 801 (delta 419), reused 0 (delta 0)
This message was followed by several screens depicting the installation of node and my project’s node packages. Heroku handles this setup automatically, and in my case, the installation processes went off without a hitch.
Step 10: Check out your app on Heroku – Application Error, hooray!
I’m glad I didn’t celebrate too early, because my Heroku app looks like this:
Application Error
An error occurred in the application and your page could not be served. Please try again in a few moments.
If you are the application owner, check your logs for details.
And no, it doesn’t go away in a few moments.
Step 11: Using MongoDB? Install MongoLab on your Heroku app
If you’ve ever tried to run your app locally while forgetting to fire up your MongoDB first, then you’ve probably seen your build process fail due to your database not being up and running.
There’s really no way to know that a not-running database is the cause of the application error screen, but I’ll spoil the surprise for you and tell you that in this case, that’s exactly what it was. If your Heroku-hosted MEAN app is using a MongoDB then you need to install an add-on called MongoLab.
Go to your app’s dashboard and click Get more addons…
If your Heroku-hosted MEAN stack app requires MongoDB, add MongoLab as a free add-on.
The addons page looks different every time I come in here, but the MongoLab icon hasn’t changed:
Click the icon to learn more about MongoLab, including its pricing structure and features. You will have to enter a credit card number to enable MongoLabs, but sandbox (which is what you’re using here) will be free. (I think this is super annoying, BTW. If it’s free, it shouldn’t require a credit card to use. I’ve never actually been charged by Heroku or MongoLab.)
To install, head back over to your Command Line/Terminal window and enter:
$ heroku addons:add mongolab
You’ll get this sort of response:
Adding mongolab on chickenbreastmeals... done, v4 (free)
Welcome to MongoLab. Your new subscription is being created and will be available shortly. Please consult the MongoLab Add-on Admin UI to check on its progress.
Use `heroku addons:docs mongolab` to view documentation.
IMPORTANT SIDE NOTE: My server.js file is already configured to expect MONGOLAB_URI. I’ve provided my server.js code here in case you need to do the same to your server file:
'use strict';
var express = require('express');
var bodyparser = require('body-parser');
var mongoose = require('mongoose');
var http = require('http');
var app = express();
mongoose.connect(process.env.MONGOLAB_URI || 'mongodb://localhost/meals-development');
app.use(express.static(__dirname + '/build'));
app.use(bodyparser.json({limit:'50mb'}));
app.use(bodyparser.urlencoded({limit: '50mb', extended: true}));
require('./routes/admin-routes')(app);
var server = http.createServer(app);
var port = process.env.PORT || 3000;
app.listen(port, function() {
console.log("Listening on " + port);
});
From here, I attempted to view my app again. This time I got:
Le sigh. But this is progress – I don’t get an Application Error anymore, so the database installation made a difference. Checking the Chrome console, my Heroku app is generating this error:
Failed to load resource: the server responded with a status of 404 (Not Found)
Step 12: Giving Heroku access to my Build folder
I scratched my head a bit over this “cannot GET/” problem and Googled it, which led me to this Stack Overflow question, Heroku Cannot Get.
Just like the original asker, my .gitignore contained a line for my build folder, which meant Heroku had nothing to serve as it had no access to my “compiled” project.
I removed the build line from .gitignore, and pushed the updated .gitignore file and build/ folder to both GitHub and Heroku like so:
$ git push origin master
$ git push heroku master
Step 13: IT’S ALIVE!
At last, I see my app when I visit chickenbreastmeals.com. It’s lacking the database entries from my local development environment, so I’ll update this post once I get those in.
Hope this guide helped you deploy your MongoDB / AngularJS / Express / node.js app to Heroku! There’s only about a thousand things that can go wrong between point A and point Z, so if something in this guide doesn’t work from you it’s probably a difference in our environments or an error on my part – please leave a comment letting me know (and start Googling – good luck!).
Addendum
Did you use Gulp to build your app and automate some build processes? If so, your app probably doesn’t look so hot on Heroku right now. This is because Heroku doesn’t know which of your Gulp tasks needs to run after all your Node packages are installed. Let’s fix that!
Dev Dependencies
First off, it’s important to mention that if you installed any packages as a dev dependency (like you probably did with Gulp), Heroku will not include them in your build by default. This is because Heroku assumes you’re deploying a production build, and will run npm install –production, which ignores dev dependencies. There’s two ways to fix this:
1. In your app’s package.json, move Gulp and all related packages from the “devDependencies” list into the “dependencies” list. This is a pain and I do not recommend it.
2. Run the following terminal command to tell Heroku that it should use the standard npm install command:
heroku config:set NPM_CONFIG_PRODUCTION=false
Postinstall Scripts
With that taken care of, we need to tell Heroku what commands we want to run after all of our packages are downloaded and installed. Luckily Heroku has made this easy! Just add a “scripts” block to your package.json file, like so:
The “start” script tells Heroku how to start my server: run node with the file server.js. The “postinstall” script is actually three commands separated by &&, ran in sequence: bower install, gulp build-libs, and gulp build. In my gulpfile.js, the build-libs task concatenates and minifies several libraries like Angular and Bootstrap. This task relies on those libraries being in the bower_components folder, which is why I run bower install first.
Troubleshooting
If any of the steps in this article don’t work, there’s a couple things you can try. The most helpful thing to know is that you can run common Linux shell commands on your Heroku container with heroku run. Like this:
heroku run ls -la
This is just like running ls -la on your own system, and will list all of the files in your Heroku deployment’s main directory. This is how I figured out that I need to run bower install: there was no bower_components folder in my deployment!