Andre Vianna
Andre Vianna

Reputation: 1732

Angular directive is not behaving as expected. I need some help figuring out why

I have been trying to get a dynamic behavior from a composition of directives. Here is the code I am using for sampler.js and index.html:

"use strict";
var app = angular.module("myApp", []);
var Sampler = (function () {
    function Sampler(sampler) {
        this.sampler = sampler;
        this.name = null;
        this.value = null;
        if (sampler) {
            this.name = sampler.name;
            this.value = sampler.value;
        }
    }
    Sampler.prototype.getTemplateFor = function (file) {
        return 'templates/' + name + '/' + file + '.html';
    };
    Sampler.prototype.addA = function () {
        this.value = 'A';
    };
    Sampler.prototype.addB = function () {
        this.value = 'B';
    };
    Sampler.create = function (name) {
        var samplerClass = name + 'Sampler';
        var items = samplerClass.split('.');
        var creator = (window || this);
        for (var i = 0; i < items.length; i++) {
            creator = creator[items[i]];
        }
        if (typeof creator !== 'function') {
            throw new Error('Class named ' + samplerClass + ' not found.');
        }
        var sampler = new creator({
            name: name
        });
        if (!(sampler instanceof Sampler)) {
            throw new Error(name + ' is not instance of Sampler.');
        }
        return sampler;
    };
    return Sampler;
}());
app.directive("sampler", function ($compile) {
    return {
        restrict: "E",
        scope: { result: '=' },
        link: function (scope, element, attributes) {
            var name = !attributes.name ? '' : attributes.name;
            var sampler = Sampler.create(name);
            scope.sampler = sampler;
            var template = '<div class="sampler form-horizontal">' +
                '    <sampler-item ng-if="!!sampler.value" sampler="sampler" />' +
                '    <sampler-new ng-if="!sampler.value" sampler="sampler" />' +
                '</div>';
            if (name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('sampler'),
                    success: function (response) { template = response; },
                });
            }
            var content = $compile(template)(scope);
            element.replaceWith(content);
            scope.$watch('sampler.value', function () {
                scope.result = scope.sampler.value;
            });
        }
    };
});
app.directive("samplerNew", function ($compile) {
    return {
        restrict: "E",
        scope: { sampler: '=' },
        link: function (scope, element) {
            var sampler = scope.sampler;
            var template = '\
<div class="new">\
    <button type="button" class="btn btn-default" ng-click="sampler.addA()">Add A</button>\
    <button type="button" class="btn btn-default" ng-click="sampler.addB()">Add B</button>\
</div>';
            if (sampler.name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('new'),
                    success: function (response) { template = response; },
                });
            }
            var content = $compile(template)(scope);
            element.replaceWith(content);
        }
    };
});
app.directive("samplerItem", function ($compile) {
    return {
        restrict: "E",
        scope: { sampler: '=' },
        link: function (scope, element) {
            var sampler = scope.sampler;
            var template = '\
<div class="item">\
    Item: {{sampler.value}}\
</div>';
            if (sampler.name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('sampler'),
                    success: function (response) { template = response; },
                });
            }
            var content = $compile(template)(scope);
            element.replaceWith(content);
        }
    };
});
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
</head>
<body ng-app="myApp">
    <sampler result="result"></sampler>
    Expression: {{result}}

    <script src="lib/jquery/jquery-3.1.1.js"></script>
    <script src="lib/angular/js/angular.js"></script>
    <script src="js/directives/sampler.js"></script>
</body>
</html>

When the page loads the output is:

After page loads

After you press a button the expected result should be:

Expected result

But the result is:

Actual result

Please note that I am using link to load the template because I need to load a dynamic template with fallback to a default one.

Thinks works fine if I use the template property of the directive but that does not suits me because of the dynamic template so please do not send this as an answer.

Can anyone help me on that? Thanks

Upvotes: 1

Views: 70

Answers (2)

Andre Vianna
Andre Vianna

Reputation: 1732

I thank the answer by Stephen Tillman (https://stackoverflow.com/users/7181162/stephen-tillman), and it is a working possible solution. But after tinkering with the problem a little more, I found a solution that behaves exactly how I expected.

The main solution is to take possession of the behavior ngIfDirective directive and replace it with our own implementation. Also we have to remember to replace the original element with the new one after doing a replaceWith (instead of an append).

Here is the the code of the sampler.ts the works completely as expected:

// <reference path="../../lib/typings/jquery/jquery.d.ts" />
// <reference path="../../lib/typings/angular/angular.d.ts" />
"use strict";

interface ISampler {
    name: string;
    value: string;

    getTemplateFor(file: string): string;
    addA(): void;
    addB(): void;
    clear(): void;
}

class Sampler implements ISampler {
    public name: string = null;
    public value: string = null;

    constructor(public sampler?: ISampler) {
        if (sampler) {
            this.name = sampler.name;
            this.value = sampler.value;
        }
    }

    public getTemplateFor(file: string): string {
        return 'templates/' + name + '/' + file + '.html';
    }

    public addA(): void {
        this.value = 'A';
    }

    public addB(): void {
        this.value = 'B';
    }

    public clear(): void {
        this.value = null;
    }

    static create(name: string): ISampler {
        var samplerClass = name + 'Sampler'
        var items = samplerClass.split('.');

        var creator = (window || this);
        for (var i = 0; i < items.length; i++) {
            creator = creator[items[i]];
        }

        if (typeof creator !== 'function') {
            throw new Error('Class named ' + samplerClass + ' not found.');
        }

        var sampler = new creator(<ISampler>{
            name: name
        });
        if (!(sampler instanceof Sampler)) {
            throw new Error(name + ' is not instance of Sampler.');
        }

        return sampler;
    }
}

app.directive("sampler", ($compile) => {
    return {
        restrict: "E",
        scope: { result: '=' },
        link: ($scope: ng.IScope | any, $element, $attrs) => {
            var name = !$attrs.name ? '' : $attrs.name;
            var sampler = Sampler.create(name);
            $scope.sampler = sampler;
            var template =
'<div class="sampler form-horizontal">' +
'    <sampler-item ng-if="!!sampler.value" sampler="sampler"></sampler-item>' +
'    <sampler-new ng-if="!sampler.value" sampler="sampler"></sampler-new>' +
'</div>';
            if (name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('sampler'),
                    success: (response) => { template = response; },
                });
            }
            var newElement = $compile(template)($scope);
            $element.replaceWith(newElement);
            $element = newElement;
            $scope.$watch('sampler.value', () => {
                $scope.result = $scope.sampler.value;
            });
        }
    }
});

app.directive("samplerNew", (ngIfDirective, $compile) => {
    var ngIf = ngIfDirective[0];
    return {
        restrict: "E",
        priority: ngIf.priority + 1,
        terminal: true,
        scope: { sampler: '=' },
        link: ($scope: ng.IScope | any, $element, $attrs) => {
            var sampler = $scope.sampler;
            var comment = '<!-- show: ' + $attrs.show + ' -->';
            var template = '\
<div class="new">\
    <button type="button" class="btn btn-default" ng-click="sampler.addA()">Add A</button>\
    <button type="button" class="btn btn-default" ng-click="sampler.addB()">Add B</button>\
</div>';
            if (sampler.name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('new'),
                    success: (response) => { template = response; },
                });
            }
            $scope.$watch($attrs.ngIf, (isVisible) => {
                var newElement = $compile(isVisible ? template : comment)($scope);
                $element.replaceWith(newElement);
                $element = newElement;
            });
        }
    };
});

app.directive("samplerItem", (ngIfDirective, $compile) => {
    var ngIf = ngIfDirective[0];
    return {
        restrict: "E",
        priority: ngIf.priority + 1,
        terminal: true,
        scope: { sampler: '=' },
        link: ($scope: ng.IScope | any, $element, $attrs) => {
            var sampler = $scope.sampler;
            var comment = '<!-- show: ' + $attrs.show + ' -->';
            var template = '\
<div class="item">\
    Item: {{sampler.value}}<br />\
    <button type="button" class="btn btn-default" ng-click="sampler.clear()">Clear</button>\
</div>';
            if (sampler.name) {
                $.ajax({
                    async: false,
                    url: sampler.getTemplateFor('new'),
                    success: (response) => { template = response; },
                });
            }
            $scope.$watch($attrs.ngIf, (isVisible) => {
                var newElement = $compile(isVisible ? template : comment)($scope);
                $element.replaceWith(newElement);
                $element = newElement;
            });
        }
    };
});

Upvotes: 0

user7181162
user7181162

Reputation:

After you $compile the template for the samplerNew directive, then you are using the compiled content to replace the original element - the one that has the ng-if attribute. Hence, ng-if has no effect on the <sampler-new> element because it gets replaced each time it's rendered.

So, try taking your ng-if attribute off the <sampler-new> element and put it on the <div class="new"> element where you compile the samplerNew directive.

The Fix

  1. Go to your sampler directive
  2. Find the string literal assigned to the template variable inside the link function
  3. Cut ng-if="!sampler.value" from the <sampler-new> element
  4. Scroll down to your samplerNew directive
  5. Find the string literal assigned to the template variable inside the link function
  6. Paste ng-if="!sampler.value" on to the <div class="new"> element

Now, when you click Add A or Add B the buttons will disappear and your Item and Expression fields will display.

Upvotes: 1

Related Questions