I have two element-level directives, a search box and a search results. My markup is something like this (simplified):
<catalogue-search-box query="{{query}}">
<catalogue-search-results></catalogue-search-results>
I'm trying to access the search box controller from the search results directive, but the documentation suggests that in the directive's require property I can only find controllers on the same element or on the parent element. Is there a way to find controllers on adjacent elements?
After you comments here is how I would do it: use an object to hold all your state and pass it to both directives. Demo plunker
HTML
<body ng-controller="MySearchController">
<search-box search="mySearch"></search-box>
<search-results search="mySearch"></search-results>
</body>
JS
var search = angular.module('search', []);
//simulated service
search.service('Search', ['$timeout', '$q', function($timeout, $q) {
return {
findByQuery : function(query) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve([query + ' result1', query + ' result2']);
console.log('resolved query ' + query);
}, 2000);
return deferred.promise;
}
};
}]);
search.controller('MySearchController', ['$scope', function($scope) {
$scope.mySearch = {
query : ''
}
}]);
search.controller('SearchBoxCtrl', ['$scope', 'Search', function($scope, Search) {
$scope.execute = function(search) {
console.log(search);
if(search.query && search.query.length > 3 && !search.running) {
search.running = true;
search.promise = Search.findByQuery(search.query).then(function(val) {
search.results = val;
});
}
};
}]);
search.directive('searchBox', function(){
return {
restrict: 'E',
scope : {
search : '='
},
controller: 'SearchBoxCtrl',
template : '<div ng-hide="search.results">Query: <input type="text" ng-model="search.query" ng-disabled="search.running"></input> <button ng-click="execute(search)" ng-disabled="search.running">Search</button></div>',
replace: 'true'
};
});
search.controller('SearchResultsCtrl', function(){
});
search.directive('searchResults', function(){
return {
restrict: 'E',
scope : {
search : '='
},
controller: 'SearchResultsCtrl',
template : '<div ng-show="search.results"><div ng-repeat="result in search.results">{{result}}</div></div>',
replace: true,
link : function(scope, element, attrs, ctrl){
}
};
});
PS:
Don't use p tags in directive templates as the root node. The html parser reports 2 nodes if you have p child nodes and angular has a requirement for a single root node.
You can further use the promise in the controller to register other functions to execute when the results come in.
One way I've been experimenting with since the question is having some kind of controller directive i.e.
<catalogue-search>
<catalogue-search-box query="{{query}}">
<catalogue-search-results></catalogue-search-results>
</catalogue-search>
I can then access the "controller directive" this using the parent (^) modifier in my require statement. Each directive can then talk to each other via the controller directive.
Does this seem sensible or is it overcomplicating things?
Related
I am creating a custom directive in AngularJS. This directive should open a popup to display data. The code for the popup is in another html page and the custom directive injects the code into my main page. I am able to open the popup however I cannot display the existing data anywhere in the pop up.
Normally, I am able to display the data in the main page however the data just do not want to go into the html injected by the custom directive.
Like this I do not get any error however it does not pass the data.
Note: I had to trim some of the code here to simplify it.
This is my custom directive:
function updateCandidatePopup() {
var directive = {};
directive.restrict = "E";
directive.scope = {};
directive.templateUrl = "UpdateCandidatePopup.html";
directive.controller = function ($scope) {
$scope.SingleCandidate;
}
return directive;
}
This is where I register it:
myApp.directive("updateCandidatePopup", UpdateCandidatePopup);
This is how I use the directive in the mainpage
<update-candidate-popup value="SingleCandidate" class="modal fade" ng-model="SingleCandidate"
id="myUpdateModal"
role="dialog"
popup-data="SingleCandidate">
zxc</update-candidate-popup>
This is the UpdateCandidatePopup.html:
<div> {{SingleCandidate.FirstName}} </div>
This is the to display the data in the pop up controller: (FYI it is still trimmed)
myApp.controller('CandidatesController', function ($scope, $http, EmployerService, CandidateService) { //we injected localservice
//Select single data for update
$scope.getSingleData = function (C_ID) {
alert(C_ID);
$http.get('http://localhost:49921/api/Candidates/?C_ID=' + C_ID).success(function (data) {
$scope.SingleCandidate = data;
$scope.FName = $scope.SingleCandidate.FirstName;
alert($scope.SingleCandidate.FirstName);
alert($scope.FName);
}).error(function () {
$scope.error = "An Error has occured while loading posts!";
});
};
});
Sorry wrong !, answered your question, here I leave I found a code that will serve for your problem. In the background to the template you want to take, you let a controller and in the statement of your policy, put you going to do with those values, I think in your case is just printing.
myApp.directive('editkeyvalue', function() {
return {
restrict: 'E',
replace: true,
scope: {
key: '=',
value: '=',
accept: "&"
},
template : '<div><label class="control-label">{{key}}</label>' +
'<label class="control-label">{{key}}</label>' +
'<input type="text" ng-model="value" />'+
'<button type="button" x-ng-click="cancel()">CANCEL</button>' +
'<button type="submit" x-ng-click="save()">SAVE</button></div>',
controller: function($scope, $element, $attrs, $location) {
$scope.save= function() {
console.log('from directive', $scope.key, $scope.value);
$scope.accept()
};
}
}
});
jsFiddle
Solved the problem like below. It was only to inject to $scope in the directive controller.
myApp.directive("updateCandidatePopup", function () {
return {
templateUrl : "UpdateCandidatePopup.html",
restrict: 'E',
controller: function ($scope) {
}
}
});
I have a custom directive that simply $compiles a template into another.
.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
//
$http.get(templatePath, {
cache: $templateCache
}).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
I use it like:
<div static-include="components/campaign/details.html"></div>
Because I'm using aliases for the controller (using angular UI router), all model in any of the templates are like:
<p>Delivery Time: <span class="text-medium">{{CtrlAlias.campaign.newsletter.sentDate | date:CtrlAlias.currentUser.params.settings}}</span></p>
How do I make this directive work in multiple templates where CtrlAlias changes?
I tried changing $compile(contents)(scope); into $compile(contents)(scope.newCtrlAlias);
Any ideas?
When you $compile and then link, you are free to provide your own scope against which the compiled content is linked. That means that you can have the template content refer to some arbitrary ViewModel name, say vm:
<p>Delivery Time: <span>{{vm.campaign.newsletter.sentDate}}</span></p>
And link against a scope that has vm property:
var scope = { vm: {...} }
It actually might be even useful to use an isolate scope for your compiled content, to make sure that you aren't assuming an existence of scope variables that may or may not be there when the content is linked:
.directive('staticInclude', function($templateRequest, $compile) {
return {
link: function(scope, element, attrs){
var alias = attrs.alias || 'vm';
var templatePath = attrs.staticInclude;
var newScope = scope.$new(true); // isolate scope
newScope.vm = scope[alias];
// $templateRequest is essentially $http with $templateCache
$templateRequest(templatePath)
.then(function(html){
$compile(html)(newScope, function cloneAttachFn(clone){
element.empty();
element.append(clone);
});
});
}
};
});
Then usage is like so:
<div ng-controller="MainCtrl as main">
<div static-include="components/campaign/details.html" alias="main">
</div>
</div>
Really not sure I understand why you would need to use this so it's not easy to answer. However, one possible solution could be to wrap the template in a <div> to which you can append the desired controller information. It's a bit gross but it might work for you. You would have to pass in the controller name and it's alias but you could perhaps add that to your $state's data properties and access them from that but again it all seems a bit hacky.
DEMO
app.directive('staticInclude', function($http, $templateCache, $compile) {
return {
scope: {
ctrlName : '#',
alias : '#'
},
link: link
};
function link(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http
.get(templatePath, {
cache: $templateCache
})
.success(function(response) {
var ctrlStr = scope.ctrlName + ' as ' + scope.alias,
template = '<div ng-controller="' + ctrlStr + '" >' + response + '</div>',
contents = element.html(template).contents();
$compile(contents)(scope);
});
};
});
I'm just trying to do a simple directive, but for some reason the 2 way data binding isn't working in my directive. From my code you can see that a console log in the directive that will read the correct information I have in the $scope.displayMaintenance variable, but I can't change it in my directive.
HTML:
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
Controller:
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
console.log("i changed!: " + data);
});
Directive:
.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
templateUrl: '/partials/navbar/maintenance-banner.html',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
Any suggestions?
The issue may be that you use your directive inside another isolated scope.
I have created a sample: http://jsfiddle.net/2063n7te/
changing the model value using assignment replaces the model object which may not be reflected in the parent scope.
in short: do not bind primitives directly to the scope.
instead of
$scope.text = "foo";
use
$scope.input = {
text: "foo"
};
a good read is: http://www.thinkingmedia.ca/2015/01/learn-how-to-use-scopes-properly-in-angularjs/
point #4 applies specifically to the behaviour you are seeing.
Check that your directive can find the template URL.
Works for me:
var app = angular.module('app',[]);
app.controller('ctrl', function($scope) {
$scope.displayMaintenance = false;
$scope.$watch('displayMaintenance', function(data) {
alert("i changed!: " + data);
});
});
app.directive('maintenanceBanner', function() {
return {
restrict: 'E',
replace: true,
scope: {
displayMaintenance: '='
},
template: '<div>{{displayMaintenance}}</div>',
link: function(scope) {
console.log(scope.displayMaintenance);
scope.displayMaintenance = true;
}
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<maintenance-banner display-maintenance="displayMaintenance"></maintenance-banner>
</div>
Using directives I ended stuck when I needed to have more than one scope.
I'm building a data visualization app with Mongoose Node, Express and D3JS.
Here's the directive
angular.module('prodsChart', [])
.controller('businessCtrl', ['$scope','$http', 'BusinessSrv', 'Products', function($scope, $http, $location, BusinessSrv, Products) {
Products.get()
.success(function(data) {
$scope.products = data;
});
BusinessSrv.getTotal()
.success(function(data) {
$scope.businessSales = data;
});
}])
.directive( 'saleProd', [
function () {
return {
restrict: 'E',
link: function (scope, element) {
// Building the chart here
}
And the HTML :
<sale-prod></sale-prod>
Is it good to inject that way the Services in the Directive ?
Now I have 2 set of data in two $scope.
How do I use them in the directive ?
You can inject the $rootScope into your directive:
angular.module('prodsChart', [])
.directive( 'saleProd', ['$rootScope', function ($rootScope) {
// your code here
}]);
and then use it everywhere within the directive.
In 1st and 2nd example directive is in controller's scope and controller's datasets transferred to the directive as attributes. In 1st example directive can modify controller's data. In 2nd example directive use controllers data as strings and creates 2 objects 'productsObj' and 'salesObj' and can't modify parent's scope. It depends on how you handle attributes in the directive and how to transfer them into it. Just click on items in 'Product list (directive)' section and you'll see result.
1st: http://plnkr.co/edit/v46oEGHvUnxMNYsKAeaW?p=preview
var directive = function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'directive-template.html',
scope: {
products: '=',
sales: '='
}
};
};
html:
<div ng-controller="BusinessController as BusinessCtrl">
<sale-prod products="BusinessCtrl.products" sales="BusinessCtrl.sales"></sale-prod>
</div>
2nd: http://plnkr.co/edit/7CyIsqBNLbeZjyfbkGo9?p=preview
var directive = function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'directive-template.html',
scope: {
products: '#',
sales: '#'
},
link: function(scope, element, attrs) {
attrs.$observe('products', function(newVal) {
scope.productsObj = angular.fromJson(newVal);
});
attrs.$observe('sales', function(newVal) {
scope.salesObj = angular.fromJson(newVal);
});
}
};
};
html:
<div ng-controller="BusinessController as BusinessCtrl">
<sale-prod products="{{BusinessCtrl.products}}" sales="{{BusinessCtrl.sales}}"></sale-prod>
</div>
3rd example is just a piece of code that show's how to inject service in directive and controller. I add it because in you example i didn't see service injection in directive:
(function(undefined) {
var app = angular.module('prodsChart', []);
var controller = function($scope, Business, Products) {
// controller logic
};
var directive = function(Business) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// here you can use all Business service logic
}
};
};
var serviceBusiness = function() {
// business service logic
};
var serviceProducts = function() {
// products service logic
};
app.controller('BusinessController', ['$scope', 'Business', 'Products', controller])
.directive('saleProd', ['Business', directive])
.service('Business', serviceBusiness)
.service('Products', serviceProducts);
})();
html:
<div ng-controller="BusinessController as BusinessCtrl"></div>
<sale-prod></sale-prod>
You could inject a Service into a directive and use it to bring data across the application or use $emit.
A less elegant solution would be using a .value() and deal with it everywhere in your application.
Talk is cheap, show my codes first:
HTML:
<div add-icons="IconsCtrl">
</div>
directive:
angular.module('attrDirective',[]).directive('addIcons', function($compile){
return {
restrict : 'A',
controller : "IconsCtrl"
},
link : function (scope, elem , attrs, ctrl) {
var parentElem = $(elem);
var icons = $compile("<i class='icon-plus' ng-click='add()'></i>)(scope);
parentElem.find(".accordion-heading").append(icons);
},
}
});
controller:
function IconsCtrl($scope){
$scope.add = function(){
console.log("add");
};
}
now it works, when i click the plus icon, browser console output "add".
but i want to set the controller into the directive dynamically,like this:
HTML:
<div add-icons="IconsOneCtrl">
</div>
<div add-icons="IconsTwoCtrl">
</div>
Controller:
function IconsOneCtrl($scope){
$scope.add = function(){
console.log("IconsOne add");
};
}
function IconsTwoCtrl($scope){
$scope.add = function(){
console.log("IconsTwo add");
}
}
directive likes :
angular.module('attrDirective',[]).directive('addIcons', function($compile){
return {
restrict : 'A',
controller : dynamic set,depends on attrs.addIcons
},
link : function (scope, elem , attrs, ctrl) {
var parentElem = $(elem);
var icons = $compile("<i class='icon-plus' ng-click='add()'></i>)(scope);
parentElem.find(".accordion-heading").append(icons);
},
}
});
how to achieve my goal? thanks for your answer!
Now it is possible with AngularJS. In directive you just add two new property called
controller , name property and also isolate scope is exactly needed here.
Important to note in directive
scope:{}, //isolate scope
controller : "#", // # symbol
name:"controllerName", // controller names property points to controller.
Working Demo for Setting Dynamic controller for Directives
HTML Markup :
<communicator controller-name="PhoneCtrl" ></communicator>
<communicator controller-name="LandlineCtrl" ></communicator>
Angular Controller and Directive :
var app = angular.module('myApp',[]).
directive('communicator', function(){
return {
restrict : 'E',
scope:{},
controller : "#",
name:"controllerName",
template:"<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>"
}
}).
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 ");
}
})
Your case you can try this below code snippets.
Working Demo
HTML Markup :
<div add-icons controller-name="IconsOneCtrl">
</div>
<div add-icons controller-name="IconsTwoCtrl">
</div>
Angular Code :
angular.module('myApp',[]).
directive('addIcons', function(){
return {
restrict : 'A',
scope:{},
controller : "#",
name:"controllerName",
template:'<input type="button" value="(+) plus" ng-click="add()">'
}
}).
controller("IconsOneCtrl",function($scope){
$scope.add = function(){
alert("IconsOne add ");
}
}).
controller("IconsTwoCtrl",function($scope){
$scope.add = function(){
alert("IconsTwo add ");
}
});
This is how it is done:
Inside your directive element all you need is an attribute which gives you access to the name of the controller: in my case my card attribute holds a card object which has a name property. In the directive you set the isolate scope to:
scope: { card: '=' }
This isolates and interpolates the card object to the directive scope. You then set the directive template to:
template: '',
this looks to the directive's controller for a function named getTemplateUrl and allows you to set the templateUrl dynamically as well. In the directive controller the getTemplateUrl function looks like this:
controller: ['$scope', '$attrs', function ($scope, $attrs) {
$scope.getTemplateUrl = function () { return '/View/Card?cardName=' +
$scope.card.name; }; }],
I have an mvc controller which links up the proper .cshtml file and handles security when this route is hit, but this would work with a regular angular route as well. In the .cshtml/html file you set up your dynamic controller by simply putting as the root element. The controller will differ for each template. This creates a hierarchy of controllers which allows you to apply additional logic to all cards in general, and then specific logic to each individual card. I still have to figure out how I'm going to handle my services but this approach allows you to create a dynamic templateUrl and dynamic controller for a directive using an ng-repeat based on the controller name alone. It is a very clean way of accomplishing this functionality and it is all self-contained.
1- you don't need to use: var parentElem = $(elem); as elem is a jquery element. This is similar to: $($('#myid'))
2- you can not dynamically assign a controller, because directive controller is instantiated before the prelinking phase.
The directive controller has access to attrs, so you can dynamically choose which internal function (functions inside your controller) according to the value of your attrs['addIcons']
ps. note attrs['addIcons'] is camel naming.