Sitecore with Angular JS Part 1
AngularJS is a great framework for a couple reasons:
- Data binding which is fantastic for working with JSON
- Module based approach
- Dependency injection
- Testable
I wanted to provide a simple example for anyone to get started with incorporating AngularJS into a Sitecore implementation. This article will go over creating:
- a service class for retriving albums from Sitecore
- an API Controller
- Domain Transfer Object
- a very simple AngularJS module for listing albums
For this example, we are building a music store. The store needs to display albums and this page will have filtering such as display albums whose release date is between two dates. The next post will cover more on filtering, but for this example, we will start some implementation.
Let's start by building the AlbumService class. Also, this assumes you are using Glass Mapper for interacting with Sitecore items and search index.
Album Service Class
Albums will be retrived from a Sitecore index. We will build a method GetReleasedAlbumsBetweenDates
that will query the index for items as the index will hold the release date field from the Sitecore item for Album.
services/Albums/IAlbumService.cs
public interface IAlbumService
{
IEnumerable<IAlbum> GetReleasedAlbumsBetweenDates(DateTime startDate, DateTime endDate);
}
services/Albums/AlbumService.cs
public class AlbumService : IAlbumService
{
private ISitecoreContext _context;
private IProviderSearchContext _provider;
private ISitecoreService _database;
public AlbumService(ISitecoreContext context, ISitecoreService database, IProviderSearchContext provider)
{
_context = context;
_database = database;
_provider = provider;
}
public AlbumService() : this(
new SitecoreContext(),
new SitecoreService(Sitecore.Context.Database),
ContentSearchManager
.GetIndex("sitecore_web_index")
.CreateSearchContext())
{}
public IEnumerable<IAlbum> GetReleasedAlbumsBetweenDates(DateTime startDate, DateTime endDate)
{
var albums = _provider.GetQueryable<Album>()
.Where(x => x.TemplateId == IAlbumConstants.TemplateId
.Where(x => x.ReleaseDate >= startDate)
.Where(x => x.ReleaseDate <= endDate);
foreach(var album in albums)
{
yield return _database.Map(album);
}
}
}
Web API Controller
We want to build an API controller, MusicController
that will take a parameter DateRangeDTO
and the class for DateRangeDTO
.
api/MusicController.cs
public class MusicController : ApiController
{
}
Models/DTO/DateRangeDTO.cs
public class DateRangeDTO
{
public string StartDate { get; set; }
public string EndDate { get; set; }
}
The Domain Transfer Object will be used to transfer the range of dates that an album release date will fall between.
The MusicController
will use the AlbumService and accept the object that represents the date range. We can use the start date and the end date to pass to GetReleasedAlbumsBetweenDates
.
public class MusicController : ApiController
{
[HttpPost]
public IHttpRequestAction Albums(DateRangeDTO range)
{
var albumService = new AlbumService();
//ConvertToDateTime is an extension method that parses a string to DateTime
var startDate = range.StartDate.ConvertToDateTime();
var endDate = range.EndDate.ConvertToDateTime();
var albums = albumService.GetReleasedAlbumsBetweenDates(startDate, endDate);
if(!albums.Any())
return Ok();
return Ok(albums);
}
}
AngularJS
To start building out the client side, we need to build the Album module. As mentioned before, AngulurJS has a concept of modules so we want to encapulate any logic that will be used for managing albums.
js/app/albums/albums.js
angular
.module('app.albums')
.controller('Albums', Albums);
function Albums($http) {
//create getAlbums function
}
Next, we can create the method getAlbumsBetweenReleaseDates
that will be responsible for creating the DTO and post to the API controller we just created.
var app = angular.module('app', ['app.albums']);
angular
.module('app.albums')
.controller('albums', albums);
function albums($http, $scope) {
function getAlbumsBetweenReleaseDates(startdate, enddate) {
var daterange = {
'StartDate': startdate || '',
'EndDate': enddate || ''
};
var dto = JSON.stringify(daterange);
var requestUrl = "/api/Music/Albums";
return $http({
method: 'post',
url: requestUrl,
data: dto,
headers: {
'Content-Type': 'application/json'
}
}
}
$scope.albums = getAlbumsBetweenReleaseDates();
}
Notice how the DTO is created by creating an object that will have StartDate
and EndDate
and convert it to a JSON string.
Views/Albums/_ListAlbums.cshtml
<div ng-app="app">
<div ng-controller="albums">
<input type="text" class="albums--start-date" />
<input type="text" class="albums--end-date" />
<input type="button" value="Filter" class="albums--submit-button" />
<div ng-repeat="item in albums">
<div>{{ item.title }}</div>
<div>{{ item.artist }}</div>
<div>{{ item.releasedate }}</div>
</div>
</div>
</div>
Here we place the listing of albums in a partial view and for the sake of simplicty we have added the initialization of the app and the controller here. If AngularJS was going to be used throughout the site, we will probably put a little bit more planning on how AngularJS is introduced in an ASP.NET MVC site under Sitecore.
We can see a mocked version of this on JSFiddle:
In further posting, we can start to tackle other aspects of AngularJS and how that might change what we put together so far.
One of the biggest struggles I faced with using AngularJS was how to structure and provide good separation. So far we have:
MusicStore.Web
- api/MusicController.cs
- js/app/albums/albums.js
- Models/DTO/DateRangeDTO.cs
- services/Albums/AlbumService.cs
- services/Albums/IAlbumService.cs
- Views/Albums/_ListAlbums.cshtml
As more modules are created and dependencies need to be managed, the js
folder will become complex. For example, you notice that I declare the module for albums and the app in js/app/albums/albums.js
. Much of this should be moved to a new file js/app/app.module.js
. As more modules are created, this file will be a good place to manage them and how they are dependent on each other.
One resource that was very helpful was John Papa's styleguide on AngularJS. I constantly referenced this document in order to have some good practices and consistency with AngularJS.
We have not built out how the filter form is wired up and how that interacts with the albums
module.
The next post we can cover working with filtering the results with the start and end dates and adding on additional concepts in AngularJS such as:
- modules that are common for reuse
- includes for reusing blocks of markup
- enforcing some better separation and responsiblity