How to Use Components and Services in AngularJS
One of the biggest aha moments I’ve had when using Angular was when I discovered the joy of using Components and Services together. When I initially began learning Angular I was taking a very Rails approach to the application architecture with a heavy reliance on routing to navigate to different view templates. With the help up Components to change the DOM within a single route and Services to interact with our Rails API backend we can be less reliant on routes to navigate our application. Let’s get started!
First we will build out our Rails API TripsController that will communicate with out service. Our controller will look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
module Api
module V1
class TripsController < ApplicationController
def index
respond_with(Trip.all.order("id ASC"))
end
def show
respond_with(Trip.find(params[:id]))
end
def create
trip = Trip.create(trip_params)
if trip.save
render :json => trip
end
end
def update
trip = Trip.find(params[:id])
if trip.update(trip_params)
render :json => trip
end
end
def destroy
respond_with(Trip.destroy(params[:id]))
end
private
def trip_params
params.require(:trip).permit(:name, :depart_date, :return_date)
end
end
end
end
Now that we have our API set up we can create our TripService to communicate with it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function() {
'use strict';
function TripService($http) {
this.getTrips = function() {
return $http.get('api/v1/trips.json');
};
this.getTripById = function(id) {
return $http.get('api/v1/trips/' +id+ '.json');
};
this.newTrip = function(tripData) {
return $http.post('api/v1/trips.json', tripData);
};
this.editTrip = function(id, updatedTripData) {
return $http.put('api/v1/trips/' +id+ '.json', updatedTripData);
};
this.deleteTrip = function(id) {
return $http.delete('api/v1/trips/' +id+ '.json');
};
}
angular
.module('app')
.service('TripService', TripService);
}());
Our TripService contains a group of functions that communicate with our Rails API methods to retrieve, post, update, and delete data with the help of Angular’s $http
service. Now that we are able to communicate with our Rails API on the backend and TripService on the frontend we can build out our component and the corresponding html files. Let’s start with the component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(function() {
'use strict';
var individualTrip = {
transclude: true,
templateUrl: 'individual-trip.html',
controller: IndividualTripController,
bindings: {
trip: '=',
parentController: '='
}
};
function IndividualTripController(TripService) {
var ctrl = this;
ctrl.readMode = true;
ctrl.editMode = false;
ctrl.editableTrip = ctrl.trip;
ctrl.startEditMode = startEditMode;
ctrl.closeEditMode = closeEditMode;
ctrl.updateTrip = updateTrip;
ctrl.destroyTrip = destroyTrip;
function startEditMode() {
ctrl.readMode = false;
ctrl.editMode = true;
}
function closeEditMode() {
document.location.reload(true);
ctrl.editMode = false;
}
function updateTrip() {
return TripService.editTrip(ctrl.editableTrip.id, ctrl.editableTrip)
.success(function() {
document.location.reload(true);
});
}
function destroyTrip() {
return TripService.deleteTrip(ctrl.editableTrip.id)
.success(function() {
document.location.reload(true);
});
}
}
angular
.module('app')
.component('individualTrip', individualTrip);
}());
First we declare a variable individualTrip
to contain our component object. individualTrip
will include transclude equal to true because we want our individual trip content to be nested inside a <individualTrip>
tag. Next we set our html template. We will build this template shortly. We also set the IndividualTripController
which is defined below the component object. And finally we set a binding so we have access to the individual trip and we bind our parentController which will have access to our collection of trips so we can iterate over the collection in our index html page. Below the component object we define our controller. Here we can set the variable ctrl
to this
and set a few attributes that we will have access to in our templates. Finally we have a series of functions that will both change our mode attributes and communicate with our TripService
. Let’s quickly build out our index.html
file that will repeat over our collection and display each unique individualTrip template.
1
2
3
4
5
6
7
<div class="container">
<h2>Your Trips</h2>
<h3>Trip Name</h3>
<div ng-repeat="trip in vm.trips | orderBy:'name'" title="">
<individual-trip parent-controller="vm" trip="trip"></individual-trip>
</div>
</div>
Now we can build out our individualTrip template that will display our trip data and links to edit and delete trips.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<div ng-if="$ctrl.readMode">
<div class="col-md-6">
<h2>{{$ctrl.trip.name}}</h2>
<p>Departing: {{ $ctrl.trip.depart_date | date: 'MM/dd/yyyy' }}</p>
<p>Returning: {{ $ctrl.trip.return_date | date: 'MM/dd/yyyy' }}</p>
<button class="btn btn-primary" ng-click="$ctrl.startEditMode()">Edit Trip </button>
<button class="btn btn-danger" ng-click="$ctrl.destroyTrip()">Delete Trip</button><br>
</div>
</div>
<div ng-if="$ctrl.editMode">
<h2>Edit Your Trip</h2>
<button class="btn btn-danger" ng-click="$ctrl.closeEditMode()">Close Edit Form</button>
<form name="form" novalidate ng-submit="$ctrl.updateTrip()">
<div class="form-group">
<label for="name">Name:</label>
<input class="form-control" type="text" name="tripName" placeholder="" ng-model="$ctrl.trip.name" required ><br>
<div ng-messages="form.tripName.$error">
<div ng-message="required" ng-if="form.$submitted">Trip name is required.</div>
</div>
</div>
<div class="form-group">
<label for="depart">Depart Date:</label>
<input class="form-control" type="date" name="depart" ng-model="$ctrl.trip.depart_date" required ><br>
<div ng-messages="form.depart.$error">
<div ng-message="required" ng-if="form.$submitted">Depart date is required.</div>
</div>
</div>
<div class="form-group">
<label for="return">Return Date:</label>
<input class="form-control" type="date" name="return" ng-model="$ctrl.trip.return_date" required ><br>
<div ng-messages="form.return.$error">
<div ng-message="required" ng-if="form.$submitted">Return date is required.</div>
</div>
</div>
<input class="btn btn-primary" type="submit" value="Update Trip">
</form>
</div>
</div>
With this template we will have full access to each individual trip and be able to edit or delete without created separate routes or controllers for those actions. I hope you found this helped to unravel how components and services can make Angular development enjoyable and productive. Feel free to reach out on Twitter with any questions.