AngularJS - Walkthrough Setting up Yeoman and its generatorangular Assuming that you have installed node.js, you can install yeoman as follows: npm install -g yo npm install -g generator-angular Now, we are ready to start working with our application. I will assume that you created a new directory, called rottenpotatoes, to hold the application code. Let us create the skeleton of application using the yeoman angular generator. The latter can be done with the following command: yo angular --coffee Please note that we are using the option --coffee because we want to use coffeescript. Sure enough, if we omit this option, yeoman s plugin will generate javascript code. Additionally, yo uses the name of the current directory as the name for the application. That s why you will see some references to rottenpotatoesapp. In my case, I was required to clean up npm cache before lauching code generation with yeoman. The command to do this is the following: npm cache clean Yeoman s code generator will ask you to customize your project. Let s keep the default configuration. However, since the code generator install the support to SASS (i.e., a -simplified- language for specifying CSS stylesheets) you will be required to install Ruby s compass gem. Please be patient because the setup will take a little while! You can run the seed application, by means of the following command:
grunt serve Feature: Listing movies Let s add our first application feature: support to listing movies. Similar to a Rails application, that requires adding four elements: a view, a controller, a model and a route. Please note that the notion of model is blured in the context of front-end applications, as there is usually no persistence support attached directly to the application. The above will become clear as we proceed with the implemention of the Listing movies feature. Let s add the following: 1. VIEW: An empty file with the name app/views/index.html. Copy the following snippet to the view: html <h1>all movies</h1> {{movies}} 2. CONTROLLER: A file with the name app/scripts/controllers/movies_controllers.coffee. Copy the following snippet to the controller: 'use strict' app = angular.module('rottenpotatoesapp') app.controller 'MoviesIndexController', ($scope) -> $scope.movies = [ { id: 0, title: 'Aladdin', rating: 'G', release_date: new Date() } { id: 1, title: 'Amelie', rating: 'PG', release_date: new Date() } ] 3. MODEL: A file with the name app/scripts/services/movies_services.coffee. Let s keep this file empty for the moment. 4. ROUTE: We have to manually specify it within the file app/scripts/app.coffee. Copy the following snipped, just before the line containing the statement.otherwise :.when '/movies', templateurl: 'views/movies/index.html' controller: 'MoviesIndexController' Finally, we have to change the file app/index.html so as to load all the above files whenever we lauch the application. To this end, you have to add the following lines
by the end of the file: <script src="scripts/controllers/movies_controllers.js"></script> <script src="scripts/controllers/movies_services.js"></script> One important thing to note is the fact that both the Controller and the view share a reference to the list of movies. In the Controller side, the list of movies is refered by $scope.movies. In the View side, the list of movies is rendered via the statement {{movies}}. Formatting the movies table Let s now take some time to format the list of movies. To this end, you can copy the following HTML code in the file views/movies/index.html : <h2>listing movies</h2> <table class="table table-stripped table-bordered"> <thead> <tr> <th>title</th> <th>rating</th> <th>release code</th> <th>more Info</th> </tr> </thead> <tbody> <tr ng-repeat="movie in movies"> <td>{{movie.title}}</td> <td>{{movie.rating}}</td> <td>{{movie.release_date}}</td> <td><a href="">more about {{movie.title}}</a></td> </tr> </tbody> </table> <a href="#/movies/new">add a new movie</a> Of particular interest is the AngularJS directive ng-repeat that allow us to use an iterator to process the elements in a collection. In our example, we are using movie in movies to iterate over the list of movies, where movies is once again the variable declared in the context of MoviesIndexController. Note that for each movie, we use a <td></td> element to render each of its properties (e.g., {{movie.title}}, etc.). At the bottom of the web page, there is an <a></a> element that let s us redirect the application to the web page where we are supposed to provide a form to enter
the information of a new movie. Feature: Adding a New Movie We will repeat a process that is similar to the one above: 1. VIEW: Add a file with the name app/views/new.html and copy the following code: <h1>new Movie</h1> <form> <div> <label>title</label> <input type="text" ng-model="title"/> </div> <div> <label>rating</label> <select ng-model="rating" ng-options="r for r in ['G', 'PG', 'PG -13', 'R']"></select> </div> <div> <label>release date</label> <input type="date" ng-model="release_date"/> </div> <button ng-click="addmovie()">save</button> </form> 2. CONTROLLER: You can add another file for the new controller. However, it is possible to add the code for the controller in the existing file (i.e., movies_controllers.js ). If you opt for adding a new file, then do not forget to modify index.html to include the new file. The code for the controller will be introduced a bit later. 3. ROUTE: Add the information about the route to the file scripts/app.coffee, as follows:.when '/movies/new', templateurl: 'views/movies/new.html' controller: 'MoviesNewController' In this case, the view pushes information back to the controller. To this end, the input elements are annotated with the directive ng-model. For instance, the first text field captures the information of the movie title via the annotation ng-model="title". In the controller side, we will access to that information via $scope.title.
The directive ng-options="r for r in ['G', 'PG', 'PG-13', 'R'] allows us to specify a set of options on the drop-down menu that serves to specify the rating of a movie. Finally, the directive ng-click="addmovie()" serves a specifying that the method $scope.addmovie() on the controller will be executed when the button is clicked. Movies Service The information held by controllers is transient and is lost whenever we change from one web page to another. If we use an analogy with Rails, at the beginning of every controller action, we have to query the Model with the information provided via the hash params, and only afterwards we would be able to process a request. The persistence of information is therefore delegated to the model. In Angular, we will delegate a sort of persistence to services. Note that there a several classes of services and I will introduce first a very simple one, which will allow us to hold all the information in main memory. Now, let s take a look at the code (please copy the snippet into the file scripts/services/movies_services.coffee ). 'use strict' app = angular.module('rottenpotatoesapp') app.service 'MoviesService', -> movies = [] all: -> movies add: (movie) -> movies.push(movie) As you can see, the service uses an array (i.e., movies ) on which will store all the movies. The service also provides two methods: all() that returns all the movies, and add() that adds a new movie at the end of the array. Now we have to wire up the service with the controllers that we have defined. First, let s change the implementation of MoviesIndexController (in the file scripts/controller/movies_controllers.coffee ). 'use strict' app = angular.module('rottenpotatoesapp') app.controller 'MoviesIndexController', ['$scope', 'MoviesService', ($scope, M oviesservice) -> $scope.movies = MoviesService.all() ] Note the changes in the syntax. First, all the declaration of the controller is now
Note the changes in the syntax. First, all the declaration of the controller is now packaged as an array. Within this array, we can find a list of strings corresponding with the components we expect that the angularjs dependency injector will provide (namely, $scope and MoviesService ). Note that we are repeating the same list now as the list of parameters of the anonymous function that defines the behavior of the controller. Finally, you can see that now we replaced the literal array that we were using for initializing $scope.movies. The same variable is now set to the value returned by MoviesService.all(). Let us now introduce the implementation of MoviesNewController. Note that I decided to put both of the controllers in the same file (but you can certainly separate them). Copy the following code at the end of the file scripts/controllers/movies_controllers.coffee. app.controller 'MoviesNewController', ['$scope', '$location', 'MoviesService', ($scope, $location, MoviesService) -> $scope.addmovie = -> MoviesService.add {title: $scope.title, rating: $scope.rating, release_da te: $scope.release_date} $location.path '/movies' ] As you can see, this controller also relies on MoviesService. This service as well as the references to $scope and $location are by dependency injection. This controller provides only the implementation to the method addmovie() which is launched by clicking the Save button in the new.html view. Please note that all the information captured by the form is available via $scope related variables (e.g., $scope.title, etc.). The last addition is the call $location.path '/movies' that redirects the control to the path /movies.