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)
Related
I'm trying to have HTML inside a popover content.
It's possible with a specific directive on the next versions, but I need to use the 0.12.1 version of angular-bootstrap since I'm using Angular 1.2.28
Any idea how I could implement this ?
I'm using a simple case :
<button popover="{{ testData }}"
popover-placement="bottom"
class="btn btn-icon btn-icon-sm btn-icon-default">
<i class="fa fa-search"></i>
</button>
With in js :
$scope.testData = "<b>Bold data</b>" + " Normal data " + " <b>Encoded bold data</b>";
But obviously it's not working since HTML is not yet supported as popover content in this version.
Ok, inspired from a plunker, it can be achieved by adding the following directives :
.directive("popoverHtmlUnsafePopup", function () {
return {
restrict: "EA",
replace: true,
scope: {
title: "#",
content: "#",
placement: "#",
animation: "&",
isOpen: "&"
},
templateUrl: "template/popover/popover-html-unsafe-popup.html"
};
})
.directive("popoverHtmlUnsafe", ["$tooltip", function ($tooltip) {
return $tooltip("popoverHtmlUnsafe", "popover", "click");
}])
With the template module :
angular.module("template/popover/popover-html-unsafe-popup.html", []).run(["$templateCache", function ($templateCache) {
$templateCache.put("template/popover/popover-html-unsafe-popup.html",
"<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
" <div class=\"arrow\"></div>\n" +
"\n" +
" <div class=\"popover-inner\">\n" +
" <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
" <div class=\"popover-content\" bind-html-unsafe=\"content\"></div>\n" +
" </div>\n" +
"</div>\n" +
"");
}]);
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 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>
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.
My index.html has the following div
<div ng-view></div>
And I have my app declared as follows :
angular.module('app', [])
.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/listen', {
templateUrl : 'partials/listen.html',
controller : PlaylistCtrl
})
.when('/settings', {
templateUrl : 'partials/settings.html',
controller : SettingsCtrl
})
.otherwise({
redirectTo : '/listen'
})
}
])
;
Now, when I'm at the homepage (i.e. /#/listen), and click on a link to /#/settings, it replaces the contents of the page with the contents from partials/settings.html. How can I modify it so that the contents aren't replaced, but just added on? My goal is to have settings show up in a modal dialog, so I don't want the previous contents of the page to get cleared out.
ng-view is not going to help you here.
Instead you should combine ng-include with ng-switch or ng-show.
<div><ng-include src="listen.html"/></div>
<div ng-show="isOnSettingsUrl()"><ng-include src="settings.html"/></div>
Or something along those lines.
In the parent controller you need to read the $routeParams so that you can implement isOnSettingsUrl().
That's not possible with ng-view. You need to create an own directive and include it in your index.html:
<modal></modal>
Angular-ui has an implementation. Maybe you should check it out.
Edit:
In the past I've made my own modal (when testing out angular). I just started to learn angular, so it has lots of room for improvement (read now i would do it differently). However, it could give you an example:
app.directive('modal', function($compile, $http) {
return {
restrict: 'E',
replace: true,
compile: function(elm, attrs) {
var htmlText =
'<div id="' + attrs.id + '" class="modal hide fade" aria-hidden="true">' +
'<div class="modal-header">' +
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>' +
'<p> </p>' +
'</div>' +
'<div class="modal-body">' +
'<div>to be replaced</div>' +
'</div>' +
'<div class="modal-footer">' +
'<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>' +
'</div>' +
'</div>';
elm.replaceWith(htmlText);
return function postLink(scope, elm, attrs) {
var modal = $('#' + attrs.id);
modal.css({
width: '60%',
left: '20%',
margin: 'auto auto auto auto',
top: '10%'
});
var modalBody = modal.children('.modal-body');
modalBody.css({
maxHeight: '800px'
});
var replaceDiv = modalBody.children();
$http.get(attrs.src).success(function(data) {
var childScope = scope.$new();
childScope.modalMode = true;
var element = $compile(data)(childScope);
angular.element(replaceDiv).replaceWith(element);
});
};
}
};
});
Html:
<a class="btn" data-target="#myId" data-toggle="modal" data-backdrop="static">Open modal</a>
<modal id="myId" src="path/to/partial" ></modal>
ng-view directly updates itself with the content came from the routeProvider, not only useful, but also increases performance as it unloads the controller and the watchers which you won't be using (you are on a different page).
Also a change in the url should change the view, not append anything. What would happen if I go directly to the page? That definitely won't be intuitive.
It is expected to have a index.html page which includes layout, ng-view which will be the changing page. For other things, you can use ng-include.
In your case, I assume that you want to show a partial page in which user can update their settings.
You don't need to this with routing, you can have the settings partial within the play template and use ng-show on the partial. You then just put a link to show the partial page.
If however, you want something like grooveshark; then you do need to use ng-view with routing to settings, then you should put the play template (and its controller) outside of the ng-view as you expect it to show up in every page.