Why you're getting that pesky "can't bind to" error and how to solve it in all its variations.
Here’s how to solve the “can’t bind to” problem, plus why you’re getting it, how to solve all of its variations, and how to prevent creating this problem for yourself in your Angular projects.
The fix for this message is pretty simple—just add this line to the top of your code file and the problem disappears:
import { FormsModule } from '@angular/forms';
Don’t feel bad when you get this message—the “Can’t bind to” message is one of the most common errors that Angular developers bump into (especially, of course, developers new to Angular). A quick search on Google turns up over 120 hits (and that’s after omitting all the “very similar” entries). The problem is that you’ll constantly be bumping into variations on this problem in two different environments: Angular and JavaScript.
There Are More Like This Waiting for You
All those variations are easy to recognize because they all fall into the format “Can’t bind to <some property>
since it isn’t a known property of <component>
”. And the answer is always the same: You add an Import statement. Unfortunately, the names following “import” and “from” change, depending on the message … and not in an obvious way.
This is all fallout from the move in Angular 2 to segment Angular into multiple modules that you can load on an “as needed” basis. The benefit is that you don’t have to load all of Angular when your component only needs part of it. The downside is that you have to use import statements to load the parts your component needs. The only reason that the ngModel error is so common is because ngModel is (probably) the most frequently used directive in Angular and, as a result, often the first place that developers run into this problem.
But it’s cruel to single out the Angular framework. You’ll have this issue with any JavaScript library you might use in your project. There are actually two systems in play here: importing Angular NgModules and importing JavaScript modules. They’re very similar, though: Both Angular’s NgModules and JavaScript’s modules are packages of code that, in the eyes of the developer who put together the package, hang together in some way. Typically, if you use one of the items in a module then you’ll probably use others in the same module.
Building the Right Angular Import Statement
And—still, “similarly”—you don’t need to distinguish between the NgModule and JavaScript systems to get the modules you need: You use the JavaScript import statement for both. Here are two import statements, one importing FormsModule from the Angular forms module (which is what you need for ngModel) and one importing Customer from a Types.js file in a subfolder with the path resources/SalesOrderApp (a subfolder of the folder containing the file with these import statements, that is):
import { FormsModule } from '@angular/forms';
import { Customer } from './resources/SalesOrderApp/Types';
In the import statement, what’s special about the NgModules is in the “from” part of the import statement: Angular’s NgModules begin with @angular. The @angular name points into your Node.js modules wherever you’ve loaded them—either globally on your computer or locally in your project.
But that just leads to the main question: How do you know what to import and from where? How would you know to use FormsModule and @angular/forms to get ngModel?
Answering that question is easy, though. First, go to the Angular API reference. That page gives you a list of all the Angular items you might want to import. Clicking on ngModel in that list will take you to ngModel’s reference page. At the top of the page, you’ll find the library you need to import from (@angular/forms, in this case). Under the NgModule heading on that page, you’ll find the name of the item you need to import (FormsModule, in this case). Put that together and you get the import statement you need:
import {FormsModule} from ‘@angular/forms’
Building the Right JavaScript Import Statement
As you saw, when using JavaScript libraries, your import statement will use, by default, a file path pointing to a JavaScript file somewhere in your project (though the .js file extension is omitted from the path). You’ll need to refer to that JavaScript library’s documentation to determine the name of the item you want to import and pay attention to where you installed the library in your project to get the path to it.
If your JavaScript library files are deeply nested, those paths can get to be “cumbersomely long.” You can shorten those paths up by adding a paths section to your tsconfig file to specify @-style names that refer to folder path names in your project. You can then use those @-style names in place of paths in your import statements. You only have to make one change: Rather than using a relative path from the file with the import statement, you use a relative path from your application’s root when defining the @-style name.
In my previous example, I used this import statement to reference a JavaScript file:
import { Customer } from './classes/SalesOrderTypes';
Let’s assume that the file with this import statement is in the project’s app folder—that would put the SalesOrderTypes file in a folder with the path app/resources/SalesOrderApp/Types from my project’s root. To shorten that up in your component, you can add entries like this to your tsconfig file (this one defines the name @soTypes as the path to the project’s app/resources/SalesOrderApp/Types folder):
"paths": {
"@soTypes/*": ["app/resources/SalesOrderApp/Types *"],
}
And with that in place, you could now use this import statement in any of your components to grab that JavaScript file:
import { Customer } from '@soTypes/SalesOrderTypes';
Not only does that make it considerably easier to write your JavaScript import statements, it positions you to change your project’s folder structure without having to worry about breaking your components—you just have to update the paths section of your tsconfig file. You’ve now loosely coupled your code to your libraries and centralized the management of their paths. This is, I think, an obviously good thing.