When I use $compile to create and bind a directive, how can I also add a variable as an attribute? The variable is an object.
var data = {
name: 'Fred'
};
var dirCode = '<my-directive data-record="data"></my-directive>';
var el = $compile(dirCode)($scope);
$element.append(el);
And myDirective would be expecting:
...
scope: {
record: '='
},
...
I have tried doing
`var dirCode = '<my-directive data-record="' + data + '"></my-directive>';`
instead too.
It is quite easy, just create new scope and set data property on it.
angular.module('app', []);
angular
.module('app')
.directive('myDirective', function () {
return {
restrict: 'E',
template: 'record = {{record}}',
scope: {
record: '='
},
link: function (scope) {
console.log(scope.record);
}
};
});
angular
.module('app')
.directive('example', function ($compile) {
return {
restrict: 'E',
link: function (scope, element) {
var data = {
name: 'Fred'
};
var newScope = scope.$new(true);
newScope.data = data;
var dirCode = '<my-directive data-record="data"></my-directive>';
var el = $compile(dirCode)(newScope);
element.append(el);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="app">
<example></example>
</div>
Related
I have a main controller: MainCtrl and a function within it: $scope.ctrlFn
This function expects an argument and returns a string concatenated to the received argument value.
MainCtrl JS:
app.controller('MainCtrl', ['$scope', '$http', '$log', '$timeout', function ($scope, $http, $log, $timeout) {
$scope.ctrlFn = function(x) {
return 'some val'+x;
};
var customTemp = '<div ng-repeat="c in grid.appScope.someArray">{{c}}<my-directive url="MODEL_COL_FIELD" vvv="ctrlFn(url)" /></div>';
$scope.someArray=['a','b','c'];
$scope.gridOptions = {};
$http.get('https://cdn.rawgit.com/angular-ui/ui-grid.info/gh-pages/data/500_complex.json')
.success(function(data) {
$scope.gridOptions.data = data;
});
$scope.gridOptions.rowHeight = 50;
$scope.gridOptions.columnDefs = [
{ name:'eee' },
{ name:'age' },
{ name:'address.street'}
];
$scope.gridOptions2 = {};
$scope.gridOptions2.data =[];
$scope.gridOptions2.rowHeight =400;
$scope.gridOptions2.columnDefs =[];
var timer = function() {
for (i = 1; i < 100; i++) {
$scope.gridOptions2.data.push({
value:i,
name:'Name'+i,
url:'https://dummyimage.com/30x30/000/fff&text='+i,
image:i
});
}
$scope.gridOptions2.columnDefs =[
{
name:'image',
cellTemplate:customTemp,
width:100
},
{
name:'name',
cellTemplate:customTemp,
width:100
},
{
name:'url',
width:50
},
{
name:'value'
}
];
};
$timeout(timer,0);
console.log($scope.gridOptions);
console.log($scope.gridOptions2);
}]);
I have a directive: myDirective which creates a template and initiates scope values from parent scope variables and functions and displays them. I have managed to pass the scope variables to directive template like with scope url but not the value myValue which is meant to be assigned the value returned from the parent function.
Directive myDirective:
app.directive('myDirective', function ($log) {
return {
restrict: 'E',
replace: true,
scope: {
url: '=',
vvv: '&'
},
template: '<div ng-init="u=url; myValue=vvv" >{{u}}-{{myValue}}<img src="https://dummyimage.com/100x100/000/fff&text={{url}}" alt="Smiley face" height="100" width="100"></div>',
link: function (scope, element, attrs) {
scope.$watch('url', function(newValue) {
var url = newValue;
});
}
};
});
Here is a plunker:
http://plnkr.co/edit/yXE3AuZEPwjlmlqpFTNq?p=preview
To access the parent scope from within the directives template, which is rendered within table cells of the ui grid, you have to go through grid.appScope.variableOrFunction
So to call ctrlFn() I have to:
grid.appScope.ctrlFn()
working example:http://plnkr.co/edit/yXE3AuZEPwjlmlqpFTNq?p=preview
I wan't to program a flexible angular directive with it's properties defined in an own, simple object.
Angular:
contentFactory.directive("listViewDir", function ($compile) {
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link:
return $scope.config.link(scope, element, attrs);
}
}
});
Own Configuration Object:
contentFactory.controller("indexCtrl", function ($scope) {
$scope.config = oLiftTabs;})
var configurations = [{
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = "... myTemplate ..";
element.html(template);
$compile(element.contents())(scope);
},
}]
While my solution for the controller works well, it doesn't for the link.
Is there a more proper way for my approach? Can I realize access in my encapsulated method to the services (like $compile) without declaring it in the directive declaration?
Is this what you're trying to achieve? You didn't make it clear where you expect this object to live so I've assumed you want it a parent controller. This doesn't feel like a good idea but without knowing more about your use case it's hard to say.
DEMO
html
<body ng-controller="MainCtrl">
<list-view-dir config="config"></list-view-dir>
</body>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
$scope.config = {
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
},
};
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
Update
From your comments it sounds like you need to use a factory. Maybe something like this? It feels pretty ugly but it could be what you're looking for.
DEMO2
var app = angular.module('plunker', []);
app.factory('directiveConfigurations', function($compile){
var configurations = {
'listViewDir': {
controller: function ($scope) {
$scope.ButtonClicked = function(){
alert('Button wurde geklickt!');
};
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
}
}
};
return {
get: get
};
////////////////////////
function get(key){
return configurations[key];
}
});
app.controller('MainCtrl', function($scope, directiveConfigurations) {
$scope.config = directiveConfigurations.get('listViewDir');
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
I am trying to write a unit test for the toggleDetails function defined inside the following AngularJS directive:
angular.module('hadoopApp.cluster.cluster-directive', [])
.directive('cluster', [function() {
return {
templateUrl:'components/cluster/cluster.html',
restrict: 'E',
replace: true,
scope: {
clusterData: '=',
showDetails: '='
},
link: function(scope, element, attrs) {
scope.toggleDetails = function() {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
},
// Default options
compile: function(tElement, tAttrs){
if (!tAttrs.showDetails) { tAttrs.showDetails = 'false'; }
}
};
}]);
And this is the unit test:
'use strict';
describe('hadoopApp.cluster module', function() {
// Given
beforeEach(module('hadoopApp.cluster.cluster-directive'));
var compile, mockBackend, rootScope;
beforeEach(inject(function($compile, $httpBackend, $rootScope) {
compile = $compile;
mockBackend = $httpBackend;
rootScope = $rootScope;
}));
var dummyCluster;
beforeEach(function() {
dummyCluster = {
id:"189",
name:"hadoop-189",
exitStatus:0
};
mockBackend.expectGET('components/cluster/cluster.html').respond(
'<div><div ng-bind="clusterData.name"></div></div>');
});
it('should toggle cluster details info', function() {
var scope = rootScope.$new();
scope.clusterData = dummyCluster;
// When
var element = compile('<cluster' +
' cluster-data="clusterData" />')(scope);
scope.$digest();
mockBackend.flush();
// Then
var compiledElementScope = element.isolateScope();
expect(compiledElementScope.showDetails).toEqual(false);
// When
console.log(compiledElementScope);
compiledElementScope.toggleDetails();
// Then
expect(compiledElementScope.showDetails).toEqual(true);
});
afterEach(function() {
mockBackend.verifyNoOutstandingExpectation();
mockBackend.verifyNoOutstandingRequest();
});
});
The test fails when calling compiledElementScope.toggleDetails() because the toggleDetails function is undefined:
TypeError: undefined is not a function
Printing the content of the isolated scope inside compiledElementScope I can see that in fact the function is not included in the object.
So, it looks like the toggleDetails function is not included in the isolated scope but I don't know why.
If you use the compile function within a directive, the link function is ignored. You should return the function within the compile method:
compile: function (tElement, tAttrs) {
if (!tAttrs.showDetails) {
tAttrs.showDetails = 'false';
}
return {
post: function (scope, element, attrs) {
console.log('Test');
scope.toggleDetails = function () {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
}
};
}
Also, in order to make the test work, you should add:
scope.showDetails = false;
And the binding to the directive (because you require two values):
var element = compile('<cluster' +
' cluster-data="clusterData" show-details="showDetails" />')(scope);
Jsfiddle: http://jsfiddle.net/phu7sboz/
I want to create a directive that has dynamic view with dynamic controller. the controller and the template view is coming from the server.
The Directive
var DirectivesModule = angular.module('BPM.Directives', []);
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$window'];
function bpmCompletedTask ($window) {
// Usage:
// <bpmCompletedTask></bpmCompletedTask>
// Creates:
//
var directive = {
link: link,
restrict: 'E',
scope: {
type: '=',
taskdata: '=',
controllername:'#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllername'
};
return directive;
function link(scope, element, attrs) {
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
scope.getControllerName = function ()
{
console.warn("Controller Name is " + scope.type);
return scope.type;
}
}
}
})();
Here how I'm trying to use the directive
<div ng-controller="WorkflowHistoryController as vm">
<h2>Workflow History</h2>
<h3>{{Id}}</h3>
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome" type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</bpm-completed-task>
</div>
</div>
The problem now is when the directive gets the controller name it get it as literal string not as a parameter.
Is it doable ?
if it's not doable, What is the best solution to create dynamic views with its controllers and display them dynamically inside ng-repeat?
Thanks,
Update 20 Jan I just updated my code in case if some one interested in it. All the Credit goes to #Meligy.
The First Directive:
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$compile', '$parse'];
function bpmCompletedTask ($compile, $parse) {
var directive = {
link: function (scope, elem, attrs) {
console.warn('in the first directive - before if');
if (!elem.attr('bpm-completed-task-inner'))
{
console.warn('in the first directive');
var name = $parse(elem.attr('controllername'))(scope);
console.warn('Controller Name : ' + name);
elem = elem.removeAttr('bpm-completed-task');
elem.attr('controllernameinner', name);
elem.attr('bpm-completed-task-inner', '');
$compile(elem)(scope);
}
},
restrict: 'A',
};
return directive;
}
})();
The Second Directive
angular
.module('BPM.Directives')
.directive('bpmCompletedTaskInner',['$compile', '$parse',
function ($window, $compile, $parse) {
console.warn('in the second directive');
return {
link: function (scope, elem, attrs) {
console.warn('in the second directive');
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
},
restrict: 'A',
scope: {
type: '=',
taskdata: '=',
controllernameinner: '#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllernameinner'
};
}]);
The Html
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<div bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome"
type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</div>
</div>
Update:
I got it working, but it's really ugly. Check:
http://jsfiddle.net/p6Hb4/13/
Your example has a lot of moving pieces, so this one is simple, but does what you want.
Basically you need a wrapper directive that takes the JS object and converts into a string property, then you can use هى your directive for everything else (template, scope, etc).
.
Update 2:
Code Inline:
var app = angular.module('myApp', []).
directive('communicatorInner', ["$parse", "$compile",
function($parse, $compile) {
return {
restrict: 'A',
template: "<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>",
scope: {
message: '='
},
controller: '#'
};
}
]).
directive('communicator', ['$compile', '$parse',
function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem) {
if (!elem.attr('communicator-inner')) {
var name = $parse(elem.attr('controller-name'))(scope);
elem = elem.removeAttr('controller-name')
elem.attr('communicator-inner', name);
$compile(elem)(scope);
}
}
};
}
]).
controller("PhoneCtrl", function($scope) {
$scope.sendMsg = function() {
alert($scope.message + " : sending message via Phone Ctrl");
}
}).
controller("LandlineCtrl", function($scope) {
$scope.sendMsg = function() {
alert($scope.message + " : sending message via Land Line Ctrl ");
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>
<div ng-app="myApp">
<div ng-init="test = {p: 'PhoneCtrl', l: 'LandlineCtrl' }">
<communicator controller-name="test.p" message="'test1'"></communicator>
<communicator controller-name="test.l"></communicator>
</div>
</div>
.
Original (irrelevant now but can help other related issues)
Yes, it should work.
A test with Angular 1.3:
http://jsfiddle.net/p6Hb4/9/
Things to check:
Is the controller defined and added to the module? It will not work
If the controller is just a global function it won't work. It has to be added via the <myModule>.controller("<controllerName>", <functiion>) API
Does ng-controller work? Just adding it to the template
Similarly, does using ng-controller directly outside of the directive work?
I'm unit testing one of my directives. Its basic structure is like this
angular.module('MyApp')
.directive('barFoo', function () {
return {
restrict: 'E',
scope: {},
controller: function ($scope, $element) {
this.checkSomething = function() { .... }
},
link: function(scope, element) { .... }
}
});
In my unit test I want to test the function 'checkSomething', so I tried
var element = $compile('<barFoo></barFoo>')(scope);
var controller = element.controller()
...
However, controller is undefined. Is it possible to access the controller of the directive ?
the glue is your scope so you can do
controller: function ($scope, $element) {
this.checkSomething = function() { .... }
$scope.controller = this;
},
but i think it would be best practice to attach every function to the scope like
controller: function ($scope, $element) {
$scope.checkSomething = function() { .... }
},
and then its
var element = $compile('<barFoo></barFoo>')(scope);
var checksomthingResult = scope.checkSomething ()