Skip to content Skip to sidebar Skip to footer

Angular 1.6 Component Transclusion Scope

I'm trying to figure out how to get data into a component transclusion in Angular 1.6.4. The scenario has a component, a directive (not re-written as a component yet) and a service

Solution 1:

I've figured it out. In passing, I should note that according to the literature on the Internet, I'm doing something that I probably shouldn't do. I understand where the authors of Angular are coming from with trying to isolate scopes down the chain but I don't agree with that model, at least for transclusion.

angular.module('app')

  .service('svc', function() {
    this.connector = {};
  })

  .directive('first', ['svc', function($svc) { return {
    restrict: 'E',
    scope: { 'id': '@' },
    template: '<button ng-click="GetData()">get data</button>',
    controller: ['$scope', 'svc', function($scope, $svc) {
      $scope.connector = { data: [] };
      $svc.connector[$scope.id] = $scope.connector;
      $scope.GetData = function() {
        // This is a mock-up; I'm really doing a REST call.$scope.connector.data = [
          {id: 0, name: 'one'},
          {id: 1, name: 'two'}
        ];
        $scope.connector.data.Update($scope.connector.data);
      };
    }]    
  }; }])

  .component('second', {
    bindings: { parent: '@firstid' },
    transclude: true,
    template: '<ng-transclude></ng-transclude>',
    controller: ['$element', '$transclude', '$compile', 'svc', function($element, $transclude, $compile, $svc) {
      this.$onInit = () => { angular.extend(this, $svc.connector[this.parent]; };
      var parentid = $element.attr('firstid');
      $transclude((clone, scope) => {
        $svc.connector[parentid].Update = (data) => {
          angular.extend(scope, data);
          $element.append($compile(clone)(scope));
        };
      });
    }]
  })

;

How it works

This is essentially manual transclusion. There are too many examples on the Internet about manual transclusion where people modify the DOM manually. I don't completely understand why some people think this is a good idea. We jump through so many hoops to separate our markup (HTML) from our formatting (CSS) from our code (Angular directives/components) from our business logic (Angular services/factories/providers), so I'm not going to go back to putting markup inside my code.

I found this article and a comment on an Angular issue by Gustavo Henke that used the scope inside $transclude to register a callback. With that key bit of information, I figured I could do much more scope manipulation.

The code in $transclude seems to be outside the digest cycle. This means that anything touched inside it will not receive automatic updates. Luckily, I have control of my data's change events so I pushed through this callback. On the callback, the data are changed and the element is recompiled. The key to locate the callback in the service hasn't been bound from the controller tag yet so it has to be retrieved from the attributes manually.

Why this is bad

Components are not supposed to modify data outside their own scope. I am specifically doing exactly not-that. Angular doesn't seem to have a more appropriate primitive for doing this without breaking some other concern that's more important to leave intact, in my mind.

I think there's a, "memory leak," in this, which is to say that my element and scope aren't being disposed of correctly with each update cycle. Mine uses fairly little data, it is updated only directly by the user with a throttle and it's on an administration interface; I'm okay with leaking a little memory and I don't expect the user will stay on the page long enough for it to make a difference.

My code all expects things to be in the right place and named the right things in the markup. My real code has about four times as many lines as this and I'm checking for errors or omissions. This is not the Angular way which means I'm probably doing something wrong.

Credits

Without the Telerik article, I would have been sitting next to an even bloodier mark on my wall right now.

Thanks to Ben Lesh for his comprehensive post about $compile with appropriate disclaimers about how one shouldn't use it.

Todd Motto helped a bunch with how to write a decent Angular 1.x component in his post on upgrading to 1.6. As one may expect, the Angular documentation on components doesn't offer much more than specific pointers to exactly what things are called.

There's a little information at the bottom of AngularJS issue 7842 that does something similar and may even have a better method for managing scoped data more appropriately than I did.

Post a Comment for "Angular 1.6 Component Transclusion Scope"