0

my parent directive is fed with a file containing informations about site structure and builds a template string dynamically. The content of this string refers to other directives. These might fetch as well data from a source to build (eg) tables.

As they are all using $http.get to fetch data I wanted to inform the parent directive when these sub directives are ready using a required controller.

Problem: The parent directive is using $compile to build the site and does not "forward" the controller which results in "Error: [$compile:ctreq] http://errors.angularjs.org/1.4.1/$compile/ctreq ..."

Found already this answer: "Angular $compile with required controller" which is not a big help, especially as transcludedControllers seems to be deprecated and doesn't work in my code.

Any help or just pointing to another already asked /answered question is highly appreciated.

angular.module('TestDirectives', [])
.directive('widgetBlock', function ($compile) {
    return {
        restrict: 'A',
        replace: true,
        controller: function ($scope) {
            this.reportReady = function () {
                $scope.widgetready = String(Number($scope.widgetready) + 1);
            }
        },
        link: function (scope, elem, attr) {

            // template data will be constructed dynamically based on a 
            // xml - file fetched with $http.get
            var template = '<div><p >This isn\'t always fun</p>' +
                '<div mini-widget></div>' +
                '<div micro-widget></div></div>';

            var content = $compile(template)(scope)
            elem.append(content);


            attr.$observe('widgetready', function (newValue) {
                // quantity of widgets depends on widgets found in xml - file
                if (newValue === "2") {
                    // all widgets on screen, translate some keys
                    console.info("Application is ready to translate!!");
                }
            });
        }

    };
})
.directive('miniWidget', function ($compile, $http) {
return {
    restrict: 'A',
    require: '^widgetBlock',
    scope: {},
    link: function (scope, elem, attr, ctrl) {
        $http.get('json/daten.json').then(function (data) {
            scope.person = data.data;
            var test = '<p>hello {{person.name}}</p>';
            var content = $compile(test)(scope)
            elem.append(content);
            ctrl.reportReady();
        });
    }
}
})

.directive('microWidget', function ($compile, $http) {
return {
    restrict: 'A',
    require: '^widgetBlock',
    scope: {},
    link: function (scope, elem, attr, ctrl) {
        $http.get('json/daten2.json').then(function (data) {
            scope.person = data.data;
            var test = '<p>Whatever {{person.name}}</p>';
            var content = $compile(test)(scope)
            elem.append(content);
            ctrl.reportReady();
        });
    }
}
});

Just to be complete, main app

angular.module('DirectiveTestApp', ['TestDirectives'])
.controller('MainController', function ($scope) {
    $scope.widgetready = "0";
});

And the html

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Directives Test</title>
</head>
<body ng-app="DirectiveTestApp">
    <div ng-controller="MainController">
        <div widget-block widgetready="{{widgetready}}">

        </div>
    </div>
    <script src="bower_components/angular/angular.min.js"></script>
    <script src="script/app.js"></script>
    <script src="script/directives.js"></script>
</body>
</html>

Thanks a lot!

Community
  • 1
  • 1
danny
  • 43
  • 7

1 Answers1

0

When you $compile and then link - which is what you are doing with $compile(template)(scope) - during the link-phase, the microWidget directive looks for the required controller up the DOM tree. The problem is that your template is not in the DOM at that time.

There are 2 ways to address this:

#1: using the cloneAttachFn:

The second parameter of the link function, which is the result of $compile(template), allows you to specify a cloneAttachFn - see documentation. This function is invoked prior to link-phase and it gets a cloned version of the to-be-linked node. Use that function to place the element where needed:

var content = $compile(template)(scope, function cloneAttachFn(clonedContent){
  // this happens prior to link-phase of the content
  elem.append(clonedContent);
});

// this happens after link-phase of the content, so it doesn't work
// elem.append(content)

(btw, content[0] === clonedContent[0])

#2: append content prior to $compile/link (as suggested in an answer to this question):

var content = angular.element(template);
elem.append(content);
$compile(content)(scope);

Demo

Community
  • 1
  • 1
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • This answer was really helpful to me; should have been looking a bit deeper into the docs :-o. I was trying to use #2 in the sub directives but it should be done on the parent. – danny Jul 20 '15 at 09:20