7

I'm trying to dynamically compile an Angular component using $compile, but the scope isn't passed to the components scope, but to the $parent scope instead.

Here is a simple component that binds to a myTitle-attribute and presents it:

app.component('myComponent', {
  bindings: {
    myTitle: '<'
  },
  template: `
    <div>
      <div>Doesn't work: {{ $ctrl.myTitle}}</div>
      <div>Works: {{ $parent.$ctrl.myTitle}}</div>
    </div>`
});

Then in the controller (or directive, etc.) I compile it using $compile:

app.controller('MainCtrl', function($scope, $element, $compile) {
  var template = '<my-component></my-component>';
  
  var bindings = {
    myTitle: 'My Title'
  }
  var scope = angular.extend($scope.$new(true), {
    $ctrl: bindings
  });
  var newElement = $compile(template)(scope);
  $element.append(newElement);
  
});

When running this, it yield the result:

Doesn't work:

Works: My Title

Here's a plunker showing it in action

The question

How come the scope I create for the dynamically created component, is passed as a parent scope of the component?

Any pointer on why angular behaves like this and perhaps how to avoid it is much welcome.

Community
  • 1
  • 1
Nikolaj Dam Larsen
  • 5,455
  • 4
  • 32
  • 45

1 Answers1

7

As I see, you need to pass binding here var template = '<my-component></my-component>';

var template = '<my-component my-title="$ctrl.myTitle"></my-component>';

Full component may be like this:

app.controller('MainCtrl', function($scope, $element, $compile) { 
  var template = '<my-component my-title="$ctrl.myTitle"></my-component>'; 
  $scope.$ctrl = {myTitle: 'My Title'}; 
  $element.append($compile(template)($scope)); 
});
John Doe
  • 547
  • 3
  • 11
  • That doesn't make a difference. – Nikolaj Dam Larsen May 30 '17 at 12:46
  • You need to be aware, that you can't pass the scope to the component, because it has it's own isolate scope. To have binding variable evaluated, you need to pass it to component. `app.controller('MainCtrl', function($scope, $element, $compile) { var template = ''; this.myTitle = 'My Title'; $element.append($compile(template)($scope)); });` – John Doe May 30 '17 at 12:50
  • The code posted in the answer won't work for some reasons. I would suggest to test your code before posting it. The OP has even provided a plunk. – Estus Flask May 30 '17 at 13:34
  • Sorry, you're right. `this.myTitle = 'My Title';` should be changed to `$scope.$ctrl = {myTitle: 'My Title'};` I'll update my answer – John Doe May 30 '17 at 13:54
  • It also should be `''`, because it is `<` binding. – Estus Flask May 30 '17 at 14:13
  • While I had hoped there would be a cleaner way to "override"/extend the isolated scope of components when compiling them, aften going through the Angular source code, I agree this might be the most clean way of doing this. So I'll accept this answer for now. – Nikolaj Dam Larsen Jul 11 '17 at 08:17