Part 1: Angular Basics
Angular is a Javascript framework for creating web applications - it runs entirely in the web browser, interacting with the server through REST API calls. For the purposes of most applications, there are three main types of components which will be heavily used. These components are grouped into what angular terms a "module", and a module will be associated with an HTML page.
The three major components we'll use in this tutorial are:
Controllers
A controller, strongly paired with a particular HTML template, is responsible for providing data and functionality to that template. It will interact with services to perform actions. For example, a User Profile Controller would receive the user's profile from a service which talks to the server, and provide functions to handle the user actions such as saving their updated profile, or changing their password. These functions would all talk to services to do their work, and handle the responses from those service calls to display success or failure messages to the user.
Services
A service, implemented as what Angular calls a "factory", provides functionality which is shared across the application. Services are where the interaction with the server would be implemented, along with other shared functionality like setting page titles, interacting with local storage, and so on.
Directives
Directives are responsible for any actions which affect the DOM (in layman's terms, that means anything that creates or updates the actual HTML of the page). In normal usage, we'll use directives in two common ways - using pre-existing directives to provide simple control over the HTML, and making our own directives to provide re-usable components for our application.
Pre-existing directives allow for such things as repeating a set of elements based on some data (ng-repeat), or selectively showing elements based on a boolean test (ng-if or ng-show), and many other simple tasks. We'll talk about these when we create a template shortly.
Creating our own directives can be for performing similar tasks where pre-existing directives don't quite suffice, or for creating small re-usable components of our application - like a specific table with a set of functions that we can bind to a specialised piece of data. A directive is created as a controller-template pair, and can be included into normal templates using special attributes or elements in that template. At the complex end, a directive can end up almost as complex as a full page/controller pair - however they should not generally communicate outside themselves, instead providing attributes to allow the templates they are included in to specify the data they will work with - leaving the responsibility for interacting with services (and, by extension, the server) to the top-level controllers they are included by. We'll look at how this is accomplished later in this tutorial.
Giving things a go - our first Controller!
To get started, we need to create a simple application. In the solution opened in the introduction, you'll find the Angular code in wwwroot/js/src/ - at this time, you should only have app.js and routes.js in there. Open app.js, and you'll see the following:
(function () { 'use strict'; angular.module("app", ['ui.router']); })();
All this does is create a new Angular module named "app", and declare that it depends on the "ui.router" module (which we'll talk about later).
Set up a route
To have a page, we need to set up a route. We don't have any routes defined, so open up routes.js and add a route in where the comment indicates - we'll talk about routing much more later, so you can just paste this in for now:
$stateProvider.state('home', { url: '/', // URL to match templateUrl: 'js/src/home/home.html', // Template to use controller: 'Home as home' // Controller ("Home") and its // alias for referencing it // inside the template ("home") });
Add your template and controller files
Create a folder named "home" under the wwwroot/js/src/ folder, and create an HTML file called home.html and a Javascript file named Home.controller.js.
In your home.html, delete ALL of the content (this is a template so we don't want all the HTML headers etc), and put some simple HTML like the following:
<p>Hello! Welcome to Angular!</p>
Now, in your Home.controller.js, you need to create your controller. Here is the boilerplate for an empty controller I like to start with, so paste this in then we'll go from there - have a read of the comments to get a feel for what's going on here:
(function () { 'use strict'; // Registers this controller with the Angular module "app" angular.module('app').controller('Home',Home); // Inject the services and data this controller depends on - this is // how we wire services to controllers. Home.$inject = []; // Define the controller - the parameters passed into this function // will be auto-provided by angular based on the $inject line above. function Home() { var vm = this; // "Public" properties // "Public" functions // "Private" properties // Initialisation // "Public" function definitions // "Private" function definitions // Event Subscriptions } })();
This doesn't actually do anything at all yet, it's just an empty controller! However, it's good for things to run, so let's finish wiring things up so we can see it running. The last thing that needs to be done before we can run is to add the declarations to the ASP.NET MVC view to bootstrap our Angular app. To do that open up Views/Home/Index.cshtml and replace the main <div> as follows:
<div ng-app="app"> <ui-view></ui-view> </div>
The ng-app="app" tells angular to bootstrap itself using the module 'app' (this is the name we gave our module in app.js), and <ui-view></ui-view> tells the router this is where our page templates will go.
Now, debug your application and check that when the web browser opens, you see the HTML you put into your home.html template instead of the Hello World message.
Doing a bit of data binding!
To demonstrate a bit of data binding, which is how the controller gives data to the template, let's add an array of strings to the controller. In Home.controller.js, add a public property named "list" and define a few strings in an array, something like this:
vm.list = ["Example String 1", "A second Thing!"];
Now, in your template, let's wire something to that. Add an "unordered list" (<ul>) with a list item inside (<li>), then on that list item, add the "ng-repeat" directive:
<ul> <li ng-repeat="item in home.list">{{item}}</li> <ul>
Here, "home" is the name we gave to our controller in the route definition, "list" is the name of the property on our controller, and "item" is an arbitrary name to insert each item in the array into so we can read it out inside the repeated element. Angular will duplicate the tag on which ng-repeat is specified (and all tags contained by it) for each item in the array bound to, and replace the content in curly braces with the specified variable.
Run this up, and you should now see a list on your page, repleat with the items you put into your controller. Still pretty boring though, so let's make it interactive. In your template, add a link with an ng-click attribute - this binds a function on the controller to a click action in the web browser:
<a href="#" ng-click="home.addItem('horse')">Add a horse to the list!</a>
Again, "home" is the alias we've given our controller in the route definition, and "addItem" references a function on that controller. Now we need to define that function, which has two parts. First, in the "Public functions" section of the controller, add:
vm.addItem = addItem;
... then, in the "Public function definitions" section, add the following function definition:
function addItem(itemToAdd) { // Add the length of the array to the end of the string - Angular // de-duplicates thearray when displaying so we'll only see one // entry if we keep adding the same value. vm.list.push(itemToAdd + (vm.list.length + 1)); }
Run up your site again, and now each time you click on the "Add a horse" link, you should see an extra horse appear by magic in the list! You'll note that we didn't have to do any special wiring to get the view to update after changing our list - Angular watches the data it has been bound to and will automatically update the page whenever things change!
This works in reverse too, so let's add a text box so we can add things other than horses to our list. In your controller, add a new public property called newItem, and set it to "", then update the addItem function as follows:
function addItem() { // We don't want to add empty entries - we will change this to // show an error later. if (vm.newItem.length == 0) return; // If we're still here, add whatever to the list, then clear // the newItem property. vm.list.push(vm.newItem); vm.newItem = ""; }
Now, replace the add link in your template with a new text box bound to the newItem property, and a new add link which doesn't pass a parameter any more:
<input type="text" ng-model="home.newItem" /> <a href="#" ng-click="home.addItem()">Add!</a>
Here, we're using the ng-model directive to bind the value of this text box to the controller property newItem, so now, as the user changes the text in the textbox, the value of that property will be updated - and vice-versa. Run this up, enter an item, and click the Add link to see it in action. If everything is working right, the value should be added, and the textbox should be emptied, each time you click add.
As a last exercise, add a function on your controller to remove a specific item from the list, and update your template to call this function somehow. As a hint, you can remove a specific item from an array in javascript like this:
var exampleArray = ["horse", "sheep", "cow"]; var itemToRemove = "sheep"; // Check where the item to remove is located in the array. var ind = exampleArray.indexOf(itemToRemove); // If we have found the item to remove, splice it out. if (ind > -1) exampleArray.splice(ind, 1);
... and remember that you can include things inside an ng-repeat!
You can check your solution against this branch which shows a final working solution for all the steps discussed in Part 1.
Ready to continue?
Onwards to Part 2