Search code examples
angularjsangularjs-directiveangular-ui-routerangularjs-scope

How to get data from a page with a parent route controller to a custom directive using Angularjs?


I'm using Angularjs 1.4x and I have a page that has a controller declared something like this:

$stateProvider
        .state('myPage', {
            url: '/my-page',
            templateUrl: templateUrl,
            controller: 'searchCtrl', ...

On that page I have a directive, called myDirective that has a button with ng-click that sends some text as a parameter back to the directive controller. What I'm trying to do is get that parameter value back to the directive when the user clicks the button and then call another controller to get that parameter value and open a modal.

If I use $scope I can get the value from searchCtrl, but I've been unable to figure out a way to get the directive to receive the parameter value. In the past, I would put the button in the template, but here the button is already in the myPage.html page.

I know now that that I can't use ng-controller on the page as it already has a controller from the $route, which is searchCtrl. So what is the proper way to do this?

Here is my code, but I don't have ui-router in it, but I don't know if that matters, and the plunker:

Index.html

<!DOCTYPE html>
<html>
  <head>
    <script data-require="[email protected]" data-semver="1.4.2" src="https://code.angularjs.org/1.4.2/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <div id="app" ng-app="app">
    <div ng-controller="myCtrl">
          <my-directive ctrl-fn="ctrlFn"></my-directive> 
        </div>
      </div>

  </body>
</html>

myPage.html

<div>
  <button ng-click="ctrlFn({param: 'hello'})">Click Here</button>
</div>

script.js

// Code goes here
var app = angular.module('app', []);
app.controller('myCtrl', function($scope){

  $scope.ctrlFn = function(param) {
      console.log("param is: " + JSON.stringify(param));
  };  
});

function newCtrl($scope) {
  //How can I call this function from inside the directive and pass in param?
  //Do something here
}

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    scope: {
      ctrlFn: '&'
    },
   templateUrl: "./myPage.html",
    link: function(scope, element, attributes, controller) {
      scope.ctrlFn = scope.ctrlFn();

    }
  };
});

Any ideas on how to implement this properly?


Solution

  • If you're ok with putting the callback function in a service, you can do something like this:

    var app = angular.module('app', []);
    app.controller('mainCtrl', function($scope, newCtrl) {
    
      $scope.ctrlFn = function(param) {
        console.log("param is: " + JSON.stringify(param));
        newCtrl.notify(param);
      };
      console.log(" $scope.ctrlFn is: " + JSON.stringify($scope.ctrlFn));
    });
    
    function newCtrl() {
      return {
        notify: notify
      }
    
      function notify(param) {
        console.log("In newCtrl.notify, param is: " + JSON.stringify(param));
      };
    }
    
    app.directive('myDirective', function() {
      return {
        restrict: 'E',
        scope: {
          ctrlFn: '&'
        },
        templateUrl: "./myPage.html",
        link: function(scope, element, attributes, controller) {
          scope.ctrlFn = scope.ctrlFn();
        }
      };
    });
    
    app.factory("newCtrl", newCtrl);
    

    (I wouldn't really call it newCtrl though, as it's now a service).

    See: https://plnkr.co/edit/5N957XYFPZVndr75lQ8N?p=preview

    If you need the $scope in the service, you could pass that in as a parameter.

    If you don't need the anonymous function in mainCtrl, you could go one step further and do this:

    $scope.ctrlFn = newCtrl.notify;
    

    EDIT:

    A bit more complex (and I apologize for the naming of all my params/properties as ngModel), but this is what I tend to do, when I want to have a directive that gets passed data from the page controller, then that directive opens a modal, and based on user interaction, changes that value of the data (model) which gets updated back through the directive to the initial controller/view:

    1. I create a directive, on which I set the ng-model property in my (main controller/page) view, to a property on the controller $scope.

    2. The directive can access (read & write) this value on the isolated scope and can then use it in the template.

    3. On a user action, the directive opens a modal and passes the value in to the modal controller via the resolve property of bootstrap's modal.open method. I typically pass in multiple values so I put them all in an 'options' composite parameter.

    4. The modal controller receives this value (in the options) as it gets injected into the modal controller [the resolve property provides it when it gets requested by the modal controller].

    5. The modal controller can then set this value into the modal view (via the modal $scope/vm). Typically, I load data in the modal dialogue, e.g. show a list of items for the user to pick from.

    6. On the Close event/action, I return the value (altered by user interaction or not) to the caller [being the directive's controller], which sets it back onto the directive's [isolated] scope, which essentially updates the property on the mainController, as it is bound via the = value.

    https://plnkr.co/edit/qAa1pOwigCgJ99Lx6vsj?p=preview