0

I have a complex data structure that requires lots of inline editing. To make the code easier, much of the breaking down of components over this data structure has been placed into nested directives.

For example, here is an example of the data structure:

{
   "pageElements":[
      {
         "id":1721,
         "pageId":861,
         "position":0,
         "type":"Paragraph",
         "text":"<p>Who is the captain of the Enterprise NX-01?</p>"
      },
      {
         "id":1722,
         "pageId":861,
         "position":1,
         "type":"Question",
         "question":{
            "id":1664,
            "type":"ShortAnswer",
            "successMessage":"You really know your captains!",
            "hints":[

            ],
            "answerAllowance":"SINGLE",
            "minimumNumberOfKeywords":1,
            "keywords":[
               {
                  "id":45394,
                  "text":"John Archer",
                  "accuracy":0,
                  "ignoreCase":false
               }
            ]
         }
      },
      {
         "id":2786,
         "pageId":861,
         "position":2,
         "type":"Paragraph",
         "text":"<p>fafadffadfda</p>"
      }
   ]
}

Imagine the main page for this controller iterates over pageElements:

<ol class="pageElements">
    <li ng-repeat="pageElement in page.pageElements">
        <!-- page-element directive uses $compile 
             to create a specific DOM based on pageElement.type -->
        <div page-element ng-model="pageElement"></div>
    </li>
</ol>

Now, let's imagine that some modifications of a single pageElement occur within the page-element directive (or some child directive). How do we get those modifications into the page.pageElements list? For some reason, I can get changes to propagate to parent directives if they are just objects, but once the parent object is contained within a list, the updates no longer propagate.

How can I solve this? Is the problem really with ng-repeat stopping the propagation upwards?

egervari
  • 22,372
  • 32
  • 121
  • 175
  • It's a little difficult to understand the question. As an example are you trying to change something like the "minimumNumbeOfKeywords" field or add a keyword object to your keywords list? It sounds like you have a scope hierarchy issue, but difficult to pinpoint without a more complete example. – Ryan Q Mar 11 '14 at 03:11
  • @RyanQ It's all good, I solve with the dot notation problem. Apparantly I am not the first person to come across this with ng-repeat. It looks like a scope issue, but my posted answer links the stackoverflow thread that goes into more detail about the actual problem. I am confident this solved the issue. – egervari Mar 11 '14 at 03:38
  • Yeahp the scope hierarchy (aka dot notation) problem gets quite a few people. – Ryan Q Mar 11 '14 at 03:59

3 Answers3

1

to share data between directive and controllers use SERVICE, it is nothing but the injections that you do in controllers for eg: $http,$rootScope,$scope similarly you can have user defined services.. inject it in your controller and directive and use to exchange data!

Second part: how to tell a controller that a value has changed in directive? say your directive has a variable A and on change of A you want to do something in controller then you have following two ways

  1. Either use $watch
  2. or use $emit method (preferably use this! it is a kind of broadcast i.e. when the change occurs on A then this $emit is fired and all controllers will have access to this $emit event you can listen to it and do what ever you want to trigger)
Rishul Matta
  • 3,383
  • 5
  • 23
  • 29
  • Watches are being used. There are about half a dozen nestings of directives that all uses watches. When the leaf directive changes something, it does propagate to the parents. The only issue was that it stopped at `ngRepeat`, and didn't propagate to `page.pageElements`, as I said in my question. A service can't be used because managing the nested hierarchy of the object graph would be a total nightmare. – egervari Mar 11 '14 at 08:11
  • so if the propagation is not happening beyond ng-repeat then use $emit to fire a broadcast and this will be accessible to all the controllers. oki i just read your answer seems nice – Rishul Matta Mar 11 '14 at 08:18
  • It was being was propagated in a sense, but it was not a reference to the object - it was just a primitive value because it was not using a dot notation. So while the inner childs - even ngRepeat - were getting updated, page.pageElements was still stuck with the old values. – egervari Mar 11 '14 at 08:21
0

You just need to set the directive scope propperly. Take a look at this code

<html>
    <head></head>
    <body>
        <div ng-app="app">
            <div ng-controller="ctrl">
                <div>
                    <h1>I am the parent scope.</h1>
                    {{page}}
                </div>
                <ol class="pageElements">
                    <li ng-repeat="pageElement in page.pageElements">
                        <!-- page-element directive uses $compile 
                             to create elemented based on type -->
                        <div>
                            <h2>I am still the parent scope, only that I watch one element</h2>
                            {{pageElement}}
                        </div>>
                        <div page-element my-page-element="pageElement"></div>
                    </li>
                </ol>
            </div>
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
        <script>
            angular.module('app', [])
                .controller('ctrl', function($scope) {
                    $scope.page = {"pageElements":[{"id":1721,"pageId":861,"position":0,"type":"Paragraph","text":"<p>Who is the captain of the Enterprise NX-01?</p>"},{"id":1722,"pageId":861,"position":1,"type":"Question","question":{"id":1664,"type":"ShortAnswer","successMessage":"You really know your captains!","hints":[],"answerAllowance":"SINGLE","minimumNumberOfKeywords":1,"keywords":[{"id":45394,"text":"John Archer","accuracy":0,"ignoreCase":false}]}},{"id":2786,"pageId":861,"position":2,"type":"Paragraph","text":"<p>fafadffadfda</p>"}]};
                })
                .directive('pageElement', function() {
                    return {
                        template: '<div><h3>I am the directive scope. You can change the "text" property here to see it propagate.</h3><input ng-model="pageElement.text"><p>{{pageElement.text}}</p><div>{{pageElement}}<div></div>',
                        scope: {
                            pageElement: '=myPageElement'
                        }
                    };
                });
        </script>
    </body>
</html>

Working fiddle: http://jsfiddle.net/9rcu3/

The magic is in the directive scope binding:

scope: {
    pageElement: '=myPageElement'
}

Take a look at the angular developer guide, they have an awesome tutorial on building directives:

http://docs.angularjs.org/guide/directive

Roger
  • 2,912
  • 2
  • 31
  • 39
  • Yes, I can get the values viewable like yours no problem... the code I am working with is actually non-trivial. I am simplifying it greatly. I did actually figure it out. By using the dot notation, we can get angular to do 2-way binding correctly. Now when a `pageElement` is updated in the child directives, it will actually propagate correct to `page.pageElements`. Without the dot notation, it won't - the scope will be isolated within `ngRepeat`. – egervari Mar 11 '14 at 03:26
0

Okay, I solved it this way. Apparantly models need to have a containing object reference while inside of ngRepeat:

<ol class="pageElements">
    <li ng-repeat="pageElement in page.pageElements">
        <div page-element model="page.pageElements[$index]"></div>
    </li>
</ol>

So, the trick was page.pageElements[$index] instead of pageElement

I got this solution from:

Directive isolate scope with ng-repeat scope in AngularJS

Community
  • 1
  • 1
egervari
  • 22,372
  • 32
  • 121
  • 175
  • I discovered a the exact same solution last night, but given the elegance that Angular gives, it felt like the wrong solution, to be passing around array elements, etc. This morning I continued work and found that the cause of the problem was not Angular, but the way in which I was copying the model object inside the directive (for editing), and then over-writing the original scope object (having $$hashKey) with the editied model object. The solution was to copy the changed properties back into the scope object instead of overwriting it completely. – David Kirkland Jul 08 '15 at 01:10