3

I am building a huge form that calls various directives to build a complete form. The Main Page calling the Form Builder passes the ng-model data like this:

<div form-builder form-data=“formData”></div>

Then the Form Builder Page calls various child directive to build various sections of the Form:

FormBuilder.html:

<div form-fields></div>
<div photo-fields></div>
<div video-fields></div>
 .. etc.. etc...

When using $scope in controller, I had no problem accessing the $scope in the child directives like this:

function formBuilder() {
    return {
         restrict: 'A',
         replace: true,
         scope: {
            formData: '='
         },
         templateUrl: 'FormBuilder.html',
         controller: function($scope) {
            $scope.formSubmit = function() {
            // Submits the formData.formFields and formData.photoFields
            // to the server
            // The data for these objects are created through 
            // the child directives below
         }
     }
   }
}

function formFields() {
    return {
            restrict: 'A',
            replace: true,
            templateUrl: 'FormFields.html',
            controller: function($scope) {
               console.log($scope.formData.formFields);
            }
    }
}

function photoFields() {
    return {
            restrict: 'A',
            replace: true,
            templateUrl: 'PhotoFields.html',
            controller: function($scope) {
               console.log($scope.formData.photoFields);
            }
   }
}
... etc..

But ever since I got rid of the $scope and started using ControllerAs, I am having all sorts of trouble accessing 2 way binding with the Parent - Child Controllers.

function formBuilder() {
    return {
         restrict: 'A',
         replace: true,
         scope: {
           formData: '='
         },
         templateUrl: 'FormBuilder.html',
         controller: function() {
              var vm = this;
             console.log(vm.formData);  // Its fine here

             vm.formSubmit = function() {
               // I cannot change formData.formFields and formData.photoFields 
               // from Child Directive "Controllers"
            }
        },
        controllerAs: ‘fb’,
        bindToController: true
   }
}

function formFields() {
    return {
            restrict: 'A',
            replace: true,
            templateUrl: 'FormFields.html',
            controller: function() {
                var vm = this;
                console.log(vm.formData.formFields); 
                // No way to access 2 way binding with this Object!!!
            }
   }
}

function photoFields() {
    return {
        restrict: 'A',
        replace: true,
        templateUrl: 'PhotoFields.html',
        controller: function() {
            var vm = this;
            console.log(vm.formData.photoFields); 
            // No way to access 2 way binding with this Object!!!
        }
    }
}

Whatever I try, I am reaching a road block. Things I have tried are:

  1. Isolated Scopes: I tried passing formData.formFields and formData.photoFields as isolated scopes to the child directive, but I then end up getting the $compile: MultiDir error due to nested isolated scopes so it is not possible.
  2. If I don’t have individual directives for each form section and have all of them in 1 directive under formBuilder directive, then it becomes a humungous directive. The above is just a sketch but each child directive builds 1 big form put together in the end. So merging them together is really the last resort since it does become hard to maintain and unreadable.
  3. I don’t think there is a way to access Parent directive’s ControllerAs from Child Directive's Controller any other way from what I have seen so far.
  4. If I use the parent’s ControllerAs in the child directive template’s ng-model like <input type=“text” ng-model=“fb.formData.formFields.text" />, that works fine, but I need to access the same from the Child directive’s controller for some processing which I am unable to do.
  5. If I get rid of the controllerAs and use the $scope again, it works like before but I am trying to get rid of the $scope altogether to prepare myself for future Angular changes.

Since it is an advanced form, I need to have separate directive to handle various form sections and since nested isolated scopes are not allowed since Angular 1.2, it is making it ever harder especially when trying to get rid of $scope using ControllerAs.

Can someone guide me what are my options here please? I thank you for reading my long post.

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
Neel
  • 9,352
  • 23
  • 87
  • 128

1 Answers1

2

Basically you need to use require option of directive (require option is used for communicate directive with directive). Which will give access to its parent controller by just mentioning require option in child directive. Also you need to use bindToController: true which will basically add isolated scope data to the directive controller.

Code

function formBuilder() {
    return {
         restrict: 'A',
         replace: true,
         bindToController: true, 
         scope: {
            formData: '='
         },
         templateUrl: 'FormBuilder.html',
         controller: function($scope) {
            $scope.formSubmit = function() {
            // Submits the formData.formFields and formData.photoFields
            // to the server
            // The data for these objects are created through 
            // the child directives below
         }
     }
   }
}

Then you need to add require option to child directives. Basically the require option will have formBuilder directive with ^(indicates formBuilder will be there in parent element) like require: '^formBuilder',.

By writing a require options you can get the controller of that directive in link function 4th parameter.

Code

function formFields() {
    return {
        restrict: 'A',
        replace: true,
        require: '^formBuilder',
        templateUrl: 'FormFields.html',
        //4th parameter is formBuilder controller
        link: function(scope, element, attrs, formBuilderCtrl){
            scope.formBuilderCtrl = formBuilderCtrl;
        },
        controller: function($scope, $timeout) {
            var vm = this;
            //getting the `formData` from `formBuilderCtrl` object
            //added timeout here to run code after link function, means after next digest
            $timeout(function(){
                console.log($scope.formBuilderCtrl.formData.formFields);
            })
        }
    }
}

function photoFields() {
    return {
        restrict: 'A',
        replace: true,
        require: '^formBuilder',
        templateUrl: 'PhotoFields.html',
        //4th parameter is formBuilder controller
        link: function(scope, element, attrs, formBuilderCtrl){ 
            scope.formBuilderCtrl = formBuilderCtrl;
        },
        controller: function($scope, $timeout) {
            var vm = this;
            console.log(vm.formData.photoFields);
            //to run the code in next digest cycle, after link function gets called.
            $timeout(function(){
                console.log($scope.formBuilderCtrl.formData.formFields);
            })
        }
    }
}

Edit

One problem with above solution is, in order to get access to the controller of parent directive in directive controller it self, I've did some tricky. 1st include the the formBuilderCtrl to the scope variable from link function 4th parameter. Then only you can get access to that controller using $scope(which you don't want there). Regarding same issue logged in Github with open status, you could check that out here.

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
  • 1
    That is a fantastic solution @pankajParkar. I will try that right away. Thank you so much as I was stuck on this for 3 days trying to figure out a solution! However, I do have a question on `scope` in `link` function. From what I read, Angular 2 will probably be getting rid of `$scope`, so would the above break on Angular 2 since the `scope` is used in `link()`? I understand, its probably too early to prepare for Angular 2, but your thoughts on this as off the rumours today? – Neel Dec 06 '15 at 11:24
  • 1
    @Neel yes you are correct.. I understood that you are using `controllerAs` pattern come close to angular2 migration.. but there is no solution from my end on that. We need to have scope variable in `controller` & `link` function. there is still open bug on AngularJS issue list https://github.com/angular/angular.js/issues/5893 – Pankaj Parkar Dec 06 '15 at 11:37
  • Thanks for your input @PankajParkar and +1 for the link. I hope Angular 2 comes up with a elegant way to do this. But for now, your solution will help me. Thanks again. :) – Neel Dec 06 '15 at 11:44
  • 1
    @Neel angular2 have better implementation for this using DI..if you read up on the link which I've provided to you.. You will come to know many things.. – Pankaj Parkar Dec 06 '15 at 11:45