Build template string inside directive - angularjs

I'm trying to build a string of HTML code inside the directive and then use that as the directive's template.
I tried this but it doesn't work.
myApp.directive('myDirective', [function() {
return {
restrict: 'E',
scope: {
data: '=' // this is the data that will shape the HTML
},
template: str,
controller: function($scope){
$scope.str = ****BUILD THE STRING HERE USING 'data'****
}
};
}]);
Then I tried passing the app's scope to the directive, but got an error again
myApp.directive('myDirective', ['$scope', function($scope) {
var str = ****BUILD THE STRING HERE USING $scope.data****
return {
restrict: 'E',
template: str
};
}]);
I'm confused about how I can do this. In the first example the string is built correctly and when I do
template: {{str}}
I see the string but obviously, it just shows up as text on the page and it's not interpreted as HTML code.
Is there a way to access either myApp's or the directive controller's scope within the return statement of the directive?
I could build the string outside of the directive, but even if I do that I still can't access myApp's scope within the directive.

Directives, by nature have access to the outer scope, if you don't strictly define it with an inner scope (or isolated scope). Example:
angular.module('app',[]).controller('ctrl', function($scope) {
$scope.example = {
message: "Hello world"
};
}).directive("myDirective", function() {
return {
template: '<strong>{{example.message}}</strong>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div my-directive></div>
</div>
As you can see in the example above - The directive "knows" the outer scope values without you need to actually inject it.
Now, you can create an isolated scope and by doing this you don't limit yourself to a given scope:
angular.module('app',['ngSanitize']).controller('ctrl', function($scope) {
$scope.example = {
html: "<strong>Hello world</strong>"
};
}).directive("myDirective", function() {
return {
scope: {
data: '='
},
template: '<div ng-bind-html="data"></div>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-sanitize.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div my-directive data="example.html"></div>
</div>
By including ngSanitize I an able to use the ngBindHtml directive and pass an HTML structure to the directive inner scope.

Strictly speaking what you're trying to do can be achieved by using ng-if to output html that uses different directives based on the data count in your controller. This is better 'separation of concerns', as it means you're moving your presentation logic into the view where it belongs, and your controller is then concerned with your business logic including a data count variable (which you can then use in the ng-if).
If using this approach, you'll want to put your data retrieval into a service that the controller uses, and use the same controller for both directives.

Related

How to retrieve custom property in isolate scope?

I've read quite a lot examples on how to do binding in an isolate scope but not how to retrieve scope custom properties in template.
I think the fix must be very simple but I just don't figure out what's wrong here
<!doctype html>
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script>
</head>
<body>
<div my-directive>
</div>
<script>
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {
data.myProperty:"1234"
},
template:'Inside myDirective, isolate scope: {{ data.myProperty }}'
};
})
</script>
</body>
</html>
Somehow, data.myProperty couldn't be reached.
You can't directly use and access bounded properties in bindings like you were doing data.myProperty:"1234". It will eventually result in error.
You have to pass custom property via attribute of your directive. Over here you can consider adding custom-data attribute, add mention the scope property name over it. So it would be passed to isolated scope directive.
Controller
$scope.data = {
myProperty: 'My Property Value'
}
Html
<div my-directive custom-data="data">
</div>
directive
angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'A',
scope: {
customData: '=data'
//without alias it will look like below.
//customData: '='
},
template:'Inside myDirective, isolate scope: {{ data.myProperty }}'
//without alias template would look like below
//template:'Inside myDirective, isolate scope: {{ customData.myProperty }}'
};
})
Note: It seems like you are using older unstable version. If possible update angular to latest angularjs 1.7.x version, to find more features and performant angularjs. After 1.5.x version, you could also use < inside binding (customData: '<data') to keep one way data binding flow. Just to not confuse you I used =(two way data binding) for demo.

angularJS - variable component templates

Is it possible to define a variable template for a component in angularJS 1.6?
Something like this:
<div class="test">
<{{$ctrl.GetElement()}}>
</div>
for cases in which I want to decide in runtime what the template be like.
Is there a way to do it?
Here is a simple example of a "variable template" using $compile. Let's define a "generator" directive which will be able to generate other directives:
app.directive('createDirective', function($compile) {
return {
scope: {
directiveName: '#'
},
link: function(scope, element) {
var newHtml = '<' + scope.directiveName +'></ '+ scope.directiveName +'>';
element.append($compile(newHtml)(scope));
}
};
});
This "generator" directive takes in a string (via the attribute "directive-name"), assembles new HTML, compiles it, and appends the resulting HTML to the generator directive.
I've defined a separate directive named "Hello", which I want to be called dynamically from the generator directive:
app.directive('hello', function() {
return {
restrict: 'E',
link: function(scope, element) {
element.append("Hello!");
}
}
});
Now, we can use the generator directive to compile the "Hello" directive
<div create-directive directive-name="hello"></div>
which results in this generated HTML
<hello class="ng-scope">
<!-- hello-->
Hello!
</hello>
In addition, we can pass a variable from a controller to the generator directive in a similar way:
app.controller('MainCtrl', function($scope) {
$scope.newDirective = "from-controller";
});
And in the HTML:
<div create-directive directive-name="{{newDirective}}"></div>
Be sure to take a look at the $compile documentation.
Demo

angularjs directive on bind output

I have something like this:
.controller('contr',['$scope', '$http',function($scope, $http){
$http.post(...).success(function(){
$scope.myTextVar = "some text here";
$scope.completed == true;
});
}]);
an HTML snippet like so:
<div class="myClass" ng-if="completed == true" manipulate-header>
<p>{{myTextVar}}</p>
</div>
and the directive, for simplicity sake let's say it looks like this:
.directive('manipulateHeader',function(){
return{
restrict: 'A',
link: function(scope, elem){
console.log(angular.element(elem).find('p'));
}
}
});
The manipulate-header directive is supposed to do some manipulation of the text inside the <p></p> tag, however, it runs before {{myTextVar}} gets replaced and hence it outputs {{myTextVar}} instead of some text here.
How may i get around this problem? (i can pass the variable inside the directive scope, but i'm thinking there must be another way).
EDIT: the controller is there and working as intended. Issue is not related to it. I didn't include it to shorten the post.
If it MUST be a directive
If you're trying to do string manipulation in your link function, you're going to have a bad time. The link function is executed before the directive is compiled (that's the idea of the link function), so any bindings (ng-bind or otherwise) will not have been compiled inside of link functions.
To execute code after the compilation stage, you should use a controller. However, you cannot access the DOM in controllers (or rather, you shouldn't). So the logical solution is to instead modify the scope argument instead. I propose something like this:
angular.directive('manipulateHeader', function() {
return {
scope: {
myTextVar: '='
},
controller: function($scope, myFilter) {
// you can't use bindToController here because bindToController executes *after*
// this function
this.modifiedText = myFilter($scope.myTextVar);
},
controllerAs: 'ctrl',
// display the modified text in a template
template: '<span ng-bind="ctrl.modifiedText"></span>'
};
})
.filter('myFilter', function() {
return function(inputText) {
// do some text manipulation here
};
});
Usage:
<manipulate-header myTextVar='myTextVar'></manipulate-header>
Or:
<p>{{ myTextVar | myFilter }}</p>
You could, of course, make this an attribute instead, but best practice indicates that directives that have a template should be an element instead.
The above is only if you need this to be a directive. Otherwise, it should almost definitely be a filter.
If you need to change the $scope variable from your controller you need to isolate scope ,
scope:{
myattr='#', // this will provide one way communication , you can define in your template as <p myattr="hello"><p>
message:'&', //This allows you to invoke or evaluate an expression on the parent scope of whatever the directive is inside
message:'=' // sets up a two-way binding expression between the directive's isolate scope and the parent scope.
}
refer https://docs.angularjs.org/guide/directive
As suggested by #DanPantry - you most likely want a filter not a directive
Read this guide about using filters
https://docs.angularjs.org/guide/filter
Here is an example of such a filter (from documentation)
angular.module('myStatefulFilterApp', [])
.filter('decorate', ['decoration', function(decoration) {
function decorateFilter(input) {
//This is the actual modification of text
//That's what you are looking for
return decoration.symbol + input + decoration.symbol;
}
decorateFilter.$stateful = true;
return decorateFilter;
}])
.controller('MyController', ['$scope', 'decoration', function($scope, decoration) {
$scope.greeting = 'hello';
$scope.decoration = decoration;
}])
.value('decoration', {symbol: '*'});
I am not sure whether you defined $scope.myTextVar
in correct scope. Like, if you defined it in any controller, then directive should be under the controller scope.
Here is the updated HTML
<div ng-controller ="MainController">
<div class="myClass" manipulate-header>
<p>{{myTextVar}}</p>
</div>
</div>
JS :
app.controller('MainController', ['$scope', function($scope) {
$scope.myTextVar = "some text here";
}]);
app.directive('manipulateHerader',function(){
return{
restrict: 'A',
link: function(scope, elem){
console.log(angular.element(elem).find('p'));
}
}
});
Here is the plunker

Using a function created in parent scope and also local scope varibles in an AngularJS directive with a template

So I am trying to create a directive which will replace an element with HTML which includes an ng-click which calls a function created in $scope.
An example I made can be found at http://jsfiddle.net/adinas/wbfu9ox3/5/
My HTML is
<div ng-app="myapp" ng-controller="mycontroller">
{{greeting}}
<!--This works-->
<div xyz></div>
<!--This works-->
<div ng-click="dosomething('Click me 2')">Click me 2</div>
<!--The click added in the directive here does NOT work-->
<div abc mytext="Click me 3"></div>
My JS is
var myapp = angular.module('myapp',[]);
myapp.controller('mycontroller', ['$scope', function($scope) {
$scope.greeting = 'Hola!';
$scope.dosomething = function (what) {
alert(what);
};
}]);
//Works
myapp.directive('xyz', function () {
return {
template: '<div ng-click="dosomething(\'Click me 1\')">Click me 1</div>'
};
});
//Does not work
myapp.directive('abc', function () {
return {
template: '<div ng-click="dosomething(\''+attr.mytext+'\')">Click me 3</div>'
};
});
I made 3 elements. The first two show that A. without the directive the click work. B. a directive without using the 'dosomething' function also works.
The 3rd element which tries to pass a parameter to the 'abc' directive and also call the function fails.
How can I also pass a parameter and also use the function? Thanks.
Well, if you look in the console, you will see an error that shows why this is not working:
Error: attr is not defined
You're trying to access an attr variable that doesn't exist.
The way to include attribute values in your directive template is to include them in the directive's scope:
scope: {
mytext: '#'
}
This means that there will now be a mytext variable in the scope of the directive.
This creates a problem, however, since the use of the scope: {} option creates an isolate scope for the directive, and this means that the enclosing controller's scope is not part of the directive's scope.
If feasible, the clean approach here is to also pass in the dosomething function as a parameter of the directive:
<div abc mytext="Click me 3" action='dosomething'></div>
return {
template: '<div ng-click="action(mytext)">Click me 3</div>',
scope: {
mytext: '#',
action: '='
}
};
http://jsfiddle.net/jx1hgjnr/1/
But if that's not what you want to do and you really want to access dosomething from the parent scope, one thing you can do is use $parent.dosomething:
return {
template: '<div ng-click="$parent.dosomething(mytext)">Click me 3</div>',
scope: {
mytext: '#'
}
};
http://jsfiddle.net/c815sqpn/1/
Such directive should have isolated scope, so it can be used everywhere.
That is simple directive that introduce two parameters: func and param.
app.directive('pit', function () {
return {
scope : {
func: '=',
param: '='
},
template: '<button ng-click="func(param)">Click me 3</button>'
};
});
And this is how u use it in html:
<div pit func="test1" param="test2"></div>
My plunker:
http://plnkr.co/edit/YiEWchRPwX6W7bohV3Zo?p=preview

Accessing parent controller data in combination with isolated scope in Angular

First the code, then the explanation:
index.html
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.7" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<my-directive data="1" />
<my-directive data="2" />
</div>
</body>
</html>
app.js
angular.module("myApp", []).directive("myDirective", function ($parent) {
return {
restrict: "E",
scope: {
data: "#",
},
template: function(element, attrs){
switch(attrs.data){
case '1':
return '<h3>'+ $parent.stringForDirective1 + '</h3>';
case '2':
return '<h3>'+ $parent.stringForDirective2 + '</h3>';
}
}
};
}).controller('myCtrl',function($scope){
$scope.stringForDirective1 = 'I was returned by the directive with HTML attribute data having the value 1.'
$scope.stringForDirective2 = 'I was returned by the directive with HTML attribute data having the value 2.'
});
Now for the explanation. If I were to set 'scope: false' on my directive, I could easily access the controller's data as the directive is positioned inside of its scope. However, from my understanding, in order to use any value from an HTML attribute with a custom directive, the entire directive must be put into an isolated scope.
I want to use an HTML attribute to return a template that uses the parent controller's data.
How do I get the benefits of the controller data when using 'scope: false' while being able to pass in a custom HTML attribute?
The $parent example does not work, I simply added it to show the way I've been thinking towards a solution, and I think it shows my intent clearly.
Not sure why you injected the $parent dependency. I hope that was the one you only used to show how you were thinking: The $parent example does not work, I simply added it to show the way I've been thinking towards a solution, and I think it shows my intent clearly.
Anyway, you don't need any of that. To make it all work, simply get rid of that dependency, don't concat the parent scope values into the template, but let Angular take care of it after it compiles the template (using the double curly braces for binding):
switch(attrs.data){
case '1':
return '<h3>{{$parent.stringForDirective1}}</h3>';
case '2':
return '<h3>{{$parent.stringForDirective2}}</h3>';
}
That will still look for scope.$parent, which is what you wanted.
See the fully working example here: http://plnkr.co/edit/pW5G2Yy4SelW5DxKBMqW?p=preview
Or here, as a snippet:
angular.module("myApp", [])
.directive("myDirective", function() {
return {
restrict: "E",
scope: {
data: "#",
},
template: function(element, attrs) {
switch (attrs.data) {
case '1':
return '<h3>{{$parent.stringForDirective1}}</h3>';
case '2':
return '<h3>{{$parent.stringForDirective2}}</h3>';
}
}
};
})
.controller('myCtrl', function($scope) {
$scope.stringForDirective1 = 'I was returned by the directive with HTML attribute data having the value 1.'
$scope.stringForDirective2 = 'I was returned by the directive with HTML attribute data having the value 2.'
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<my-directive data="1"></my-directive>
<my-directive data="2"></my-directive>
</div>
</body>
Note: had to specify the directive closing tag, since the shorthand version reads only the first directive (Plunker saying that the trailing solidus is not allowed on your directive element), see here: http://plnkr.co/edit/Qt0z0poU0ogoQq4C9a3n?p=preview
There are three possible modes of scope that a directive can have:
isolated scope (scope: {})
child scope (scope: true)
inherited scope (scope: false)
Depending on the needs of your directive, any one of these scope modes is valid.
If you want to create a directive with isolated scope, then you can pass in the models to your directive's isolated scope through the element's attributes:
scope: {
modelA: '=', // model binding
modelB: '#', // string binding
modelC: '&' // method binding in parent scope
}
Attributes
<div directive model-a="user" model-b="hello {{ user.name }}" model-c="addUser(user)"></div>
Example (not an ideal directive implementation but done to show how models can be passed to an isolated scope through attributes)
angular.module("myApp", []).directive("myDirective", function ($parent) {
return {
restrict: "E",
scope: {
data: "#",
stringForDirective1: '=?',
stringForDirective2: '=?'
},
template: '<h3 ng-if="data = '1'">{{stringForDirective1 }}</h3><h3 ng-if="data = '2'">{{stringForDirective2 }}</h3>'
};
}).controller('myCtrl',function($scope){
$scope.stringForDirective1 = 'I was returned by the directive with HTML attribute data having the value 1.'
$scope.stringForDirective2 = 'I was returned by the directive with HTML attribute data having the value 2.'
});
HTML
<body ng-app="myApp">
<div ng-controller="myCtrl">
<my-directive data="1" string-for-directive1="stringForDirective1" />
<my-directive data="2" string-for-directive2="stringForDirective2" />
</div>
</body>

Resources