I'm working on a page that is made up of 5 directives, for example:
<directive-one></directive-one>
<directive-two></directive-two>
<directive-three></directive-three>
<directive-four></directive-four>
<directive-five></directive-five>
I would like to be able to re-order these on demand so that a user can control how their page looks. The only way I could think of doing that was putting them in an ng-repeat:
$scope.directiveOrder = [{
name: "directive-one",
html: $sce.trustAsHtml('<directive-one></directive-one>'),
order: 1
}, ...
HTML:
<div ng-repeat="directive in directiveOrder" ng-bind-html="directive.html">
{{directive.html}}
</div>
This will give me the right tags, but they aren't processed as directives by angular. Is there a way around that? I'm assuming it's something to do with $sce not handling it, but I might be way off?
Try creating a new directive and using $compile to render each directive:
https://jsfiddle.net/HB7LU/18670/
http://odetocode.com/blogs/scott/archive/2014/05/07/using-compile-in-angular.aspx
HTML
<div ng-controller="MyCtrl">
<button ng-click="reOrder()">Re-Order</button>
<div ng-repeat="d in directives">
<render template="d.name"></render>
</div>
</div>
JS
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
})
.directive("render", function($compile){
return {
restrict: 'E',
scope: {
template: '='
},
link: function(scope, element){
var template = '<' + scope.template + '></' + scope.template + '>';
element.append($compile(template)(scope));
}
}
})
.controller('MyCtrl', function($scope, $compile) {
$scope.directives = [{
name: 'directive-one'
}, {
name: 'directive-two'
}];
$scope.reOrder = function () {
$scope.directives.push($scope.directives.shift());
console.log($scope.directives);
};
});
I hope You can easily done it.
var myApp = angular.module('myApp',[])
.directive('directiveOne', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive One'};
}
}
})
.directive('directiveTwo', function() {
return {
restrict: 'E',
scope: {},
template: '<h1>{{obj.title}}</h1>',
controller: function ($scope) {
$scope.obj = {title: 'Directive Two'};
}
}
});
myApp.controller('ctrl',function($scope){
$scope.data = [{name:'directive-one'},{name:'directive-two'}];
});
<html ng-app='myApp'>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.17/angular.min.js"></script>
</head>
<body ng-controller='ctrl'>
<div ng-repeat='item in data'>
<item.name></item.name>
<directive-one></directive-one>
</body>
</html>
Related
I have problem that is inability to render child directive (selected-item-template) in the parent template.
Code bellow:
HTML (Main/child directive)
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-model="customer"
cs-items="contacts"
>
<display-item-template>
<span>{{$parent.item.id}}</span>
<span>{{$parent.item.name}}</span>
</display-item-template>
<selected-item-template>
Your have selected customer: {{$parent.item.name}}
</selected-item-template>
</compact-select>
Directive
angular.module('core').directive('compactSelect', [function($timeout) {
return {
templateUrl : 'modules/core/views/components/compact-select-tpl.html',
bindToController: true,
transclude: true,
scope: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csModel: '=',
csItems: '=csItems'
},
controllerAs : 'ctrl',
controller : function($scope) {
}
};
}]).directive('displayItemTemplate', function() {
return {
require: '^compactSelect',
restrict: 'E'
}
}).directive('selectedItemTemplate', function() {
return {
require: '^compactSelect',
restrict: 'E'
}
});
Directive Template (modules/core/views/components/compact-select-tpl.html)
<div class="compact-select-repeater-box" style="" >
<div ng-transclude ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">
<span>{{item.name}}</span>
<span>{{item.id}}</span>
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
**HERE I WANT SELECTED ITEM TEMPLATE**
</div>
Question: How I can tell where child directive needs to be rendered?
Directive on ng-repeat works, but when I add two directives everything gets combined together and that's not what I want. Is there is a way to specify with ng-transclude where to render which directive? Like ng-transclude="displayItemTemplate" and ng-transclude="selectedItemTemplate" respectively?
This Example Based on Directives, and i want to show you how multi directives work together on one array.
I hope this helps you.
var app = angular.module("app", []);
app.controller("ctrl", function ($scope) {
$scope.selectedList = [];
$scope.data = [
{ name: "a", checked: true },
{ name: "b", checked: false }
];
$scope.getResult = function () {
console.log($scope.selectedList);
}
});
app.directive("directiveA", [function () {
return {
restrict: "E",
template: "<ul ng-repeat=\"item in items\">" +
"<li><directive-c data=\"item\"></directive-c> {{item.name}} <directive-b data=\"item\"></directive-b></li>" +
"</ul>",
scope: {
items: "="
}
};
}]);
app.directive("directiveB", function () {
return {
restrict: "E",
template: "<button ng-click=\"renderData(data)\">{{data.checked ? 'unchecked':'checked'}}</button>",
scope: {
data: "="
},
link: function (scope) {
scope.renderData = function (data) {
data.checked = !data.checked;
}
}
}
});
app.directive("directiveC", function () {
return {
restrict: "E",
template: "<input type=\"checkbox\" ng-model=\"data.checked\">",
scope: {
data: "="
}
}
});
app.directive("directiveD", function () {
return {
restrict: "E",
template: "<ul ng-repeat=\"item in items\">" +
"<li ng-if=\"item.checked\">{{item.name}}</li>" +
"</ul>",
scope: {
items: "=",
result: "="
},
link: function (scope) {
scope.$watch("items", function (newValue) {
scope.result = [];
if (newValue) {
angular.forEach(scope.items, function (item, index) {
if (item.checked) scope.result.push(item);
});
}
}, true);
}
}
});
<!DOCTYPE html>
<html ng-app="app" ng-controller="ctrl">
<head>
<title></title>
</head>
<body>
<h3>items</h3>
<directive-a items="data"></directive-a>
<h3>selected items</h3>
<directive-d items="data" result="selectedList"></directive-d>
<hr />
<button ng-click="getResult()">get selected list</button>
<small>after click, check your console</small>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
I have figured out how to achieve following. It can be done with multiple transcludsions, since angular 1.5.
I just need to define transcludeSlot.
Code bellow:
HTML (Main/child directive)
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-model="customer"
cs-items="contacts"
>
<display-item-template>
<span>{{$parent.item.id}}</span>
<span>{{$parent.item.name}}</span>
</display-item-template>
<item-selected-template>
Your have selected customer: {{$parent.csModel.name}}
</item-selected-template>
</compact-select>
Directive
angular.module('core').directive('compactSelect', [function($timeout) {
return {
templateUrl : 'modules/core/views/components/compact-select-tpl.html',
bindToController: true,
transclude: {
'repeaterItemSlot': 'displayItemTemplate',
'itemSelectedTemplateSlot' : 'itemSelectedTemplate'
},
scope: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csModel: '=',
csItems: '=csItems'
},
controllerAs : 'ctrl',
controller : function($scope) {
}
};
}]);
Directive Template (modules/core/views/components/compact-select-tpl.html)
<div class="compact-select-repeater-box" style="" >
<div ng-transclude="repeaterItemSlot" ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">
<span>{{item.name}}</span>
<span>{{item.id}}</span>
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
<div ng-transclude="itemSelectedTemplateSlot"></div>
</div>
So this works nicely similar to XAML.
I'm trying to build a directive with a controller, which updates a ViewModel-variable and calls a callback-function. In the callback-function the updated variable should be used, but it still got the old value.
HTML:
<div ng-app="app" ng-controller="AppCtrl">
Var: {{vm.var}}
<ng-element var="vm.var" func="vm.func()"></ng-element>
</div>
JavaScript:
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.vm = {
var: 'One',
func: function() {
alert($scope.vm.var);
}
};
});
app.directive('ngElement', function(){
return {
restrict: 'E',
scope: true,
bindToController: {
var: '=',
func: '&'
},
controllerAs: 'ctrl',
replace: true,
template: '<button ng-click="ctrl.doIt()">Do it</button>',
controller: function() {
this.doIt = function() {
this.var = 'Two';
this.func();
};
}
};
});
So when clicking the button, doIt() ist called, updates var and calls func(). But when func() is executed, var still got the old value "One". Right after the execution the ViewModel gets updated and the value is "Two".
Is there any way to update the ViewModel before executing the function?
JSFiddle
Not sure exactly what your directive is doing, as I've never used bindToController, but this seemed to work:
directive('ngElement', function () {
return {
restrict: 'E',
scope: {
var: '=',
func: '&'
},
replace: true,
template: '<button ng-click="doIt()">Do it</button>',
controller: ['$scope', '$timeout', function($scope, $timeout) {
$scope.doIt = function() {
$scope.var = 'Two';
$timeout(function () {
$scope.func();
});
};
}]
};
});
I have a directive which shows input fields and I want to initialize those fields with data from the server. The problem is that I can't do that while using ng-model.
Before using a directive I used in the controller something like $scope.field1 = $scope.first.field1
Here's my code. I simplified it for the sake of readability but the idea's here.
In my controller I have this code:
app.controller('MyController',
['$scope', 'myData', function($scope, myData) {
myData.then(function(data) {
$scope.first = data.first;
$scope.second = data.second;
});
}]);
Inside first and second I have 2 field: field1 and field2.
In in html code, I have this bit:
<h1>First</h1>
<my-directive info="first"></my-directive>
<h1>Second</h1>
<my-directive info="second"></my-directive>
The directive is as follows:
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'static/js/myDirective.html',
link: function(scope, element, attrs) {
scope.doStuff = function() {
/* Do stuff with
scope.field1 and scope.field2 */
}
}
};
});
and the myDirective.html code:
<input type="text" ng-model="myfield1" />
<input type="text" ng-model="myfield2" />
<input type="submit" ng-click="doStuff()" />
If in myDirective.html I write:
<input type="text" value="info.field1" />
I can see the value fine.
Any ideas?
probably your directive initializes before your data loads. and your directive sees ng-model as an undefined variable. You are not using info directly in template so no auto $watch's for you :).
you need to $watch your info variable in directive and call your doStuff function on change.
note: i wouldn't recommend adding a controller to a directive just for this task. adding a controller to a directive is needed when you need to communicate with other directives. not for waiting async data
edit
you should do
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'static/js/myDirective.html',
link: function(scope, element, attrs) {
scope.doStuff = function() {
/* Do stuff with
scope.field1 and scope.field2 */
}
scope.$watch('info', function(newValue){
scope.doStuff();
});
}
};
});
Check working demo: JSFiddle.
Define a controller for the directive and do the initialization inside it:
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
controller: ['$scope', 'myData', function ($scope, myData) {
myData.then(function(data){
$scope.first = data.first;
$scope.second = data.second;
});
}],
...
},
Inside the directive, myfield1 and myfield2 don't exist. You are close to solving the issue by using info.field1 instead.
var myApp = angular.module('myApp', []);
//Faking myData here; in your example this would come from the server
var myData = {
first: {
field1: "FirstField1",
field2: "FirstField2"
},
second: {
field1: "SecondField1",
field2: "SecondField2"
}
};
myApp.controller('MyController', ['$scope', function($scope) {
$scope.first = myData.first;
$scope.second = myData.second;
}]);
myApp.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
template: '<input type="text" ng-model="info.field1" /><input type="text" ng-model="info.field2" /><input type="submit" ng-click="doStuff()" />',
link: function(scope, element, attrs) {
scope.doStuff = function() {
alert('Info: ' + scope.info.field1 + ', ' + scope.info.field2);
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<h1>First</h1>
<my-directive info="first"></my-directive>
<h1>Second</h1>
<my-directive info="second"></my-directive>
</div>
I have trouble getting my transcluding directive to work. I want to do the following: Create a directive that outputs a list where the content of each item is defined by transcluded content. E.g:
<op-list items="myItems">
<span class="item">{{item.title}}</span>
</op-list>
so I would use ng-repeat inside op-list's template and must be able to access the scope created by ng-repeat inside the transcluded content.
This is what I've done so far:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope', function ($scope) {
$scope.myModel = {
name: 'Superhero',
items: [{
title: 'item 1'
}, {
title: 'item 2'
}]
};
}]);
myApp.directive('opList', function () {
return {
template: '<div>' +
'<div>items ({{items.length}}):</div>' +
'<div ng-transclude ng-repeat="item in items"></div>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
});
<html ng-app="myApp">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="MyCtrl">
<div>Hello, {{myModel.name}}!</div>
<op-list items="myModel.items">
<span>title: {{item.title}}|{{$scope}}|{{scope}}|{{items}}</span>
</op-list>
</div>
</html>
Check if this works:
myApp.directive('opList', ['$scope', function ($scope) {
return {
template: '<div>' +
'<div>items ({{model.items.length}}):</div>' +
'<ng-transclude ng-repeat="item in model.items"></ng-transclude>' +
'</div>',
restrict: 'E',
replace: true,
transclude: true,
scope: {
items: '='
}
};
}]);
If it doesn't then try to inspect $scope in console and see if you're able to access your model.
I have two nested directives for building a treeview in Angular:
Parent directive:
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '='
},
template: "<ul><node ng-repeat='node in nodes' node='node'></node></ul>"
}
});
Child directive:
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '='
},
template: "<li>{{node.ObjectName}}</li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children'></nodes>");
$compile('<nodes nodes="node.Children"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
The controller:
function myController($scope, DataService) {
$scope.init = function() {
DataService.getData(0, 0).then(function(data) {
$scope.treeNodes = $.parseJSON(data.d);
});
}
$scope.focusNode = function(prmNode) {
console.log(prmNode);
}
}
HTML:
<div ng-app="testTree" ng-controller="myController">
<div ng-init="init()">
<nodes nodes='treeNodes'></nodes>
</div>
</div>
My question is how can I implement a click on the <li> which will call the "focusNode" function in the controller?
You could pass in the function through an attribute.
Javascript
var myApp = angular.module('myApp', []);
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '=',
clickFn: '&'
},
template: "<ul><node ng-repeat='node in nodes' node='node' click-fn='clickFn()'></node></ul>"
}
});
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '=',
clickFn: '&'
},
template: "<li><span ng-click='clickFn()(node)'>{{node.ObjectName}}</span></li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children' click-fn='clickFn()'></nodes>");
$compile('<nodes nodes="node.Children" click-fn="clickFn()"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
function myController($scope) {
$scope.focusNode = function(prmNode) {
console.log(prmNode);
}
$scope.root = {
ObjectName: 'Root',
Children:[{
ObjectName: 'A',
Children: [{
ObjectName: 'B'
}, {
ObjectName: 'C'
}]
}]
};
}
HTML
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.2.8" src="http://code.angularjs.org/1.2.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app='myApp' ng-controller="myController">
<node node="root" click-fn="focusNode"></node>
</body>
</html>
Since I can't comment until I get 50 rep cred :(...
I just wanted to add that I also got an error when trying the plunkr that W.L.Jared shared above. To fix the error, I changed the controller from a global function to:
angular.module('myApp').controller('myController', function($scope){...})
The error went away.
Good answer though :) Exactly what I was looking for.