Scope doesn't get associated with new element

2019-09-03 20:15发布

问题:

In below code i am trying to add a button at compile phase, and assigned ng-click to a method from scope. During linking phase, through debugging I found the "compiledEle" contains the button, then also ng-click doesn't call the scope method.

angular.module("app", [])
        .controller("ctrl1", function($scope){
            $scope.myModelObj = {
                name: 'Ratnesh',
                value: 100
            };
        })
        .directive("dirOne",function($compile){
            return {
                restrict: 'E',
                scope: {
                    myModel : "="
                },
                compile: function(ele){
                    ele.append("<button ng-click=\"showAlert()\">click ME</button>")
                    return {
                        post: function($scope, compiledEle){
                            $scope.showAlert = function(){
                                alert("The button is clicked");
                            };
                        }
                    };
                }

            };
        });

What could be the reason that the scope method doesn't get binded to button added during compile phase, but the same can be binded if I button in the template/templateUrl. Also the method get bind if in the linking phase we include a line:

$compile(compiledEle.contents())($scope);)

Also it will be get bind to the method if instead of adding "$scope.showAlert" in linking phase , we already have the method in controller !!!

    .controller("ctrl1", function($scope){
                $scope.myModelObj = {
                    name: 'Ratnesh',
                    value: 100
                };
                $scope.showAlert = function(){
                    alert("The button is clicked");
                };
            })

compile method is to do DOM manipulation and linking phase is to link compiled html to scope. So we can add new element to DOM during compile and new scope method in linking phase, so where is my expectation is getting wrong?

回答1:

The problem is that your compile function does not have access to the scope of the element instance yet at the moment of compilation.

You want ng-click to execute a method of the instance scope, which is not yet available when the template is compiled.

I have added comments to the code to illustrate what happens:

app.directive("fromCompile", function($compile) {
  return {
    restrict: 'E',
    scope: {},
    compile: function(tElement) {

      // When AngularJS compiles the template element, it does not have
      // access yet to the scope of the iElement, because the iElement does
      // not exist yet.
      // You want ng-click to execute a method of the iElement scope
      // which does not exist here yet because you are modifying the
      // template element, not the instance element.
      // This will not give you the effect you are looking for because it
      // will execute the function in a scope higher up the scope hierarchy.
      tElement.append('<button ng-click=\"showAlert()\">Using compile: click me (this will not work correctly)</button>');
      return {
        post: function(scope, iElem, iAttrs) {
          scope.showAlert = function() {
            alert("This button was added using compile");
          };
        }
      };
    }
  };
});

To solve the problem you can either use a template to let AngularJS automatically compile the template for you:

app.directive("fromTemplate", function($compile) {
  return {
    restrict: 'E',
    scope: {},
    template: "<button ng-click=\"showAlert()\">Using template: click me (this will work)</button>",
    link: function(scope, iElem, iAttrs) {
      scope.showAlert = function() {
        alert("This button was added using template");
      };
    }
  };
});

Or compile the template manually yourself in the link function of the element instance (because you can access the correct scope there):

app.directive("fromLink", function($compile) {
  return {
    restrict: 'E',
    scope: {},
    link: function(scope, iElem, iAttrs) {
      var linkFn = $compile("<button ng-click=\"showAlert()\">Using link: click me (this will work)</button>");
      var button = linkFn(scope);
      iElem.append(button);
      scope.showAlert = function() {
        alert("The button was added using $compile in link function");
      };
    }
  };
});

I have created a Plunk with all code and working versions right here.

Hope that helps!