I'm fairly new to AngularJS Directives and was wondering how I could implement passing a custom string, which is a url that contains a controller variable, to be used later on by that controller?
Calling Directive
<div ng-controller="MyController">
<my-custom-form after-submit="group/{{insertedId}}" />
</div>
Directive
app.directive('myCustomForm', function() {
return {
templateUrl: 'myCustomForm.html',
link: function(scope, element, attr) {
scope.loadUrl = attr.afterSubmit;
}
}
});
Controller
app.controller('MyController', function($scope, $location, $http) {
$scope.loadUrl = '';
$scope.insertedId = NULL;
$http({
method:'POST',
url:'edit.php',
data:formData
}).success(function(response) {
$scope.insertedId = response.dbInsertedPrimaryId; // used in "after-submit"
$location.path($scope.loadUrl);
});
});
So when $location.path() called it sends me to a url for example:
http://example.com/group/22
The reason for this is so that I can pass a specific/different url each time the directive is used, like so
<div ng-controller="MyController">
<my-custom-form after-submit="user/{{insertedId}}" />
<my-custom-form after-submit="home/{{insertedId}}/something" />
<my-custom-form after-submit="{{insertedId}}/anything" />
</div>
Or maybe there is a better way to do this? I don't know that's why I'm asking. Thanks.
Try with this
app.directive('myCustomForm', function() {
return {
scope: {
afterSubmit = '#',
},
templateUrl: 'myCustomForm.html',
controller: function($scope) {
console.log($scope.afterSubmit);
}
}
});
Notice that the scope variable name is camelCase (stackOverflow), instead the attribute is dash-case (stack-overflow)
This because HTML cannot understand camelCase and javascript cannot understand dash-case
The # for the variable stand for string. You can use:
# for strings
= for variables coming from the controller (ex: after-submit="ctrl.submitUrl")
& for callbacks (ex: on-submit="ctrl.submit()")
You should use controller: instead of link: ... Here the reason: AngularJS : link vs compile vs controller
Related
In my angular application, I defined a directive foo-directive, which is placed in a parent controller as below:
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click="parent.tClick()" tab-click2="parent.tClick2(v)"></foo-directive>
</div>
</div>
pass two method: parent.tClick() and parent.tClick2(v) into the directive and bind to tab-click and tab-click2 attributes respectively. The difference is that the second one has a parameter
and the JS code goes as below:
function ParentCtrl($timeout){
this.city= "London";
this.tClick = function(){
console.log("debugging parent tclick...");
}
this.tClick2 = function(v){
console.log("debugging parent tclick2...");
console.log(v)
}
}
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick();
this.tabClick2("abc");
}
}
}
function fooDirective() {
return {
scope: {
data: '=',
tabClick : "&",
tabClick2: "&"
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.click()">{{ foo.data }}</div>',
link: function ($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
Now the issue comes from the second method this.tabClick2("abc"). There is TypeError message. I have reproduced this issue with this live demo:
https://jsfiddle.net/baoqger/sv4d03hk/1/
any help?
When passing the functions into your directive, you should pass the "reference" to the function, rather than the "result" of the function. Adding the parenthesis, is actually executing the function and returning the result to the directive. As neither function returns a value, they will both be passing undefined.
Given that the value of the parameter you want to pass (v) to the function is inside the directive scope, not that of the parent, you dont need to even tell the directive that the function accepts a parameter. You just pass it to the function inside the directive. ie.
<foo-directive data="parent.city" tab-click="parent.tClick" tab-click2="parent.tClick2"></foo-directive>
According to the docs, using & is then you want to evaluate the result of the attribute:
The & binding allows a directive to trigger evaluation of an expression in the context of the original scope, at a specific time.
Instead, we want to be able to execute the passed attribute, and in particular give it a variable. As such either = (two-way binding) or # (one-way binding) are probably more appropriate for what we're after.
tabClick: "=",
tabClick2: "="
You can also do away with your controller completely by updating your template.
template: '<div ng-click="foo.tabClick();foo.tabClick2(data)">{{ foo.data }}</div>'
Updated JSFiddle
function ParentCtrl($timeout) {
this.city = "London";
this.tClick = function() {
console.log("debugging parent tclick...");
}
}
function FirstCtrl() {}
function fooDirective() {
return {
scope: {
data: '=',
tabClick: "="
},
controller: 'FirstCtrl',
controllerAs: 'foo',
bindToController: true,
template: '<div ng-click="foo.tabClick(data)">{{ foo.data }}</div>',
link: function($scope, $element, $attrs, $ctrl) {
//console.log($ctrl.name);
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('FirstCtrl', FirstCtrl)
.controller('ParentCtrl', ParentCtrl)
<script src="https://code.angularjs.org/1.6.2/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ParentCtrl as parent">
<foo-directive data="parent.city" tab-click=":: parent.tClick"></foo-directive>
</div>
</div>
PS. if you're concerned about performance using # or =, consider one time bindings using ::. ie. <foo-directive data="parent.city" tab-click=":: parent.tClick" tab-click2=":: parent.tClick2"></foo-directive>
Try the following snippet of code for your "FirstCtrl":
function FirstCtrl() {
this.$onInit = function(){
this.click = function(){
this.tabClick({v: this.data});
}
}
}
As you are using an expression binding (&), you need to explicitly call it with a JSON containing "v" and it's value. like the following:
this.tabClick({v: this.data});
I am currently writing an angular directive that uses a template in a different HTML file and an isolated template. The directive gets some string via # to its scope and that value is available in teh controller function.
Somehow its not available via {{}} in the HTML template. Why is that so? How can I change that? I read something about the template using the parent scope but I don't fully understand that.
Here is a code example:
angular.module('moduleName')
.directive('aGreatDirective', function () {
return {
restrict: 'E',
scope: {
mapid: '#'
},
templateUrl: "path/to/template.html",
controller: ['$scope', function (scope) {
console.log($scope.mapid); // is defined
}
}
});
And the html code for the template:
<div id="{{mapid}}"></div>
The result in the browser is exactly the same where it should be:
<div id="theValueOfmapid"></div>
Thanks for your help!
PS Here is a jsfiddle: fiddle
Your fiddle was incorrect since you didn't have your controller defined or $scope injected properly. The following will work just fine:
template:
<div ng-controller="MyCtrl">
<a-great-directive mapid="thisisthemapid"></a-great-directive>
Some other code
</div>
js:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function () {
});
myApp.directive('aGreatDirective', function() {
return {
restrict: 'E',
scope: {
mapid: '#'
},
template: "<div id='{{mapid}}'> {{mapid}} </div>",
controller: ['$scope', function($scope) {
console.log($scope.mapid); // is defined
}
]}
});
Fiddle
Note that in my example, the injected variable in your directive's controller should be $scope, not scope, for consistency reasons.
I have two directives and a controller, the problem is that i can't call a function 'addMarkers()' of the controller from my directive .
i have the following codes:
derectives.js
app
.directive('collection', function () {
var tpl = '<ul><member ng-repeat="member in collection" member="member"></member></ul>';
return {
restrict: "E",
replace: true,
scope: {
collection: '='
},
template: tpl
}
})
app
.directive('member', function ($compile) {
var tpl = '<li><a ng-click="addMarkers(member)" >{{member.title}}</a>'+
'<input class="align" ng-if="!member.children" type="checkbox" ng-checked="true"/></li>';
return {
restrict: "E",
replace: true,
scope: {
member: '='
},
template: tpl,
link: function (scope, element, attrs) {
if (angular.isArray(scope.member.children)) {
element.append("<collection collection='member.children'></collection>");
$compile(element.contents())(scope)
}
}
}
})
controller.js
app
.controller('IndexCtrl', function($scope, itemProvider){
itemProvider.getItems().success(function(data){
$scope.items = data;
});
$scope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}
});
index.html
<div id="menu" ng-controller="IndexCtrl">
<nav>
<h2><i class="fa fa-reorder"></i>All Categories</h2>
<collection collection='items'></collection>
</nav>
</div>
$rootScope is the global scope which should be used only when required. It should be kept as clean as possible to avoid pollution of scope variables.
In order to access parent method from isolated scope you can use $parent service, as shown below:
scope.$parent.addMarkers();
example: basic example
In your case, The directive that wants to access the parent is again called from inside another directive,hence for such cases you can use $parent.$parent,
scope.$parent.$parent.addMarkers(); as shown in the following:
example:example for your case
This can be done if the number of directives using the parent scope is limited. If the hierarchy is long, then using multiple $parent makes the code clumsy. In those cases, it is preferable to add the parent method inside a service and call the service itself from the directives.
Example: service example
I should use $rootScope instead of $scope like below,
$rootScope.addMarkers = function(item){
alert("Helloo");
$scope.markers = itemProvider.addMarkers();
}
I am trying to expand on the bootstrap ui library with my own custom control. This control will be used in an AngularJS app. Currently, I'm getting stuck on the scoping.
My plunker is here
This plunker is a simplified version of a more complex control. The concept that I'm trying to highlight is the scoping. You will notice that the custom control, my-query, is pre-populated with the value of myController.$scope.query. You will also see that the query is put in the page underneath the custom control. As I type, the value does NOT get updated. Why? My code looks like the following:
myApp.directive('myQuery', [function() {
return {
restrict:'E',
transclude: true,
scope: {
query: '='
},
template: '<div ng-controller="myQueryController"><input type="text" ng-model="query" /><button ng-click="go_Click()">go</button></div>'
};
}]);
myApp.controller('myQueryController', ['$scope', function($scope) {
$scope.go_Click = function() {
$scope.$emit("goClicked");
};
}]);
What am I doing wrong?
In your directive template, you are adding an additional controller which is adding in another scope. That is what is causing the problem. Instead of doing it that way, move the controller logic into either a controller function or a link function defined on your directive, either will work.
Try this. Here's an example using a controller function. Note that I moved your original myQueryController inside the directive and removed the ng-controller directive from the myQuery directive's template.
'use strict';
var myApp = angular.module('myApp', []);
myApp.controller('myController', ['$scope', function($scope) {
$scope.queryValue = 'test';
$scope.$on('goClicked', function() {
$scope.performAction();
});
$scope.performAction = function() {
alert('Using ' + $scope.queryValue);
};
}]);
myApp.directive('myQuery', [function() {
return {
restrict:'E',
transclude: true,
scope: {
query: '='
},
template: '<div><input type="text" ng-model="query" /><button ng-click="go_Click()">go</button></div>',
controller : function ($scope) {
$scope.go_Click = function() {
$scope.$emit("goClicked");
};
}
};
}]);
<div ng-controller="myQueryController">
A controller creates a new scope. So <input type="text" ng-model="query" /> doesn't use query from the directive's scope but from the controller's scope. Instead of using a controller you can define the go_Clickfunction in the directive's link method.
Do you need this?:
http://plnkr.co/edit/6IrlnXvsi2Rneee0hGC8?p=preview
scope: {
model: '='
}
The problem was that you used a primitive type which was passed by value into your directive. Always use complex types which are passed by reference.
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.