0

I have a factory function that has some data and a some functions which manipulate it. This data is used several times in the application, at some points the data is on the page and then is also opened up in a modal- manipulating the data in the modal changes the data in the background page. What is the most 'angular way' to create separate instances of the data?

Updated:

The factory:

factory('filterFactory', [function () {


return {
    getGroups: function(term){
        // TODO: get terms groups with $http
        if(term){
            // get terms
        }
        else {
            return this.terms;
        }
    },
    addGroup: function(group){
        for (var term in this.terms) {
            if(this.terms[term].name === group){
                this.terms[term].included = true;
            }
        }
    },
    removeGroup: function(group){
        for (var term in this.terms) {
            if(this.terms[term].name === group){
                this.terms[term].included = false;
            }
        }
    },
    selectGroup : function(group){
        for (var term in this.terms) {
            if(this.terms[term].name === group){
                this.terms[term].included = true;
            } else {
                this.terms[term].included = false;
            }
        }
    },
    setAll : function(value){
        for (var term in this.terms) {
            this.terms[term].included = value;
        }
    },

    isCollapsed: true,

    terms: {
        people: {
            included: false,
            name: 'person',
        },
        organizations: {
            included: false,
            name: 'organization',
        },
        ...
    }

};

}]).

Attempted implementations:

    $scope.newTermMeta.type = filterFactory;

var temp = filterFactory;
$scope.newTermMeta.type = temp.terms;

$scope.newTermMeta.type = filterFactory.getGroups();

var temp = filterFactory;
$scope.newTermMeta.type = temp.terms;

$scope.newTermMeta.type = Object.create(filterFactory.getGroups());

note: none of the above implementations created an isolated instance.

Template Code:

<div class="modal-body">

    <form>
        <label> 
            <h2>{{newTermMeta.name}}</h2>
        </label>
        <br>
        <span>Add your term to relevant term groups:</span>
        <br>
        <div class="well well-sm col-sm-8">
            <button ng-repeat="group in newTermMeta.type" btn-checkbox class="btn btn-sm btn-default margin5" type="button" ng-model="group.included">{{group.name}}</button>
        </div>
        <br>
        <span>Optional:</span>
        <br>
        <label> Enter a concise (at most one sentence) definition for this term:</label>
        <input class="form-control width80p" type="text" ng-model="newTermMeta.definition">
    </form>
</div>
bornytm
  • 793
  • 1
  • 11
  • 27
  • are these separate instances in different controllers or are you setting that $scope.newTermMeta in a shared controller perhaps? – Matt Pileggi Mar 11 '14 at 16:49
  • newTermMeta only exists in one controller (newTermModalInstanceCtrl). – bornytm Mar 11 '14 at 16:53
  • This answer to a somewhat similar question seems to be the best bet. http://stackoverflow.com/a/16626527/2061741 – bornytm Mar 11 '14 at 16:54
  • 1
    Here is a fiddle showing the Object.create approach. http://jsfiddle.net/LAFC2/ I believe it's practically equivalent since Object.create uses "new" under the covers. I also believe that approach would have the same shortcomings if the returned instance referenced a shared array of objects. It may be what you are seeing since "terms" is an array of objects. – Matt Pileggi Mar 11 '14 at 18:16

1 Answers1

1

The factory object is a shared instance, so anything you change in that factory object is going to change for everyone using it.

Factory is the right thing to do, but it sounds like you want to encapsulate it in a different scope. This is fairly easy with Angular since all controllers get their own scope and directives have the option of having their own scopes as well. Assuming you are showing it in a controller you could do something like this:

myapp.controller('OtherCtrl', [ 'MyFactory', '$scope', function(MyFactory,$scope) {

  // create a defensive copy of the factory object by creating a new object with the wrapped object as its prototype chain, anything can then access the data but changing the properties change only on $scope.theObject directly
  $scope.theObject = Object.create(MyFactory.getTheObject());

 // alternatively, copy the relevant properties into our scope, it's protected but you won't be updated when the factory object changes unless you also specify a $watch statement
 var obj = MyFactory.getTheObject();
 $scope.name = obj.name;

}]);

Updated: caveat

One caveat when using the Object.create() defensive copy mechanism is that it will only JSON stringify the properties you modify. It will not work if you intend to modify a property then submit the entire object back to the server. It does work great for read-only properties, or for serializing only the modified properties however. And Angular will update the values of the non-modified properties without a $watch since it can still detect changes via the prototype chain.

A fiddle demonstrating the difference between JSON.stringify and the prototype chain values is here http://jsfiddle.net/XXdqv/

Matt Pileggi
  • 7,126
  • 4
  • 16
  • 18
  • 1
    No prob. One caution on using the Object.create() variation is that it doesn't properly serialize to JSON if you intend to submit it back to the server as-is. I use the Object.create copy for read-only views and I like it, but you'll probably have to do the second option of creating a scope object with copies of the values if it's for a form object that gets submitted back. – Matt Pileggi Mar 10 '14 at 16:22
  • I can't get this to work for my use case. It seems I am either implementing your solution incorrectly or perhaps the factory is not the best way to achieve this due to its data binded nature. I've updated the question with the factory- none of the implemented solutions made the instance in the second controller independent of the others. – bornytm Mar 11 '14 at 07:32
  • sorry it's not working. Can you add the code that is modifying/rendering the data also? – Matt Pileggi Mar 11 '14 at 14:36