1
votes

I'm trying to create an AngularJS directive to work with a modal window and I can't figure out how to isolate the scope. My goal is to have a modal window to add a new location. I want the directive to populate the modal with any needed data from the parent scope and handle the save button click. I got this working using the parent scope, but I have not been able to get it working with isolated scope.

The parent controller has a locations property, which in this case is the only parent property I need. The newLocation object should be isolated from the parent scope. When I add the isolated scope, the name input is not populated and the click handler doesn't fire. The scope declaration only allows @, =, and & properties so I can't augment it there, and adding properties in link has no effect.

Directive:

        angular.module('pw').directive('addLocationModal', [
        'appApi', function (appApi) {
            return {
                restrict: 'A',
                scope: {
                    locations: '='
                },
                link: function (scope, element, attrs) {
                    scope.newLocation = {
                        name: 'xxx'
                    };
                    scope.saveLocation = function () {
                        alert('save location');
                    };
                }
            };
        }
    ]);

Modal:

<div class="modal fade" id="add-location-modal" add-location-modal>
<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal">
                <span>&times;</span>
            </button>
            <h4 class="modal-title">Add Location</h4>
        </div>
        <div class="modal-body">
                <div class="form-group">
                    <label>Name</label>
                    <input type="text" class="form-control" ng-model="newLocation.name" required />
                </div>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            <button type="button" class="btn btn-primary" ng-click="saveLocation()">Save</button>
        </div>
    </div>
</div>

2
can you provide jsfiddle or plnkr?Zeeshan Hassan Memon
If you need to keep scope isolated, you should pass initial value using prefix @ and use prefix & to let parent scope know the changes made to location on save functionZeeshan Hassan Memon
I created a Plunker -- plnkr.co/edit/FxtSPHR3QFJOd0PTnMiI but it doesn't work even without the isolated scope and I'm out of patience with this.Jamie Ide
be far simpler by getting rid of bootstrap.js and using angular-ui-bootstrap. No need to re-invent the wheelcharlietfl
Interesting project but I don't want to fully commit to Angular.Jamie Ide

2 Answers

1
votes

you need to move div

<div class="modal-dialog">...

to the template of the directive

return {
    restrict: 'A',
    templateUrl : 'view.html',
    scope: {
       locations: '='
    },
    link: function(scope, element, attrs) {
         scope.newLocation = {
             name: 'xxx'
         };
        scope.saveLocation = function() {
           alert('save location');
        };
    }
};

in view.html

<div class="modal-dialog">
   .....
   .....
</div>

in index.html

<div class="modal fade" id="add-location-modal" add-location-modal>

</div>

directive html should compile against the directive scope (to bind the scope data and the functions to the view more likely bind the directives like to the scope like ng-click, ng-model if that not compiled against the desired scope its not going to bahave as we expected), here you have no directive template and you assume html between the directive div in index.html behaves as the directive html but its actually its not.

move the inner content of the <div class="modal fade" id="add-location-modal" add-location-modal> to the template of the directive will solve this. Because directives ng-model and the ng-click are compiled against the directive scope not it's parent scope.

here is the simple DEMO

update - solution for your first comment

you can compile the html between directive div and append to same element in directive compile function as,

compile: function(tElement, tAttr, transclude) {
  var contents = tElement.contents().remove();
  var compiledContents;
  return function(scope, iElement, iAttr) {
    if (!compiledContents) {
      compiledContents = $compile(contents, transclude);
    }
    compiledContents(scope, function(clone, scope) {
      iElement.append(clone);
    });
  };
},

and move your scope properties to directive controller function

 controller: function($scope) {

     $scope.newLocation = {
        name: 'xxx'
     };

     $scope.saveLocation = function() {
        alert('save location');
     };
}

here is the updated DEMO

1
votes

I think I might have got it. The modal is not working with an open and close, but I forked @K.Toress's plunk and I think I got the isolate scope working.

http://plnkr.co/edit/8DwJqc?p=preview

The important pieces.

  1. You need to use a controller as well as a directive. You need the scope in the controller to start with.

    app.controller('MainCtrl', function($scope) {
      $scope.locations = [{name: "loc1", level: 1}, {name: "loc2", level: 2}]
    });
    
  2. When you define the directive, the directive scope will have a directiveLocations object.

    scope: {
      directiveLocations: '=locations-attr'
    },
    
  3. It gets the name of this object from the locations-attr attribute on the element, which is "locations".

    <div class="modal" add-location-modal locations-attr="locations">
    
  4. So now we have mapped the controller $scope.locations to the directiveLocations in the directive scope.

  5. For saving the location back to the parent controller scope, you simply append to the directiveLocations list since it is now the same as the parent $scope.locations.

    var tempLocation = {
      name: scope.newLocation.name,
      level: scope.newLocation.level
    }
    scope.directiveLocations.push(tempLocation);
    

I'm not sure why I had to create a new object here. There was something strange about adding the same object back into the parent scope.