BurgerPedia: A Complete Laravel 5 and AngularJS Tutorial with Bootstrap to Make it Pretty – Part 10

in AJAX/HTML/CSS/jQuery/Javascript/Laravel/PHP/Tutorials & Samples/Web Development

Part 10 – AngularJS Modules Controllers and Templates

At this point, our AngularJS application and all of its directory setup is complete. We have also created the Burgerpedia module file along with the 2 template files and 2 controller files needed to make the application work. In the end, our application consists of the following 5 files

File location File type
app.module.js public/app/app.module.js Module file
hamburgers.controller.js public/app/hamburgers/ hamburgers.controller.js Controller file
hamburgers.template.htm public/app/hamburgers/ hamburgers.template.htm Template file
hamburger.controller.js public/app/hamburger/ hamburger.controller.js Controller file
hamburger.template.htm public/app/hamburger/ hamburger.template.htm Template file

Now, we are ready to analyze all the files needed to make our frontend work.

AngularJS Modules

The file app.module.js contains our core BurgerPedia module. The contents of the file is as follows:

 
// define the 'Burgerpedia' module
// also include ngRoute for all our routing needs
var BurgerPedia = angular.module('BurgerPedia', ['ngRoute']);

 // define our canstant for the API
BurgerPedia.constant('constants', {
		API_URL: 'http://localhost/burgerpedia/public/api/'
	});
	
// configure our routes
BurgerPedia.config(function($routeProvider) {
	$routeProvider
		// route for the hamburgers page
		.when('/', {
			templateUrl : 'app/hamburgers/hamburgers.template.htm',
			controller  : 'hamburgersController'
		})

		// route for a single hamburger 
		.when('/hamburger/:hamburgerID', {
			templateUrl : 'app/hamburger/hamburger.template.htm',
			controller  : 'hamburgerController'
		})

		// default route
		.otherwise({
               redirectTo: '/'
        });
		
			
});

The first thing we do in this file is define a module called “BurgerPedia” and we also define that it needs the ngRoute module. The ngRoute module helps your application become a Single Page Application by routing your application to different pages without reloading the entire application. The ngRoute module is used to help you with no page reloading.

ngRoute gives us access to the $routeProvider. $routeProvider is the key service that sets the configuration of URLs and mappings. It always maps the routes to 2 things:

  • the corresponding ng-template,
  • the corresponding controller

All of this routing setup is done inside BurgerPedia.config() using $routeProvider. With $routeProvider, you can do basic logic that translates to the following syntax in English: “when you see this route, use this template file and load this controller”. Thanks to the $routeProvider, we are able to create the 2 URL mappings that we need. Here is how everything lines up:

URL Associated template Associated controller
‘/’ app/hamburgers/hamburgers.template.htm hamburgersController
‘/hamburger/:hamburgerID’ app/hamburger/hamburger.template.htm hamburgerController

In the case of the /hamburger/ route, we also pass in the parameter hambergerID. That is why we have ‘:hamburgerID’. The ‘:’ lets AngularJS know that hamburgerID is a parameter that is being passed. Once Angular knows that it is a parameter, it has a way of extracting the parameter data.

On top of defining our two main routes, we also define the default route, which simply redirects us to ‘/’. This is the default behavior when any unknown route is passed tour application.

Finally, we define a constant for the URL of the backend API that provides our data inside BurgerPedia.constant(). I setup the constants as a JSON array so that I can easily add multiple constants easily.

The Hamburgers Controller and Template

To understand controllers and templates, let’s first look at the contents of the app/hamburgers folder. Here is the contents of the hamburgers.controller.js file

// create the controller and inject the Angular $scope
BurgerPedia.controller('hamburgersController', function hamburgersController($scope, $http, $location, constants) {
	// set our current page for pagination purposes
	 $scope.currentPage=1;
	 $scope.lastPage=1;
	 $scope.loadMoreText='Load More Burgers...';
	
	//retrieve hamburgers listing from API
	$http.get(constants.API_URL + "hamburgers", {params: { page: $scope.currentPage }})
		.success(function(response) {
			$scope.hamburgers = response.data;
			$scope.currentPage = response.current_page;
			$scope.lastPage = response.last_page;
			
			if($scope.currentPage >= $scope.lastPage){
				$scope.loadMoreText='All Burgers Loaded!';
			}
		});
	
	// infinite scroll of the hamburgers
	$scope.loadMoreBurgers = function() {
		// increase our current page index
		$scope.currentPage++;
		
		
		//retrieve hamburgers listing from API and append them to our current list
		$http.get(constants.API_URL + "hamburgers", {params: { page: $scope.currentPage }})
			.success(function(response) {
				$scope.hamburgers = $scope.hamburgers.concat(response.data);
				$scope.currentPage = response.current_page;
				$scope.lastPage = response.last_page;
				
				if($scope.currentPage >= $scope.lastPage){
					$scope.loadMoreText='All Burgers Loaded!';
				}
			});
			
	};
	
	// adding a burger
	$scope.addBurger = function() {
			
		//add the new hamburger to our listing
		$http.post(constants.API_URL + "hamburgers", $scope.hamburger)
			.success(function(response) {
				
				console.log(response);
				
				// close the modal
				$scope.closeModal();
				
				// load the page for our newly created burger
				$scope.loadBurgerPage(response.id);
				

			})
			.error(function(response, status, headers, config) {
				// alert and log the response
				alert('Failed to add the burger: [Server response: '+status + '] - ' +response.name[0]);
				console.log(response);
				
			});

	}
	
	// load the page for an individual burger
	$scope.loadBurgerPage = function(id){
		 $location.path("hamburger/"+id);
	}
	
	// display the modal form
	$scope.showModal = function() {
		$('#addBurgerModal').modal('show');
	}
	
	// display the modal form
	$scope.closeModal = function() {
		$('#addBurgerModal').modal('hide');
	}
});

And here is the contents of the hamburgers.template.htm file

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
  <div class="container">
	<h1>Burgerpedia - We've Got the Burgers</h1>
	<p>Burgerpedia is an online directory of hamburger descriptions. It was created as a tutorial on how to integrate Laravel with AngularJS and Bootstrap. Laravel is used for the backend that supplies a REST API. AngularJS is used as the client that consumes the REST API. Bootstrap is used to make things pretty 🙂  </p>
	<p><a class="btn btn-primary btn-lg" href="http://miftyisbored.com/burgerpedia-a-complete-laravel-5-and-angularjs-tutorial-with-bootstrap-to-make-it-pretty/" role="button">View the tutorial &raquo;</a></p>
  </div>
</div>

<div class="container">
  <!-- Example row of columns -->
  <div class="row">
	<div class="col-md-12">
	  <div class="container">
		<div class="row" >
			<div class="text-right"><button type="submit" class="btn btn-success btn-lg" ng-click="showModal()">Add A New Burger <i class="glyphicon glyphicon-plus"></i></button></div>
			<div class="col-md-12">
				<h1>Hamburgers</h1>
				<p>Below are all the hamburgers at BurgerPedia. Click on a burger to view its descriptions or click on the add button to add a burger to our burger encyclopdia.</p>
				<form action="#" method="get">
					<div class="input-group">
						<!-- USE ANGULAR AJAX CALLS TO SEARCH BY FILTERING -->
						<input class="form-control" id="system-search" name="q" placeholder="Burger filter.." ng-model="searchText">
						<span class="input-group-btn">
							<button type="submit" class="btn btn-primary" disabled="disabled"><i class="glyphicon glyphicon-search"></i></button>
						</span>
					</div>
				</form>
				
			</div>
			<div class="col-md-12">
				<table class="table table-list-search table-striped">
					<thead>
						<tr>
							<th>Hamburger ID</th>
							<th>Hamburger Name</th>
							<th>Author</th>
							<th>Added On</th>
							<th>Actions</th>
						</tr>
					</thead>
					<tbody >
						<tr ng-repeat="hamburger in hamburgers | filter:searchText">
							<td>{{  hamburger.id }}</td>
							<td><a href="#/hamburger/{{hamburger.id}}">{{  hamburger.name }}</a></td>
							<td>{{  hamburger.author }}</td>
							<td>{{  hamburger.created_at }}</td>
							<td> 
								<a class="btn btn-info btn-detail" ng-click="loadBurgerPage(hamburger.id)">View Burger Descriptions</a>
							</td>
						</tr>
					</tbody>
				</table> 
				<!-- End of Table-to-load-the-data Part -->					
			</div>
			<div class="text-right"><button class="btn btn-primary" ng-disabled="currentPage >= lastPage" ng-click="loadMoreBurgers()" id="loadMoreButton" >{{loadMoreText}}</button></div>
			
			<!-- Modal (Pop up when detail button clicked) -->
			<div class="modal fade" id="addBurgerModal" tabindex="-1" role="dialog" aria-labelledby="addBurgerModalLabel" aria-hidden="true">
				<div class="modal-dialog">
					<div class="modal-content">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
							<h4 class="modal-title" id="addBurgerModalLabel">Add A New Burger to the Database</h4>
						</div>
						<div class="modal-body">
							<form name="frmAddBurger" class="form-horizontal" novalidate="">

								<div class="form-group error">
									<label for="hamburgerName" class="col-sm-3 control-label">Hamburger Name</label>
									<div class="col-sm-9">
										<input type="text" class="form-control has-error" id="name" name="name" placeholder="hamburger Name" value="" 
										ng-model="hamburger.name" ng-required="true">
										<span class="help-inline" 
										ng-show="frmAddBurger.name.$invalid && frmAddBurger.name.$touched">We can't let you continue without a Hamburger Name</span>
									</div>
								</div>
								<div class="form-group error">
									<label for="hamburgerAuthor" class="col-sm-3 control-label">Author</label>
									<div class="col-sm-9">
										<input type="text" class="form-control has-error" id="author" name="author" placeholder="Author" value="" 
										ng-model="hamburger.author" ng-required="true">
										<span class="help-inline" 
										ng-show="frmAddBurger.author.$invalid && frmAddBurger.author.$touched">You need to give us an Author's Name</span>
									</div>
								</div>
								<div class="form-group error">
									<label for="hamburgerAuthor" class="col-sm-3 control-label">Hamburger Overview</label>
									<div class="col-sm-9">
										<textarea class="form-control" rows="3" class="form-control has-error" id="overview" name="overview" placeholder="hamburger Overview" value="" ng-model="hamburger.overview" ng-required="true" ></textarea>
										<span class="help-inline" 
										ng-show="frmAddBurger.overview.$invalid && frmAddBurger.overview.$touched">Dude, give us an overview of this hamburger</span>
									</div>										
								</div>

							</form>
						</div>
						<div class="modal-footer">
							<button type="button" class="btn btn-primary" id="btn-save" ng-click="addBurger()" ng-disabled="frmAddBurger.$invalid">Add Hamburger </button>
						</div>
					</div>
				</div>
			</div>
		</div>
	   </div>
	</div>
  </div>

  <hr>

  <footer>
	<p>&copy; 2016 by Mifty, You are free to use this code as you like</p>
  </footer>
</div> <!-- /container -->

The first thing you will notice is that both files are related. The template file provides the overall look and feel of the page while the controller manipulates data within the page. The controller is also the file that will be responsible for interacting with our Laravel backend’s API. Its initial definition is like so:

BurgerPedia.controller('hamburgersController', function hamburgersController($scope, $http, $location, constants) { ..

The line above defines a controller ‘hamburgersController’ within the BurgerPedia module that was created in app/app.module.js. The following variables are injected into the controller as dependencies:

  • $scope: The scope is the binding part between the HTML (view) and the JavaScript (controller). That is why the scope is available for both the view and the controller.
  • $http: The $http service is one of the most common used services in AngularJS applications. The service makes a request to the server, and lets your application handle the response. It is responsible for all communications with remote APIs, including our Laravel API
  • $location: The $location service parses the URL in the browser address bar (based on the window.location) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
  • constants: this defines the various constants that we do not want to redefine over and over. In our case, our single most important constant is API_URL

Now lets take a closer look at the hamburgers controller. We start off by initiating some local variables but we also do a call right away to our Laravel Backend for the list of hamburgers in our database with GET request to API_URL/hamburgers using the $http.get() method. Once the method has completed successfully, we pass the data returned to our view, so that it can be displayed in the hamburgers.template.htm template view. How do we pass the data you ask? Well, that’s when the $scope comes in and we learn just how important $scope is for passing data in and out. The scope is a JavaScript object with properties and methods, which are available for both the view and the controller. The response from a succesul GET request is passed to .success enclosure. The enclosure method assigns the response to $scope.hamburgers variable. Anything defined as $scope.* is also available in the view. So, the $scope.hamburgers variable will be available in our view.

Inside the template file, we create a table to store our data and use the ng-repeat directive to loop through the results and add them to the table. That is what this done in the following line inside the template:

<tr ng-repeat="hamburger in hamburgers | filter:searchText">
	<td>{{  hamburger.id }}</td>
	<td><a href="#/hamburger/{{hamburger.id}}">{{  hamburger.name }}</a></td>
	<td>{{  hamburger.author }}</td>
	<td>{{  hamburger.created_at }}</td>
	<td> 
		<a class="btn btn-info btn-detail" ng-click="loadBurgerPage(hamburger.id)">View Burger Descriptions</a>
	</td>
</tr>

Because of how we configured the application in app.module.js, HamburgersController is in charge of the DOM sub-tree inside the template. The expressions in curly braces ({{hamburger.name}} and {{hamburger.author}}) denote bindings, which are referring to our application model, which is set up in our HamburgersController controller.

The ng-repeat=”hamburger in hamburgers” attribute on the <tr> tag is an Angular repeater directive. The repeater tells Angular to create a <tr> element for each hamburger in the list, using the <tr> tag as the template.

The expressions wrapped in curly braces ({{hamburger.name}} and {{hamburger.author}}) will be replaced by the values of the expressions.

Angular also comes with filtering, right out of the box thanks to the ng-model directive. In our sample template, we link the input box, called ‘searchtext’ to the datafilter of our hamburgers with just one line of code: “filter:searchText”. AngularJS does the rest and voila!, automatic text filtering!

I think that the reason why I love AngularJS so much is how well it sticks to the principles of event-driven programming. Every function defined inside the hamburgers controller is triggered by an event in the view. So let’s look at these events/actions:

  • clicking on the ‘Load More Burgers’ button, which is bound to $scope.loadMoreBurgers(),will trigger an AJAX call to get additional burgers
  • typing on the searchbox will trigger the built-in filtering mechanism
  • clicking on the ‘Add Hamburger’ button, which is bound to $scope.addBurger() will trigger the modal to add a new hamburger

It’s easy to understand what is going on and easy to add new methods, if required. And all interaction with the Laravel backend that we created earlier is handled by the controller through RESTful API calls.

So let’s go ahead and look at the methods inside the controller:

Method

Role

API call

loadMoreBurgers()

Loads more hamburgers and appends them to our table of hamburgers when the user clicks on the ‘Load More Burgers’ button

GET to API_URL/hamburgers

addBurger()

Adds a new hamburger to our database of hamburgers

POST to API_URL/hamburgers

loadBurgerPage()

Loads the page for a given hamburger

N/A

showModal()

Opens the modal window to Add a new hamburger

N/A

closeModal()

Closes the modal window to Add a new hamburger

N/A

Here is the hamburgers page with all burgers loaded:

hamburgers-page

Here is the add hamburger modal window when you click on the ‘Add A New Burger’ button:

hamburgers-add-burger

On the modal, we have all the input parameters along with the ‘Add Hamburger’ button that is linked to the addBurger() method. The modal contains the various input fields necessary to build a brand new hamburger, along with validation for these fields. This brings us to another great thing that AngularJS does very well: validation. (Yup, the AngularJS lovefest continues!!!) AngularJS does an amazing job at form validation and keeps it really simple. If you take some time to look at the modal responsible for adding a brand new hamburger, you will notice that:

<form name="frmAddBurger" class="form-horizontal" novalidate="">

The line above defines a form called frmAddBurger that will be used to add a brand new hamburger. We also add the the ‘novalidate’ attribute to stop the HTML5 validation.

We also use the ng-model directive to bind the various input fields of our hamburger model. So in the case of the ng-model=”hamburger.name”, anything entered in the hamburger name text box is automatically linked to the hamburger.name variable. This is a two-way connection, so any changes that are made to the value of hamburger.name by AngularJS is also visible in the text box too. ng-required= “true” validates our form and checks if a value has been supplied. If no value is supplied, the $invalid class will be attached to the field. AngularJS also supplies error messages, but of course, these messages should only be displayed when the user interacts with the form field. That is why AngularJS only displays the error messages if the field has the invalid class and the user has touched the field.

<span class="help-inline" ng-show="frmAddBurger.name.$invalid && frmAddBurger.name.$touched">We can't let you continue without a Hamburger Name</span>

Since the form can only be submitted by ‘Add Hamburger’ button. That is why AngularJS disalbes the button as long as there is an $invalid calss anywhere inside the form.

<button type="button" class="btn btn-primary" id="btn-save" ng-click="addBurger()" ng-disabled="frmAddBurger.$invalid">Add Hamburger </button>

Once the user fills all required fields and provides valid information, the submit button is made clickable.

The Hamburger Controller and Template

Now, let’s first look at the contents of the app/hamburger folder

Here is the contents of the hamburger.controller.js file

// create the controller and inject the Angular $scope
BurgerPedia.controller('hamburgerController', function hamburgerController($scope, $http, $location, $routeParams, constants) {
	// set our current page for pagination purposes
	$scope.hamburger_id = $routeParams.hamburgerID;
	$scope.hamburger_name = "Unknown Burger";
	$scope.hamburger_author = "Saint Nick";
	$scope.hamburger_overview = "Ho Ho Ho";
	$scope.hamburger_created_date = "Christmas";
	
	// modal title
	$scope.modal_title = 'Add a new Description for '+$scope.hamburger_name;
	$scope.modal_button = "Add Description";
	
	// pagination
	$scope.currentPage=1;
	$scope.lastPage=1;
	$scope.loadMoreText='Load More Descriptions...';
	
	$http.get(constants.API_URL + "hamburgers/" + $scope.hamburger_id )
		.success(function(response) {
			$scope.descriptions = response.data;
			$scope.hamburger_name = response.name;
			$scope.hamburger_author = response.author;
			$scope.hamburger_overview = response.overview;
			$scope.hamburger_created_date = response.created_at;
			
			// got the burger, now get the descriptions from the API
			$http.get(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions")
				.success(function(response) {
					$scope.descriptions = response.data;
					$scope.currentPage = response.current_page;
					$scope.lastPage = response.last_page;
					
					if($scope.currentPage >= $scope.lastPage){
						$scope.loadMoreText='All Descriptions Loaded!';
					}
				})
				.error(function(response, status, headers, config) {
						// log the response
						console.log(response);
						
						// alert and log the response
						alert('Failed to get the burger descriptions: [Server response: '+status + '] - ' +response.name[0]);
						
					});
		})
		.error(function(response, status, headers, config) {
				// log the response
				console.log(response);
				
				// reload the main page
				$scope.loadBurgersPage();
				
			});
	
	// infinite scroll of the hamburgers
	$scope.loadMoreDescriptions = function() {
		// increase our current page index
		$scope.currentPage++;
		
		
		//retrieve descriptions listing from API and append them to our current list
		$http.get(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions", {params: { page: $scope.currentPage }})
			.success(function(response) {
				$scope.descriptions = $scope.descriptions.concat(response.data);
				$scope.currentPage = response.current_page;
				$scope.lastPage = response.last_page;
				
				if($scope.currentPage >= $scope.lastPage){
					$scope.loadMoreText='All Descriptions Loaded!';
				}
			});
			
	};
	
	// when talk about adding a description, it is either a new description or an exisitng one. the button value tells us what to do...
	// its not the cleanest way to do it but it works.
	$scope.addDescription = function(descriptionID){
		
		if($scope.modal_button == "Edit Description"){
			//edit the existing description to our hamburger
			$http.put(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions/"+descriptionID, $scope.description )
				.success(function(response) {
					
					console.log(response);
					
					// close the modal
					$scope.closeModal();
					
					// reload the page
					location.reload();

				})
				.error(function(response, status, headers, config) {
					// alert and log the response
					alert('Failed to edit the description: [Server response: '+status + '] - ' +response.name);
					console.log(response);
					
				});
		}else{
			//add the new description to our hamburger
			$http.post(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions", $scope.description)
				.success(function(response) {
					
					console.log(response);
					
					// close the modal
					$scope.closeModal();
					
					// reload the page
					location.reload();
					

				})
				.error(function(response, status, headers, config) {
					// alert and log the response
					alert('Failed to add the description: [Server response: '+status + '] - ' +response.name[0]);
					console.log(response);
					
				});
		}
	}
	
	$scope.deleteDescription = function(descriptionID){
		var confirmDelete = confirm('Are you sure you want to delete this description?');
		if (confirmDelete) {
			$http.delete(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions/"+descriptionID)
			.success(function(response) {
				
				console.log(response);
				$location.reload();
				

			})
			.error(function(response, status, headers, config) {
				// alert and log the response
				alert('Failed to add the description: [Server response: '+status + '] - ' +response.name[0]);
				console.log(response);
				
			});
		}else{
			return false;
		}
		
	}
	
	// load the burgers page
	$scope.loadBurgersPage = function(){
		 $location.path("hamburgers");
	}
	
	// display the modal form
	$scope.showModal = function(action,descriptionID) {
		
		 switch (action) {
			case 'edit':
				$scope.modal_title = "Edit Description for "+$scope.hamburger_name;
				$scope.modal_button = "Edit Description";
				$http.get(constants.API_URL + "hamburgers/" + $scope.hamburger_id + "/descriptions/"+descriptionID)
					.success(function(response) {
						console.log(response);
						$scope.description = response;
					});
				break;
			case 'add':
			default:
				$scope.description = null;
				$scope.modal_title = 'Add a new Description for '+$scope.hamburger_name;
				$scope.modal_button = "Add Description";
				break;
		}
	
		$('#addDescriptionModal').modal('show');
	}
	
	// close the modal form
	$scope.closeModal = function() {
		$('#addDescriptionModal').modal('hide');
	}
});

And here is the contents of the hamburger.template.htm file

<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
  <div class="container">
	<h1>{{hamburger_name}}</h1>
	<small>added by {{hamburger_author}} on {{hamburger_created_date}}</small>
	<p>{{hamburger_overview}}</p>
	<p><a class="btn btn-success btn-lg" ng-click="showModal('add',0)" role="button">Add a Description</a> <a class="btn btn-primary btn-lg" href="#/" role="button">Back to the Burger Directory</a> <a class="btn btn-primary btn-lg" href="http://miftyisbored.com/burgerpedia-a-complete-laravel-5-and-angularjs-tutorial-with-bootstrap-to-make-it-pretty/" role="button">View the tutorial &raquo;</a></p>
  </div>
</div>

<div class="container">
  <!-- Example row of columns -->
  <div class="row">
	<div class="col-md-12">
	  <div class="container">
		<div class="row">
			<div class="col-md-12">
				<div class="row" ng-repeat="description in descriptions | filter:searchText">
					<div class="col-sm-12 burger-description-block">
						<div class="burger-description-block-title">{{ description.title}}</div>
						<p class="burger-description-block-meta">Added by {{ description.author}} on {{ description.description}}</p>
						<p class="burger-description-block-description">{{ description.description}}</p>
						<hr />
						<button class="btn btn-sm btn-primary" ng-click="showModal('edit',description.id)" id="loadMoreButton" >Edit Description</button> <button class="btn btn-sm btn-danger" ng-click="deleteDescription(description.id)" id="loadMoreButton" >Delete Description</button>
						
					</div>
					<hr>
				</div>
				<hr>
				<div class="text-center"><button class="btn btn-success" ng-click="showModal('add',0)" id="loadMoreButton" >Add A Description</button> <button class="btn btn-primary" ng-disabled="currentPage >= lastPage" ng-click="loadMoreDescriptions()" id="loadMoreButton" >{{loadMoreText}}</button></div>
			</div>
		</div>
	   </div>
	</div>
  </div>
  
  <!-- Modal (Pop up when detail button clicked) -->
			<div class="modal fade" id="addDescriptionModal" tabindex="-1" role="dialog" aria-labelledby="addDescriptionModalLabel" aria-hidden="true">
				<div class="modal-dialog">
					<div class="modal-content">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
							<h4 class="modal-title" id="addDescriptionModalLabel">{{modal_title}}</h4>
						</div>
						<div class="modal-body">
							<form name="frmAddDescription" class="form-horizontal" novalidate="">

								
								<div class="form-group error">
									<label for="title" class="col-sm-3 control-label">Description Title</label>
									<div class="col-sm-9">
										<input type="text" class="form-control has-error" id="title" name="title" placeholder="Title" value="description.title" 
										ng-model="description.title" ng-required="true">
										<span class="help-inline" 
										ng-show="frmAddDescription.title.$invalid && frmAddDescription.title.$touched">A Title for your description is needed</span>
									</div>
								</div>
								<div class="form-group error">
									<label for="description" class="col-sm-3 control-label">Description</label>
									<div class="col-sm-9">
										<textarea class="form-control" rows="3" class="form-control has-error" id="description" name="description" placeholder="hamburger Description" value="" ng-model="description.description" ng-required="true" value="description.description" ></textarea>
										<span class="help-inline" 
										ng-show="frmAddDescription.description.$invalid && frmAddDescription.description.$touched">Dude, give us your description of this hamburger</span>
									</div>										
								</div>
								<div class="form-group error">
									<label for="author" class="col-sm-3 control-label">Author</label>
									<div class="col-sm-9">
										<input type="text" class="form-control has-error" id="author" name="author" placeholder="Author" value="description.author" 
										ng-model="description.author" ng-required="true">
										<span class="help-inline" 
										ng-show="frmAddDescription.author.$invalid && frmAddDescription.author.$touched">Your Author Name is needed</span>
									</div>
								</div>

							</form>
						</div>
						<div class="modal-footer">
							<button type="button" class="btn btn-primary" id="btn-save" ng-click="addDescription(description.id)" ng-disabled="frmAddDescription.$invalid">{{ modal_button }}</button>
						</div>
					</div>
				</div>
			</div>

  <hr>

  <footer>
	<p>&copy; <?= date('Y') ?> by Mifty, You are free to use this code as you like</p>
  </footer>
</div> <!-- /container -->

Once again, you will notice is that both files are related. The template file provides the overall look and feel of the page while the controller manipulates data within the page. The controller is once again the file that will be responsible for interacting with our API. Its initial definition is like so:

BurgerPedia.controller('hamburgerController', function hamburgerController($scope, $http, $location, $routeParams, constants) { .. 

The line above defines a controller ‘hamburgerController’ within the BurgerPedia module that was created in app/app.module.js. The same variables as hamburgersController are injected into the controller as dependencies.

The second most important thing that happens is that we use the $routParams service to obtain the hamburger ID and pass it to the scope with this line of code:

$scope.hamburger_id = $routeParams.hamburgerID;

Now, we know which hamburger ID we need to obtain information for. We start off by initiating some local variables but we also do a call right away to our Laravel API for the details of the hamburger that we are interested in. This is done with the GET request to API_URL/hamburgers/{hamburger_id} using the $http.get() method. Once the method has completed successfully, we make an additional GET request to the Laravel backend to get all descriptions associated to the hamburger. This is done through a GET request to API_URL/hamburgers/{hamburger_id}/descriptions. Once the information comes back from the Laravel backend, the AngularJS view is updated with the information.

Once again, we use the ng-repeat directive to loop through the descriptions and add them to the table. That is what is done in the following line inside the template:

<div class="row" ng-repeat="description in descriptions | filter:searchText">
	<div class="col-sm-12 burger-description-block">
		<div class="burger-description-block-title">{{ description.title}}</div>
		<p class="burger-description-block-meta">Added by {{ description.author}} on {{ description.description}}</p>
		<p class="burger-description-block-description">{{ description.description}}</p>
		<hr />
		<button class="btn btn-sm btn-primary" ng-click="showModal('edit',description.id)" id="loadMoreButton" >Edit Description</button> <button class="btn btn-sm btn-danger" ng-click="deleteDescription(description.id)" id="loadMoreButton" >Delete Description</button>
		
	</div>
	<hr>
</div>

So let’s go ahead and look at the methods inside the controller:

Method Role API call
loadMoreDescriptions() Loads more hamburger descriptions and appends them to our table of hamburger descriptions when the user clicks on the ‘Load More Descriptions’ button GET to API_URL/hamburgers/{hamburgerID}/descriptions
addDescription() Adds a new hamburger description or Edits an existing hamburger description depending on the modal state. PUT to API_URL/hamburgers/{hamburgerID}/descriptions/{descriptionID} for editing

POST to API_URL/hamburgers/{hamburgerID}/descriptions/ for adding

deleteDescription() Deletes a hamburger description DELETE to API_URL/hamburgers/{hamburgerID}/descriptions/{descriptionID}
loadBurgersPage() Loads the burgers listing page N/A
showModal() Opens the modal window to Add a new hamburger N/A
closeModal() Closes the modal window to Add a new hamburger N/A

Here is a sample hamburger page with all descriptions loaded:

hamburger-bat-burger

Here is the modal window when you click on the ‘Add A New Description’ button:

hamburger-add-description

The same modal is used when you click on ‘Edit Description’ button:

hamburger-edit-description

The modal logic for the hamburger template is very similar to the one used for the hamburgers template. The only difference is that the Edit action will pre-fill the modal with information about the description that we want to edit.

Previous Step: Getting familiar with AngularJS
Next Step: Tutorial Conclusion

Tutorial Contents
Tutorial Resources

Mifty Yusuf is a Montreal-based software developer who enjoys playing with new web technologies as well as comic books and illustrations. He beleives that, no matter what the question is, the answer is always Batman!

4 Comments

  1. Hi, your tutorial is very helpful, i need some help, i not getting the required output, i am getting the empty Burgerpedia page alone that too in the link http://localhost:8080/burgerpedia/public.
    Where to put the controller and js files, inside public we should create a folder called app and create the controller files in that?

    I am new learner of Laravel, Bootstrap and AngularJS only your tutorial was helpful till now, i could not find a better tutorial. Great work keep it up bro.

    • Hi Praveen,
      Thanks for the kind words and sorry for the late reply. were you able to figure things out? I have not checked the blog in quite some time and just noticed your comment now.

  2. Hello Mifty, first thank you for this very helpful tutorial, I like it very much 🙂
    First I had the same problem like Praveen Kumar, I was getting an empty Burgerpedia page without the database data.
    But the problem was easy to solve: If you use another url different to http://localhost… you have to change it not only in the .env file, but also in public/app/app.module.js (line 6) otherwise you will get a javascript error and no data on the page.

    Best regards from Germany
    Arthur

Leave a Reply

Your email address will not be published.

*

Latest from AJAX

Go to Top