I made a custom directive with AngularJS,
and in the template I called a function in controller,
but it didn't work.
thanks for your help :)
<div ng-contorller="myCtrl">
<ng-selectbox my-function="myfunction()" items="codes"></ng-selectbox>
</div>
myapp.controller("myCtrl", function($scpoe){
$scope.myfunction= function(){
alert("123");
};
});
myapp.directive("ngSelectbox", function(){
return {
restrict: "E",
scope: {
items: "=",
myfunction: "&"
},
template:
"<div id='selectbox'>" +
"<ul ng-repeat='item in items'>" +
"<li ng-click='myfunction()'>{{item.TYPE}}</li>" +
"</ul>" +
"</div>"
};
});
Do not add calling brackets where you are using your directive , just use like this <ng-selectbox my-function="myfunction" items="codes"></ng-selectbox>
You should place your directive inside your controller wrapper like below.
<div ng-controller="myCtrl">
<ng-selectbox my-function="myfunction()" items="codes"></ng-selectbox>
</div>
Related
Fiddle
I have two buttons. When pressed it displays a modal, with som text. But I also want to add some html dynamically depending on which button is pressed.
I have tried both $observe and $watch methods, but I'm having problems making it work.
here is my code.
angular.module('TM', [])
.controller('protocolCtrl', function(){
this.text = 'Now looking at the protocol part';
this.modalId = 'protocolModal';
})
.controller('categoryCtrl', function(){
this.text = 'Now looking at the category part';
this.modalId = "categoryModal";
})
.directive('modalDirective', function(){
return {
restrict: 'E',
scope: {
ctrl: '=',
modalId: '#',
},
template: ['<div id="{{modalId}}" class="modal fade" role="dialog">',
'<div class="modal-dialog">',
'<div class="modal-content">',
'<div class="modal-header">',
'<h4 class="modal-title">Modal Header</h4>',
'</div>',
'<div class="modal-body">',
'<p> {{ ctrl.text }} </p>',
'</div>',
'<div class="modal-footer">',
'<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>',
'</div>',
'</div>',
'</div>',
'</div>'].join(''),
link: function(scope, element, attrs) {
element.$observe('modalId', function(){
var modal = element.find('#{{modalId}}');
if(modal == 'protocolModal'){
element.find('#{{modalId}}').append('<div>this is a protocol test...</div>');
} else {
element.find('#{{modalId}}').append('<div>this is a category test...</div>');
}
});
}
}
});
I don't think there is element.$observe - there is attrs.$observe and scope.$watch. You already have modelId on the scope, so let's use that.
Also, instead of the wonky .find by id, inject an element as a placeholder for the template and replaceWith it accordingly:
template: '<div id="{{modalId}}">\
...\
<div class="modal-body">\
<template-placeholder></template-placeholder>\
</div>\
</div>",
link: function(scope, element){
// ...
var unwatch = scope.$watch("modalId", function(val){
var placeholder = element.find('template-placeholder');
if(val == 'protocolModal'){
placeholder.replaceWith('<div>this is a protocol test...</div>');
} else {
placeholder.replaceWith('<div>this is a category test...</div>');
}
unwatch(); // seems like you don't really need to set it again
}
}
See i updated your Fiddle
Use attr in link function. because you already given attribute to
your html i.e: modal-id="{{pctrl.modalId}}
<modal-directive ctrl="pctrl" modal-id="{{pctrl.modalId}}"></modal-directive>
if(attrs.modalId == 'protocolModal'){
element.find('#{{modalId}}').append('<div>this is a protocol test...</div>');
} else {
element.find('#{{modalId}}').append('<div>this is a category test...</div>');
}
Edit :
use $timeout
$timeout(function () {
if (attrs.modalId == 'protocolModal') {
element.find('#' + attrs.modalId).append('<div>this is a protocol test...</div>');
} else {
element.find('#' + attrs.modalId).append('<div>this is a category test...</div>');
}
}, 1000)
Now why $timeout because you are applying template and and at same
time your link function append your div so it will not apply your div
.So first apply template and then in template append your div
And if you want to show that div in popup content the use this selector.
see this example: https://jsfiddle.net/kevalbhatt18/o76hxj69/6/
element.find('#'+attrs.modalId +' .modal-body').append('<div>this is a protocol test...</div>');
If your two DOM structure diverses, you can consider using two different templates depending on some parameter value.
templateUrl can be specified as a function, such as:
angular.module('Joy', [])
.controller('ProfileCtrl', ['$scope', function ($scope) {
$scope.user = {
name: 'Elit'
};
}])
.directive('profile', [function () {
return {
restrict: 'A',
templateUrl: function (elem, attrs) {
return 'style-' + attrs.color + '.html';
}
};
}]);
And use the directive as:
<div ng-app="Joy" id="play-ground" ng-controller="ProfileCtrl">
<div profile color="red"></div>
<div profile color="green"></div>
</div>
In this case, if the color is red, the directive will load template from style-red.html. Otherwise from style-green.html.
In your case, you might maintain a flag in the outside controller. Clicking either button will change this flag value and pass in to the directive. The directive will load different templates accordingly.
I'm pretty new to Angular and I got a question regarding directives and inheritance.
Let's assume there are two directives:
<my-pic my-log="log pic" my-warn="pic warn" my-src="/some/url/pic.jpg"></my-pic>
<my-button my-log="log button" my-warn="button warn" my-text="button text"></my-button>
JS-Code
angular.module("app", [])
.directive("myPic",function(){
return {
restrict: "E"
,scope: {
log: "#myLog"
,src: "#mySrc"
,warn:"#myWarn"
}
,template: '<img src="{{src}}" title="{{log}} | {{warn}}"></img>'
,controller: function($scope, $log){
$log.log($scope.log);
$log.warn($scope.warn);
}
};
})
.directive("myButton",function(){
return {
restrict: "E"
,scope: {
log: "#myLog"
,text: "#myText"
,warn: "#myWarn"
}
,template: '<button title="{{log}} | {{warn}}">{{text}}</button>'
,controller: function($scope, $log){
$log.log($scope.log);
$log.warn($scope.warn);
}
};
});
Code Pen
http://codepen.io/anon/pen/VLMdRL
QUESTION
The question is, is it possible to create a third directive from which the MyPic and MyButton directives could be derived from and that handles the my-log="..." and my-warn="..." attributes (the $log code and adding the title attribute to the template code)?
The my-log and my-warn attributes must be handled by the same directive and the attribute values should still be accessible by the child directives.
Thanks for your help!
In this case you don't need to inheritance. Just write a service to handle your common logging operations and inject it into your directives.
Okay I found a solution I can live with, however maybe there is a better solution to do this. What I did is, I created a third directive 'myLogger' that is then added to the template of the 'myButton' and 'myPic' directives. I also created a function that is defined as a constant and that adds the logging properties to the scope of the directive. The 'myLogger' directive does the console logging and ads the 'title' attribute to the HTML-element.
Any advices/ideas to improve that solution are very welcome. I would also appreciate if someone has a better solution.
HTML code:
<my-pic my-log="log pic" my-warn="pic warn" my-src="/some/url/pic.jpg"></my-pic>
<my-button my-log="log button" my-warn="button warn" my-text="button text"></my-button>
Javascript code:
angular.module("app", [])
.constant("addLogScope",function(opts){
return angular.extend(opts || {},{
myWarn: "#"
,myLog: "#"
});
})
.directive("myLogger",function(){
return {
restrict: "A"
,scope: true
,controller: function($scope, $log){
console.log($scope.myLog);
console.info($scope.myWarn);
}
,link: function($scope, $element, $attrs){
$element.prop("title",$scope.myLog + " | " + $scope.myWarn);
}
};
})
.directive("myPic",function(addLogScope){
return {
restrict: "E"
,scope: addLogScope({
mySrc: "#"
})
,transclude: true
,template: '<img src="{{mySrc}}" my-logger></img>'
,controller: function($scope){
}
};
})
.directive("myButton",function(addLogScope){
return {
restrict: "E"
,scope: addLogScope({
myText: "#"
})
,template: '<button my-logger>{{myText}}</button>'
,controller: function($scope){
}
};
});
Code pen: http://codepen.io/anon/pen/rVYgew
I have a few buttons with ng-click in a controller and I want to change the a directive template when I click on a button. What is the best way to achieve this? (I will later use a slide in effect of the element)
Directive:
myModule.directive('foo', function() {
return {
scope: {
templateType: '#'
},
template: '<div ng-if="templateType == \'a\'">' +
' this is template A ' +
'</div>' +
'<div ng-if="templateType == \'b\'">' +
' this is template B ' +
'</div>'
};
});
Template:
<foo template-type="{{ templateType }}"></foo>
<a ng-click="templateType = 'b'" href="">Set template to b</a>
Controller:
$scope.templateType = 'a';
You can extend the example provided by #JB Nizet and use ng-include to swap templates, and managing larger size templates becomes easier.
myModule.directive('foo', function() {
return {
scope: {
templateType: '#'
},
template: '<div ng-include="\'/templateRoot/\' + templateType">'+
'</div>'
};
});
Now you can create separate template files and make them available on specific url (in this case /templateroot/a or /templateroot/b)
This is possibly easy, but I have browsed the different questions here on SO and in the Angular documentation and can't really find what I'm looking for.
In a directive:
function ssKendoGrid() {
return {
scope: {
dataSource: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl",
}
}
That uses the controller:
function ssKendoGridCtrl($scope) {
alert($scope.dataSource);
//other stuff
}
If I want to access the value of dataSource I assumed I'd be able to do something like this:
<div ng-controller="myController">
<div ss-kendo-grid data-source="test"></div>
</div>
MyController is:
function myController($scope) {
$scope.test = "Tested";
}
But it comes as undefined when I try to alert($scope.dataSource); the value..
Now I know I can do this:
<div ss-kendo-grid="test"></div>
And access it in the directive and controller like this:
return {
scope: {
ssKendoGrid: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
//In controller
alert($scope.ssKendoGrid);
But I would like to be able to pass in a JSON object to do various things with and this doesn't seem as clean as in the markup I'd like it to be more intuitive to look at the html and know what the dataSource is.
What I'm really looking for is an understanding of what I'm doing wrong, why doesn't this work?? I've obviously not got the right understanding of how to pass various things to the isolated scope of the directive.
SOLVED
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
Cheers
you need to access the directive scope variable as
<div ss-kendo-grid data-source="test"></div>
similarly as you name the directive in the HTML markup
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
And a better convention is to simply not use a directive with "data-" at the beginning of it.
invite.directive('googlePlaces', function (){
return {
restrict:'E',
replace:true,
// transclude:true,
scope: {location:'=location'},
template: '<input id="google_places_ac" name="google_places_ac" type="text" class="input-block-level"/>',
link: function(scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
console.log(scope.location);
scope.$apply();
// scope.$apply(function() {
// scope.location = location;
// });
});
}
};
});
I'm trying to customize a template within a directive and include references to attributes in the parent scope. I'm pretty new to Angular but I've done a fair bit of searching and I've based my attempts on Customizing the template within a Directive. However if I pass a reference to a parent scoped variable as an attribute to the directive it doesn't get resolved, possibly because it's still undefined at the time the compile function is called.
My directive definition looks like this:
app.directive('sectionHeader', function() {
return {
restrict: 'EC',
replace: true,
transclude: true,
scope: {sectionName:'#sectionName', imageUrl:'#imageUrl'},
compile: function(element, attrs) {
var imageHtml = attrs.hasOwnProperty('imageUrl') ? '<div style="float: left; padding-right: 5px;"><img class="float_left" src="' + attrs.imageUrl + '" alt=""/></div>' : '';
var htmlText =
'<div>' + imageHtml + '<h1 class="float-left">' + attrs.sectionName + '</h1>' +
'<div class="clear"></div>' +
'<div class="modal_hr"></div></div>';
element.replaceWith(htmlText);
},
};
});
And I'm using the directive like this:
<div class="section-header" section-name="{{currentFeatureName}}"></div>
The problem is the {{currentFeatureName}} variable from my controller doesn't appear to be defined when the compile function is called on the directive.
One way I've considered to get around this is within the compile function set up an observer function on the sectionName attribute that updates h1 element content when it sees a change. This seems a little clunky and I was wondering if there is a better or more elegant way of doing this.
Check out the $observe function in Directive docs.
But beside that, there actually seems to be no need to do what you were trying to do. See:
var app = angular.module('plunker', []);
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.currentFeatureName = 'Current Feature Name';
$scope.imageUrl = "https://lh3.googleusercontent.com/GYSBZh5RpCFwTU6db0JlHfOr_f-RWvSQwP505d0ZjWfqoovT3SYxIUPOCbUZNhLeN9EDRK3b2g=s128-h128-e365";
}
]
);
app.directive('sectionHeader', function() {
return {
restrict: 'EC',
replace: true,
transclude: true,
scope: {
sectionName:'#',
imageUrl:'#'
},
template: '<div><div style="float: left; padding-right: 5px;" ng-show="imageUrl"><img class="float_left" ng-src="{{imageUrl}}" alt=""/></div><h1 class="float-left">{{sectionName}}</h1><div class="clear"></div><div class="modal_hr"></div></div>'
};
});
HTML:
<div ng-controller="AppController">
<div class="section-header" section-name="{{currentFeatureName}}" image-url="{{imageUrl}}"></div>
</div>
Plunker.
You are correct on why this isn't working. Interpolated attributes are not available when the compile and link functions run because no digest has occurred yet to resolve the interpolation to a value. You can read more about this here. You are also right about the solution: use attrs.$observe( 'sectionName', function ( val ) { ... });
However, it doesn't look like you need a dynamic template. If this was your template:
<div>
<div style="float: left; padding-right: 5px;" ng-show="{{imageUrl}}">
<img class="float_left" ng-src="{{imageUrl}}" alt="" />
</div>
<h1 class="float-left">{{sectionName}}</h1>
<div class="clear"></div>
<div class="modal_hr"></div>
</div>
Then you wouldn't need any logic in a compile or link function. Perhaps this pattern will also help you.