I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks
Related
I am trying to make an angular directive that renders dynamic content urls based on an attribute placed on the directive. Example:
Directive:
angular
.module('myModule')
.directive('myContent', directive);
function directive() {
return {
replace: true,
templateUrl: function (elem, attrs) {
return attrs.contentUrl;
}
};
}
HTML:
<div my-content content-url="url/to/my-content.html"></div>
However what I would like is for the content-url attribute to be populated by a string from the controller. So let's say the controller is using the "controllerAs" syntax with the name "home", I would like the html to read:
<div my-content content-url="{{home.myContent.url}}"></div>
However within the directive's templateUrl function, the contentUrl attribute is being sent literally as "{{home.myContent.url}}". How can I get this value to evaluate before running the templateUrl function? Or, is there a better way to have simple, dynamic content available from a directive?
The answer is provided by #crhistian-ramirez:
Just use ng-include
<div ng-include="vm.myContent.url"></div>
I understand how to pass directives through my custom directive, like this:
Page.html
<my-directive read-only-attr="myVariable" label-style-attr="anotherVariable"></my-directive>
Directive
myApp.directive("myDirective", function () {
return {
restrict: "E",
templateUrl: "myTemplate.html",
scope: {
readOnlyScopeVar: "=readOnlyAttr",
styleScopeVar: "=labelStyleAttr"
},
link: function (scope, element, attrs) {
}
};
});
Template
<div>
<label ng-style="styleScopeVar" />
<input type="text" ng-readonly="readOnlyScopeVar" />
</div>
My template is much more complex than this but I simplified it for the question.
My question is: How do I prevent ngReadonly and ngStyle from having to run if the user hasn't specified a "read-only-attr" or "label-style-attr" on my directive? There are tons of common angular directives that I want to allow people to apply to the input and other elements inside my template (ngClass, ngDisabled, ngChange, ngPattern, ngIf, etc), but I don't want to run them all if the person hasn't specified them on my directive. It's as if I need a template to build the template.
Also, note that I've read about transclusion but I don't like the idea of allowing the user to edit the input element directly, and there are multiple elements I may want to apply things to like in this example I could change the label color if the read-only-attr reference is true.
One way to do it would be to use $compile. Here's a working plnkr:
https://plnkr.co/edit/S8pUSH?p=preview
Note there are many ways to do this, and this one is just one simple example for demonstration:
var app = angular.module('app', []); //define the module
//setup the template
app.run(['$templateCache', function($templateCache){
$templateCache.put('someDirectiveTmpl','<div>\
<label $$%%ngStylePlaceholder$$%% />My Label:</label>\
<input type="text" $$%%ngReadonlyPlaceholder$$%% />\
</div>');
}])
/**
* #description someDirective gets a config object that holds dynamic directives' names and values. e.g.:
* {
* 'ngStyle': '{background: red;}',
* 'ngReadonly': true
* }
*
*/
app.directive('someDirective', ['$log', '$compile', '$templateCache', function($log, $compile, $templateCache){
return {
restrict: 'AE',
controllerAs: 'someDirectiveCtrl',
scope: {},
bindToController: {
directiveConfig: '='
},
controller: function($scope, $element){
// a method to convert camelcase to dash
function camelCaseToDash( myStr ) {
return myStr.replace( /([a-z])([A-Z])/g, '$1-$2' ).toLowerCase();
}
// get the template
var template = $templateCache.get('someDirectiveTmpl');
var placeHolderRegExp = /\$\$%%(.*)\$\$%%/g;
// place the binding per existing property
angular.forEach(this.directiveConfig, function(varName, key){
template = template.replace('$$%%' + key + 'Placeholder$$%%', camelCaseToDash(key) + '="someDirectiveCtrl.directiveConfig.' + key + '"');
});
// remove unneeded properties placeholders
template.replace(placeHolderRegExp, '');
//compile the directive
var templateElement = angular.element(template);
$compile(templateElement)($scope);
// append to element
$element.append(templateElement);
}
}
}]);
Note the $$%%ngAnythingPlaceholder$$%%in the template. I get the config from parent directive (in plnkr I've used a controller for simplicity). I use a config object in the example (you could do this with separate variables, but I like setting a config object API).
I then replace the placeholders according to what I've got in the config and remove what I don't need. I then compile the template.
In the parent directive's controller you could do something like what I did in the controller:
$scope.config = {
ngReadonly: true
}
Again, I note you should not use a controller and of course, not to
use the $scope itself but the directive's controller's this. I use
$scope and controller here only for ease of demo.
You can add anything you want to this config (and of course, add placeholders to the various parameters in the template).
Now just add the directive to your template:
<some-directive directive-config="config"></some-directive>
The question title explain my problem i want to send data from a controller to directive so i can use the data in the directive controller or view.
Here is the controller code:
$scope.following = product.vendorId.isUserFollowing;
In the controller view:
<vas-follow following="{{following}}"></vas-follow>
following the property am trying to pass to the directive, the directive code:
.directive('vasFollow', vasFollow);
function vasFollow() {
var directive = {
restrict: "EA",
scope: {
following: '#'
},
link: link,
controller: vasFollowCtrl,
templateUrl: 'templates/directives/vasFollow.html',
};
return directive;
function link(scope, element, attrs) {
/* */
};
}
I tried first to use the following like so {{following}} in the directive view but it's not passing, also it is undefined in the directive controller.
I have read a lot of slimier issues but, i couldn't conclude why am having this problem.
Use ng-model for directive instead of your own replacement for it
Remove {{}} from assignment to share link to variable instead of just evaluated value
And please, use div or common DOM element instead of exact naming directive - it have side-effects in I.E.
I am working through the book Learning Angular Js by Brad Dayley. The book uses $scope in its examples. I am pushing myself to use controllerAs. In chapter seven the book focuses on creating custom directives.
I created a simple one similar to the example provided. Inside I am setting transclude to true. I am using the link function to append a footer to the parent div. Inside of the footer tag the author of the book calls scope.$parent.title In the example the title value comes from the parent controller.
.directive('myBox', function() {
return {
transclude: true,
restrict: 'E',
scope: {title: '#', bwidth: '#bwidth'},
template: "<div><span class='titleBar'>{{title}}"+ "</span> <div ng-transclude></div></div>",
link:function(scope, elem, attr, controller, transclude) {
console.log('scope', scope.$parent)
console.log('controller', controller);
elem.append('<span class="footer">'+ scope.$parent.title + '</span>');
elem.css('border', '2px ridge black');
elem.css('display', 'block');
elem.css('width', scope.bwidth);
}
}
})
Inside of the book the controller uses $scope, I wanted to use controller as and am using vm to equal this. Here is my function for the controller. The vm.title is supposed to be the value on the footer.
I am getting undefined from my console when I check the value.
function FunCtrl() {
var vm = this;
vm.title = "myApplication";
}
Here is a plunker of what I am trying
http://plnkr.co/edit/uUeKrTwLOfkcGpkTU1Uz?p=preview
When you use ng-controller syntax, it's just calling the members of the controller like "start()" in the scope.
<input ng-click="start()" type="button" value="Button"/>
But when you use controllerAs syntax, an instance of the controller is created and assigned to the reference variable that you have provided - "fun". So whatever you have defined in the controller is accessible only by using the reference. - "fun.start()" in the scope.
<input ng-click="fun.start()" type="button" value="Button"/>
Therefore you have to do the same when accessing the parent scope members if you have used controllerAs syntax like shown below
scope.$parent.fun.title
Demo
Note
The controller you are outputting in console is the one that belongs to the directive which you have not defined.
I have passed an object from controller to directive but when i am reading object in directive i am not able to, it seems in directive object is being read as string.Code is below , i wane to read City and State from the object.
Html File
<div ng-controller="WeatherController">
<div weather ng-object="{{Object}}"></div>
</div>
Controller
.controller('WeatherController', ['$scope', function ($scope) {
$scope.webpartData.OverviewData.Person.Address.City;
$scope.Object = {
City: '',
State: ''
};
$scope.Object.City = 'TestCity';
$scope.Object.State = 'TestState';
});
})
}])
Directive
angular.module('WeatherModule', [])
.directive('Weather', ["$timeout", function($timeout) {
return {
restrict: 'EA',
template: '<div id="weather"></div>',
scope: {
ngObject: '#ngObject'
},
link: function(scope, element, attrs) {
scope.$watch('ngObject', function(value) {
scope.ngObject = value;
});
$timeout(function() {
console.log('Location' + scope.Object.City + ',' + scope.Object.State);
}, 100);
}
};
}])
There are differences between #, = and & in referencing the directive scope members.
1. "#" (Text binding / one-way binding)
2. "=" (Direct model binding / two-way binding)
3. "&" (Behaviour binding / Method binding)
# means that the changes from the controller scope will be reflected in the directive scope but if you modify the value in the directive scope, the controller scope variable will not get affected.
# always expects the mapped attribute to be an expression. This is very important; because to make the “#” prefix work, we need to wrap the attribute value inside {{}}.
= is birectional so if you change the variable in directive scope, the controller scope variable gets affected as well
& is used to bind controller scope method so that if needed we can call it from the directive
In your case you may need to use = instead of #
Have a look on the following fiddle (It's not mine, but it have good and to the point illustration) http://jsfiddle.net/maxisam/QrCXh/
Some Related Questions also:
What is the difference between '#' and '=' in directive scope in AngularJS?
What is the difference between & vs # and = in angularJS
You should use "=" instead "#". And ng-object="Object" instead ng-object="{{Object}}".