AngularJS Tutorial - 8

New version here

Note that the all tests are present inside the sample, but we will see them at the end of this chapter, when all hopefully will be clear.

A small refactoring

To organize the fields in a decent way a new app/common.css had been added.

.controls.aligncorrect{
  padding-top: 5px;
}

And a new structure had been set for the various forms. The style "form-horizontal" had been added, with the concept of control-group and control. This is needed to align the labels and the texts. The dots means "as it was before" :)

Add and Edit

<form ... class="form-horizontal" ... >
    <div class="control-group">
        <label class="control-label" ...>....</label>
        <div class="controls">
            <input ... /> <show-error ... ></show-error>
        </div>
    </div>
    ...

Detail

Note that we are using the "form-horizontal" class even when we are in a div! And we added the "aligncorrect" class to the div containing the value.

<div class="... form-horizontal">
    <div class="control-group">
        <label class="control-label" ...>...</label>
        <div class="controls aligncorrect">
            {{...}}
        </div>
    </div>
    ...

Calendar

We will add a field on the contacts: "birthDate". It will be added to the service too, and on the add, edit and detail templates. Not mandatory, and without validation. The api already added a "brithDate" field. This will be made using the Angular-Bootstrap-UI. And add the new field on all the tests! That now will not pass!!

    <input type="text" name="birthDate"
        datepicker-popup="dd-MMMM-yyyy" 
        min="minDate" max="'2100-06-22'" ng-required="true" 
        ng-model="contact.birthDate"/>

ComboBox

Generic how-to

In general it will be fairly simple.. but the ng-options will be a little hard to understand. This is a composition of a phrase to select the item. This state that a variable choicesList is present on the scope and that it contains:

[
    {id="id",description="choice description"},
    ...
]

All this will be bindend to the item.field, present on the scope too.

    <select 
        name="field" 
        ng-model="item.field" 
        ng-options="c.id as c.description for c in choicesList">
    </select>

For further information could look on the select on Angular website.

Our implementation

Anyway we are adding a new sex repository to get the list of possible sex for the contacts. The api will be based on "api/sex" and the only available operations will be getAll and getById. We will add the SexRepositoryService to the declaration of the ContactDetailController and we will fill the $scope.sexChoices variable.

app.controller('ContactDetailController', ['$scope', '$controller',
        'ContactsRepositoryService','SexRepositoryService',
    function($scope, $controller, contactsRepositoryService, sexRepositoryService) {
        sexRepositoryService.getAll().success(function(data) {
            $scope.sexChoices = data.data;
        });

And adding the correct html on the add page (edit will follow easily)

    <select name="sex" 
        ng-model="contact.sex" 
        ng-options="s.id as s.description for s in sexChoices">
    </select>

Dialogs and passing parameters

But.. we are using common controllers for our crud operations. The ContactDetailController (with its scope) that is initialized through the detailController. Then upon request the detailController open the various modalControllers. But they will use a different scope, so we use the trick of passing the required item as dependency to the controller with the resolve function. If we don't act this way nothing will be present

To allow this I created a new service the "copyService" (pretty simple, you could see the source directly on the example) it will copy certain values from a scope into a temporary structure and then apply them to another scope. First we have to tell the detailController what fields to copy into the child controller scope. Then we will add an array of variable names to copy and the copyService itslef

app.controller('detailController',['$scope','$modal', '$location', 'repository','identifier',
        'idsToCopy','copyService',
    function($scope, $modal, $location, repository, identifier,
        idsToCopy,copyService) {

Then we should pass the idsToCopy to the detailController (and change all the relative tests!!!) inside the ContactDetailController. Note that we will be declare that we will be copying the sexChoices array.

    $controller('detailController',{
        $scope:$scope,
        repository:contactsRepositoryService,
        identifier:'contact',
        idsToCopy : ["sexChoices"]
    });

Inside the detailController

    $scope.open = function (itemId) {
        repository.getById(itemId).success(function(data) {
            $scope[identifier] = data.data;

we will create the copy service instance from the service.

            var copy = copyService(idsToCopy,identifier);
            var modalInstance = $modal.open({
                templateUrl: 'assets/'+identifier+'/detail.html',
                controller: 'detailControllerModal',
                resolve: {

And the data is copied and passed to the detailControllerModal.

                    param: function () { return copy.storeDataIds($scope);},
                    identifier: function () {return identifier;}

The detail controller model will have no to handle it and will restore the data passed on the scope

app.controller('detailControllerModal', ['$scope', '$modalInstance', 'param', 'identifier',
    function($scope, $modalInstance, param, identifier) {
        param.restoreDataIds($scope);

Now all the other controllers will follow!

Download the sample source


Last modified on: September 17, 2014