Reputation: 1732
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 you press a button the expected result should be:
But the result is:
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
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
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
sampler
directivestring
literal assigned to the template
variable inside the link
functionng-if="!sampler.value"
from the <sampler-new>
elementsamplerNew
directivestring
literal assigned to the template
variable inside the link
functionng-if="!sampler.value"
on to the <div class="new">
elementNow, when you click Add A
or Add B
the buttons will disappear and your Item and Expression fields will display.
Upvotes: 1