I have a form based on twitter bootstrap, each field have it's own configuration
// controller (the template shows this in ng-repeat
$scope.fields = [{name:"f1", label:"Field 1", with_button: false},
{name:"f2", label:"Field 2", with_button: true}]
I'm trying to make a "conditional directive" that customize the template according to "field.with_button"
// Without button
<div class="controls">
<input type="text" id="i_{{field.name}}">
</div>
// With button
<div class="controls">
<div class="input-append">
<input type="text" id="i_{{field.name}}">
<span class="add-on">bt</span>
</div>
</div>
I searched a lot and didn't find any solution, I tried to create only one div and put contents inside with a compiler function but it didn't parse, and if I call $apply it crashes.
How could I make this directive?
wrong My last try:
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls">{{innerContent}}</div>',
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
$scope.$eval('$scope.innerContent = \'<input type="text" id="input_{{field.name}}" placeholder="{{field.name}}" class="input-xlarge">\'');
}]
};
});
//<ss-field field="{{field}}"></ss-field>
You can use the $http and $compile services to do what you're after.
http://plnkr.co/edit/Xt9khe?p=preview
This plnkr should demostrate what needs to be done, but basically:
Use $http to load the template depending on the condition.
Compile the loaded template against the current scope with $compile.
angular.module('mymodule',[]).directive('ssField', ['$http', '$compile', function($http, $compile) {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls"></div>',
link: function(scope, element, attrs) {
var template;
var withButtonTmpl = 'with_button.html';
var withoutButtonTmpl = 'without_button.html';
if (scope.field.with_button) {
$http.get(withButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
} else {
$http.get(withoutButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
}
}
};
}]);
You can change the directive to be more robust so the URLs aren't directly embedded in the directive for re-usability, etc., but the concept should be similar.
Just to further expand on Cuing Vo's answer here is something similar to what I use(without using external partials and additional $http calls):
http://jsfiddle.net/LvUdQ/
myApp.directive('myDirective',['$compile', function($compile) {
return {
restrict: 'E',
template: '<hr/>',
link: function (scope, element, attrs, ngModelCtrl) {
var template = {
'templ1':'<div>Template 1</div>',
'templ2':'<div>Template 2</div>',
'default':'<div>Template Default</div>'
};
var templateObj;
if(attrs.templateName){
templateObj = $compile(template[attrs.templateName])(scope);
}else{
templateObj = $compile(template['default'])(scope);
}
element.append(templateObj);
}
};
}]);
However Im not quite sure its by the bible from performance perspective.
In AngularJS, directly manipulate the DOM must only be a last resort solution. Here, you can simply use the ngSwitch directive :
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template:
'<div class="controls" data-ng-switch="field.with_button">' +
'<input type="text" id="i_{{field.name}}" data-ng-switch-when="false">' +
'<div class="input-append" data-ng-switch-default>' +
'<input type="text" id="i_{{field.name}}">' +
'<span class="add-on">bt</span>' +
'</div>' +
'</div>',
};
});
Related
I am trying to create a reusable html element / angular directive that will be used inside of ng-repeat so I want to pass it the values it will display in the DOM.
Something worth noting, I don't care for the values to bind. They can be a one-time binding simply displaying their values the first time ng-repeat creates them.
For example here is my directive:
app.directive('newsListing', function () {
return {
restrict: 'AE',
replace: 'true',
templateUrl: '../Pages/Views/Templates/newsListing.html',
scope: {},
link: function (scope, elem, attrs) {
//Fairly sure this is where the binding needs to happen?
}
};
});
My HTML template:
<div>
<span class="glyphicon glyphicon-list-alt logo-green"></span>
<label>{{DateValue}}</label>
<label>{{Category}}</label>
<label class="noBorder">{{Content}}</label>
What I want the ending product to be:
<news-Listing Date="{{someValue}}" Category="{{someValue}}" Content="{{someValue}}"></news-Listing>
I have never created a directive before and all the guides I am trying to follow don't explain the scope, and the way binding happens inside of a directive.
Try like this
var app = angular.module('anApp', []);
app.controller('aCtrl', function($scope) {
$scope.data = [{date:"2000-12-12",category:"sport",content:"this is a test"}]
});
app.directive('newsListing', function () {
return {
restrict: 'AE',
replace: 'true',
template: '<div><label>{{date}}</label><p>{{category}}</p><p>{{content}}</p></div>',
scope: {
date:'#',
category:'#',
content:'#'
},
link: function (scope, elem, attrs) {
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="anApp" ng-controller="aCtrl">
<div ng-repeat="d in data">
<news-listing date="{{d.date}}" category="{{d.category}}" content="{{d.content}}" ></news-listing>
</div>
</div>
Here is a working example of what you want: https://jsfiddle.net/jonva/kuk3pbbz/
.directive('newsListing', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<div> < span class = "glyphicon glyphicon-list-alt logo-green" > < /span> < label > {{dateValue}} < /label> < label > {{category}} < /label> < label class = "noBorder" > {{content}} < /label>',
scope: {
dateValue: '#',
content: '#',
category: '#',
},
link: function(scope, elem, attrs) {
//Fairly sure this is where the binding needs to happen?
}
};
});
I have created a directive with a template specified. I would like to see whatever i type in the debug div below the textbox. But debug shows only the initial value and its not getting updated. I think there is some silly mistake that i might be doing. But i am not able to figure it out.
Following is the directive:
angular.module('directiveBinding', [])
.directive('mydirective', function() {
var my_template = '<div contenteditable="true" ng-model="myobj.text"' +
'class="contenteditable col-xs-12 col-md-6 col-md-offset-3">{{myobj.text}}</div>' +
'<div class="debug clearfix"><br>{{myobj.text}}</div>';
var linker = function(scope, element, attrs, ngModel) {
console.log('Linker called');
console.log('scope.myobj.text: ' + scope.myobj.text);
}
var controller = function($scope) {
$scope.myobj = {};
$scope.myobj.text = 'some value';
$scope.text = "hello world"
console.log('$scope.myobj.text: ' + $scope.myobj.text);
}
return {
require: '?ngModel',
restrict: "E",
link: linker,
controller: controller,
template: my_template,
scope: {}
};
});
And i am using it as follows:
<mydirective></mydirective>
Edit
The above code works if i change the template to use <input> instead of contenteditable div.
var my_template = '<input type="text" ng-model="myobj.text"' +
'class="contenteditable col-xs-12 col-md-6 col-md-offset-3">{{myobj.text}}</input>' +
'<div class="debug clearfix"><br>{{myobj.text}}</div>';
Here is the plunker for the same.
I believe your issue lies with $scope.text, you should never bind to primitives.
In your html change ng-model="text.something" and also {{text}} to {{text.something}}.
Here's my fiddle
I basically want to be able to change the text when a button is pressed. I have tried with both $observe and $watch inside link, but I still don't manage to get it working.
Code:
(function(){
angular.module('app', [])
.directive('testDirective', function(){
return {
restrict: 'E',
scope: {
title: '#'
},
template: '<div>this is a {{ title }}</div>',
link: function(scope, element, attrs) {
//?
}
};
});
})()
You need to pass data as scope variable, you should not pass it as a string if you want to track changes.
check this fiddle, replace counter data with your desired data. Hope this helps
<div ng-controller='myctrl'>
<test-directive title="counter"></test-directive>
<hr></hr>
<button type="button" ng-click = 'onclickbutton()'>Change names</button>
</div>
(function(){
angular.module('app', [])
.controller('myctrl',function($scope){
$scope.counter = 0;
$scope.onclickbutton = function(){
$scope.counter++;
}
})
.directive('testDirective', function(){
return {
restrict: 'E',
scope: {
title: '='
},
template: '<div>this is a {{ title }}</div>',
link: function(scope, element, attrs) {
}
};
});
})();
DEMO
Here is a simplified version of the two directives I have, my-input and another-directive:
HTML:
<body ng-controller="AppCtrl">
<another-directive>
<my-input my-input-model="data.firstName"></my-input>
</another-directive>
</body>
JS:
.directive('myInput', function() {
return {
restrict: 'E',
replace: true,
scope: {
model: '=myInputModel'
},
template: '<input type="text" ng-model="model">'
};
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
scope: {},
compile: function(element) {
var html = element.html();
return function(scope) {
var output = angular.element(
'<div class="another-directive">' +
html +
'</div>'
);
$compile(output)(scope);
element.empty().append(output); // This line breaks the binding
};
}
};
});
As you can see in the demo, if I remove element.empty().append(output);, everything works fine, i.e. changes in the input field are reflected in controller's data. But, adding this line, breaks the binding.
Why is this happening?
PLAYGROUND HERE
The element.empty() call is destroying all child nodes of element. In this case, element is the html representation of another-directive. When you are calling .empty() on it, it is trying to destroy its child directive my-input and any scopes/data-bindings that go with it.
A somewhat unrelated note about your example. You should look into using transclusion to nest html within a directive, like you are doing with another-directive. You can find more info here: https://docs.angularjs.org/api/ng/service/$compile#transclusion
I think a little bit context as to what you are trying to do well be helpful. I am assuming you want to wrap the my-input directive in another-directive ( some sort of parent pane ). You could accomplish this using ng transclude. i.e
angular.module('App', []).controller('AppCtrl', function($scope) {
$scope.data = {
firstName: 'David'
};
$scope.test = "My test data";
}).directive('myInput', function() {
return {
restrict: 'E',
replace: true,
scope: {
model: '=myInputModel'
},
template: '<input type="text" ng-model="model">'
};
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
transclude: true,
scope: {},
template : '<div class="another-directive"><div ng-transclude></div></div>'
};
});
It works if you require ngModel
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
require:'ngModel',
scope: {},
...
In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle