Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm trying to create a member's form with all the pre-defined phone types.

Contact Form

I'm making two api requests for member and phone types. Here is what I've:

First request with member's information:

    $scope.member = {
    // $scope.member is returned from an api call
        "name": "John Doe",
        "phones": [{
            "id": "Cell",
            "phone": "(651) 111-1899",
            "phone_unlisted": false
        }, {
            "id": "Work",
            "phone": "(555) 121-1212",
            "phone_unlisted": true
        }]
    };

Second request to get phone types:

    myApp.controller("phoneTypes", function ($scope) {
        // $scope.phoneTypes is returned from an api call
        $scope.phoneNames = [{
            "id": "Cell"
        }, {
            "id": "Cell 2"
        }, {
            "id": "Work"
        }, {
            "id": "Work 2"
        }];
    });

An unsuccessful attempt to create this form.

HTML -

<div ng-controller="memberInfo">
    <h3>{{member.name}}</h3>
    <form role="form" ng-controller="phoneTypes">
        <h4>Phones</h4>
        <div ng-repeat="phoneName in phoneNames" >
            <label>{{phoneName.id}}</label>

            //need to bind phone detail from $scope.member if exists otherwise leave blank
            <input type="text" ng-model="member.phones.phone" />
            unlisted: <input type="checkbox" ng-model="member.phones.phone_unlisted" />
        </div>
    </form>
</div>

I need to bind phone number of each matched phone types, otherwise leave the input field blank.

Here is http://jsfiddle.net/4nzg4/

I've gone through various tutorials and posts but I'm not able to solve this. I'm new to AngularJS, any help will be appreciated.

Thank you,

share|improve this question

2 Answers 2

up vote 1 down vote accepted

Another approach that doesn't immediately break bi-directional binding—but does have its own disadvantages—would be to add the missing phone types to $scope.member directly. This is a bit of a bigger modification to your existing code.

First, we need to combine both controllers and templates into a single template:

<div ng-controller="memberInfo">
    <h3>{{member.name}}</h3>
    <form role="form">
        <h4>Phones</h4>
        <div ng-repeat="phone in member.phones" >
            <label>{{phone.id}}</label>
            <input type="text" ng-model="phone.phone" />
            unlisted: <input type="checkbox" ng-model="phone.phone_unlisted" /> <br/>
        </div>
    </form>
</div>

Note that we are now repeat-ing through member.phones directly. But that doesn't have all the available phone types? Correct, but we'll fix that now.

var myApp = angular.module("myApp", []);

myApp.controller("memberInfo", function ($scope) {

    // $scope.member is returned from an api call
    $scope.member = {
        "name": "John Doe",
        "phones": [{
            "id": "Cell",
            "phone": "(651) 111-1899",
            "phone_unlisted": false
        }, {
            "id": "Work",
            "phone": "(555) 121-1212",
            "phone_unlisted": true
        }]
    };
    var phoneNames = [{
        "id": "Cell"
    }, {
        "id": "Cell 2"
    }, {
        "id": "Work"
    }, {
        "id": "Work 2"
    }];

    // make an array of the phone id's in $scope.member.phones
    var currently_used_names = $scope.member.phones.map( function( phone ) {
        return phone.id;
    } ); // returns [ "Cell", "Work" ]

    // add any phone names that weren't listed
    phoneNames.forEach( function( name ) {
        if ( currently_used_names.indexOf( name.id ) === -1 ) {
            $scope.member.phones.push( {
                "id": name.id,
                "phone": "",
                "phone_unlisted": false
            } );
        }
    } );
});

In this case, we add to $scope.member.phones any of the phone types that need to be listed with some sort of default template.

Advantage: Keeps bi-directional binding.

Disadvantage: Adds "wrong" data to $scope.member.phones, though it is probably easy enough to filter out manually as necessary.

Here's a fiddle to demonstrate.

share|improve this answer
    
Excellent! As always, thanks for the detailed response David. I tried to use angular.forEach to merge phone types to $scope.member but didn't have any luck. I need to remember to use "map" :) –  newbie Jan 16 at 16:06

One possible solution—though I'm not comfortable it is the best—is to remap phones array into a phones_object where the key is the id of the phone. In other words, at the end of your memberInfo controller, you can:

$scope.member.phones_object = {};
$scope.member.phones.forEach( function( phone ) {
    $scope.member.phones_object[ phone.id ] = phone;
} );

Then, in the template, you use that object to display the phone numbers:

    <div ng-repeat="phoneName in phoneNames" >
        <label>{{phoneName.id}}</label>
        <input type="text" ng-model="member.phones_object[ phoneName.id ].phone" />
        unlisted: <input type="checkbox" ng-model="member.phones_object[ phoneName.id ].phone_unlisted" /> <br/>
    </div>

That gives you what I think is the result you want.

That gives you this result.

Unfortunately, this breaks Angular's bi-directional binding, at least to the original $scope.members.phones data. You could use a $watch to keep the data in sync, if you wanted, or simply sync it back up when you are ready to re-submit data to the API, depending on your application.

share|improve this answer
    
Thanks David. This is exactly what I had in mind but I guess it does break bi-directional binding. At least I know what I was doing wrong :) –  newbie Jan 16 at 16:09

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.