3

Lets assume I have a AngularJS directive looking like this:

app.directive('psDIR', [
    function() {
        return {
            template: "<div style='padding: 5px; border: 1px solid red; margin-bottom: 10px;'><p>This is a direcive:</p> <textarea rows='5' cols='50' ng-model='md'></textarea></div>",
            restrict: 'AEC',
            scope: {}
        }
    }
]);

I am using this directive number of times on a single page. How do I get a value of every directive instance/scope of the ng-model="md" in my MainCtrl (i.e. I want to save this value in the add()) :

app.controller('MainCtrl', ['$scope',
    function($scope) {
        console.log("init");
        $scope.add = function() {
            console.log($scope);
        }
    }
]);

Plunker demo: http://embed.plnkr.co/Q5bw6CBxPYeNe7q6vPsk/preview

Any suggestions much appreciated.

3 Answers 3

3

Since you are creating isolated scope and otherwise too you cannot access the child scope from parent scope.

The way out it to pass the model as parameter from parent like

<div class="psDIR" model='field2'></div> <div class="psDIR" model='field1'></div>

Then in the directive update them with attribute binding. See update plunkr

http://plnkr.co/edit/GKZHSDe5J0eCzqlgaf4g?p=preview

Sign up to request clarification or add additional context in comments.

3 Comments

It is first time I see the model on the directive. Can you please provide me with a link maybe where I could do some more reading. many thanks
This is a random name that i took. You can take any name. Read compile documentation for directive api docs.angularjs.org/api/ng/service/$compile
This solution looks cleanest. Let me do some experimenting.
2

Usually the right thing to do when you want to communicate between scopes (i.e. directives and other controllers) is to use a service.

You can take a look at a simple plunker here: http://plnkr.co/edit/8Al31Bq9BfoazUCpqhWy?p=preview

The psDirs service keeps a registry of the directives:

app.service('psDirs', function() {
  var psDirs = {
    dirs: [],
    register: function (dir) {
      psDirs.dirs.push(dir);
    }
  };

  return psDirs;
});

And the directives register themselves and update the value when they change:

link: function (scope, elm, attr) {
  var dir = { val: "" };
  psDirs.register(dir);

  scope.$watch('md', function (n, o) {
    if (n !== o) {
      dir.val = n;
    }
  });
}

Then your controller can inject the psDirs service and access the directive registry as needed. This prevents brittle scope relationships, and allows the the directive registry service to be used elsewhere, in multiple controllers and sections of your application.

2 Comments

This solution clearly works and it is easy t understand. However I cannot help myself to think that AngularJS team have seen this situation and they implemented this situation - I am just not aware of it!:)
Keeping state like this in services is generally a bad idea IMO - and has to be done carefully if at all. For example, your code above never unregisters the directive data from the service, meaning that it never gets garbage collected and over time the service will accumulate more and more redundant data as directives are destroyed and recreated. As well as the risk of your application unintentionally re-using "dead" data, if you happen to register anything that includes a closure you could end up with quite a significant memory leak!
2

A possible solution is with require:

app.directive('psDIR', [
    function() {
        return {
            ...,
            require: "ngController",
            link: function(scope, elem, attrs, ngCtrl) {
              ngCtrl.hook(scope);
            }
        }
    }
]);

And the necessary change to the controller:

app.controller('MainCtrl', ['$scope',
    function($scope) {
        console.log("init");
        $scope.add = function() {
            var i;
            for( i=0; i < psDirs.length; i++ ) {
              console.log(i + " -> " + psDirs[i].md);
            }
        }

        var psDirs = [];
        this.hook = function(scope) {
          psDirs.push(scope);
        };
    }
]);

Try it out here: http://plnkr.co/edit/zCZ1TOm3aK8V4piCAY6u?p=preview


If you do decide to go with this solution, I'd suggest implementing the "wrapper" controller in a specialized wrapper directive, so as to avoid situations where ng-controller may be set in some other element in the hierarchy. In this case, just require: "wrapperDirective".

EDIT: The HTML for the wrapper directive case would simply look like:

<div wrapper-directive>
    <div class="psDIR"></div>
    <div class="psDIR"></div>
    <button ng-click="add()">Add</button>
</div>

And the directive itself uses the code of the previous ng-controller:

app.directive('wrapperDirective', function() {
    return {
        restrict: "A",
        // YOU MAY WANT ISOLATED SCOPE, JUST ADD scope: {},
        controller: ["$scope", function($scope) {
            // SAME CODE AS BEFORE
            console.log("init");
            $scope.add = function() {
                var i;
                for( i=0; i < psDirs.length; i++ ) {
                  console.log(i + " -> " + psDirs[i].md);
                }
            }

            var psDirs = [];
            this.hook = function(scope) {
              psDirs.push(scope);
            };
        }]
    };
});

And of course change the require configuration:

app.directive('psDIR', [
    ...
    require: "^wrapperDirective",
    link: function(scope, elem, attrs, wrapperDirectiveCtrl) {
        wrapperDirectiveCtrl.hook(scope);
    }

9 Comments

Please can you go into more details about wrapper controller?
where is the wrapperDirectiveCtrl comming from?
It is the controller of wrapperDirective.
This is a naming convention. The critival is in the require: "XXX", where XXX must match the name of the directive.
Yes, that is correct. Additionally you can require many controllers. And I have a mistake in the syntax above, the correct is require: "^wrapperDirective" (meaning search my parents for the named directive) - I will correct it in the answer. Read details here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.