When using $compile on component, why is the scope passed through $parent?
Asked Answered
C

1

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.

Creamcups answered 30/5, 2017 at 12:35 Comment(0)
P
8

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)); 
});
Petulant answered 30/5, 2017 at 12:42 Comment(6)
That doesn't make a difference.Creamcups
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 = '<my-component my-title="$ctrl.myTitle"></my-component>'; this.myTitle = 'My Title'; $element.append($compile(template)($scope)); });Petulant
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.Spanish
Sorry, you're right. this.myTitle = 'My Title'; should be changed to $scope.$ctrl = {myTitle: 'My Title'}; I'll update my answerPetulant
It also should be '<my-component my-title="\'My Title\'"></my-component>', because it is < binding.Spanish
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.Creamcups

© 2022 - 2024 — McMap. All rights reserved.