angularJS templating, proper use of directives or alternatives? - javascript

In my app, I have a main controller as usual.
Let's say it's a webshop with different products and I want to compare X products. The HTML to display 1 product, is re-usable for all other products that get compared.
This means this HTML should be in a template (this is the point where I would've used Handlebars in a jQuery driven app).
I created a partial called itemDetails.php.
This template needs to get inserted into my view - let's say: - two times with different data (normally that'd be the model but in Angular that's the scope?).
So I tried it with two directives like this:
JavaScript
myApp.directive('activeItem', function() {
return {
restrict: 'A',
templateUrl: 'partials/itemDetails.php'
};
});
myApp.directive('activeCompareItem', function() {
return {
restrict: 'A',
templateUrl: 'partials/itemDetails.php'
};
});
Inside the main View
<div class="product left" active-item>{{item.name}}</div>
<div class="product right" active-compare-item>{{item.name}}</div>
This - of course - doesn't work as both products will have the same data from the parent scope.
So I tried to isolate the scope:
myApp.directive('activeItem', function() {
return {
restrict: 'A',
scope: {
item: '#itemOne'
},
templateUrl: 'partials/itemDetails.php'
};
});
myApp.directive('activeCompareItem', function() {
return {
restrict: 'A',
scope: {
item: '#itemTwo'
},
templateUrl: 'partials/itemDetails.php'
};
});
But that doesn't work either because apparently I can use "item" only as an HTML attribute now, not as an expression {{item.name}}.
Are directives even the right approach to templating? If yes, how can I pass data from the parent scope to a directive, keep them both in sync and update/re-render the directive when the objects get changed?
It seems odd to me that I have to create new directives for each time I want to use the template.

Directive declaration:
myApp.directive('activeItem', function() {
return {
restrict: 'A',
scope: {
item: '=data' //use "data" attribute to add the different data into the directive.
},
templateUrl: 'partials/itemDetails.php'
};
});
To use it with different data
<div class="product left" active-item data="itemOne">{{item.name}}</div>
<div class="product right" active-item data="itemTwo">{{item.name}}</div>

Related

Add ng-class attribute to root element of angularjs directive

I hav a directive that looks roughly like this, with an ng-class in the template:
module.directive('myDirective', [function() {
return {
restrict: 'E',
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
//...
}
}]);
My problem is, my classes from the ng-class are applied to the div, which ends up being nested inside of the directive element after the directive is compiled: <my-directive><div>...</div></my-directive>.
Is there any way to apply the classes to the root <my-directive> element instead? I know I can dynamically add the class using javascript in the link function or controller instead of the ng-class, but I am looking for a way to avoid this.
You can do that using the link function which gives you access to the created element ( directive )
module.directive('myDirective', [function() {
return {
restrict: 'E',
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
link: function(scope, element, attrs) {
element[0].classList.add('test')
}
}
}]);
This answer shows two different ways
Manipulate the classes from the controller
Use replace: true (deprecated)
Manipulate the classes from the controller
If you don't want to use replace: true, you can manipulate the directive's root classes from the controller by injecting $element.
app.directive('myDirective', function() {
return {
restrict: 'E',
template: `
<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}">
MY DIRECTIVE
<div>
`,
controller: function($element) {
$element.addClass("foo test test2");
$element.toggleClass("foo-expanded");
$element.removeClass("test2");
}
}
});
The DEMO on PLNKR
For more information, see
AngularJS element API Reference
Use replace: true (deprecated)
Another approach is to use replace: true:
module.directive('myDirective', [function() {
return {
restrict: 'E',
replace: true,
template: `<div ng-class="{'foo-expanded': expanded, 'foo': !expanded}"><div>`
//...
}
}]);
Keep in mind that the replace property is deprecated in AngularJS and has been removed in the new Angular (v2+).
For more information, see
AngularJS Comprehensive Directive API Reference - replace
How to use the 'replace' feature for custom AngularJS directives?
Why is `replace` property deprecated in AngularJS directives?
AngularJS $compile Service API Reference - Issues with replace:true.

angular directive in directive parent click

I'm creating a directive to show differences in text. For this directive, I need a couple of buttons which I've split up in directives. A simpliefied example would be:
.directive('differenceViewer', function($templateCache, $compile) {
return {
restrict: 'E',
scope: {
oldtext: '#',
newtext: '#',
template: '#',
heading: '#',
options: '=',
itemdata: '&',
method: '&'
},
replace: false,
link: (scope, element, attr) => {
element.append(angular.element($compile($templateCache.get(scope.template))(scope)));
}
};
}).directive('diffbutton', function() {
return {
restrict: 'E',
scope: {
method: '&'
},
template: '<button class="btn btn-sm btn-success" ng-click="method()">Rollback</button>',
replace: true,
terminal: true,
link: (scope, element, attr) => {
scope.directiveClick = function(){
console.log("directive method"); // is never called
}
}
}
})
I compile the html via a template script:
<script type="text/ng-template" id="differenceViewer.html">
<div class="ibox-footer">
<div class="row">
<div class="col-md-12">
<diffbutton method="clickedBtn()">Foo</diffbutton>
</div>
</div>
</div>
</script>
Where the diffbutton is created inside the html compiled by differenceViewer.
I need to call a method in the controller that is responsible for creating all the difference-views.
app.controller('MainCtrl', function($scope) {
$scope.clickedBtn = function() {
console.log('foo'); // is never called
}
})
Here's a plunker demonstrating the issue.
What do I need to change in order to be able to forward the button click on my directive in a directive to the controller method?
I've been working with the answers of this question but still can't make it work.
Note that, if I add
scope.clickedBtn = function() {console.log("bar");}
to the differenceViewer directive, it gets called - however I need to call the method in the controller instead.
Pass on a method from the parent to the child element and then trigger it on click. For example (pseudo code coming in )
<parent-directive>
<child-directive totrigger="parentClickCallback()"></child-directive>
</parent-directive>
then in your parent directive you set
$scope.parentClickCallback = function(){//do whatever you neeed}
and on your child in its scope binding you set
scope:{
totrigger:'&'
}
and on that child button you simply add
<button ng-click="totrigger()">ClickMe</button>
every time you click that button it will trigger parentClickCallback by reference attached to totrigger.
Why would you need to complicate your code so much I'm not really sure. If you not happy with scope binding just require controller in your directive and pass the controller bound function.

Bind ng-class to a local scope expression in custom AngularJS directive

I would like my custom directive to be able to pre-process its own classes based on some values bound to its parent scope. However, I can't seem to get the directive to set its own ng-class and execute a function on the local scope.
directive('testDirective',function(){
restrict: 'E',
scope: {
inputValue: '='
},
template: '<div>dynamically styled content</div>',
bindToController: true,
controllerAs: 'ctrl',
compile: function(element){
element.attr('ng-class', 'ctrl.getClass()');
},
controller: function(){
this.getClass = function(){
//return array of classes based on value of this.inputValue
}
}
});
Unfortunately no styles get applied as the expression is never evaluated and is just being assigned as a string. I see the following when I inspect the DOM element
ng-class="ctrl.getClass()"
I realize that I could just use jQuery to add my classes in the link function, but then they would not be bound to my data. I was also hoping to avoid using $watch if at all possible.
Any ideas are welcome!
Lets imagine you have a css file like this:
.myButton1 {
color: red;
}
.myButton2 {
color: blue;
}
The in your directive you could do this with ng-class:
directive('testDirective',function(){
restrict: 'E',
scope: {
inputValue: '='
},
template: '<div ng-class="ctrl.getClass()">dynamically styled content</div>',
bindToController: true,
controllerAs: 'ctrl',
controller: function(){
this.getClass(){
return (ctrl.inputValue === 'something' ? "myButton1" : "myButton2");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Your getClass method must return valid pre-defined, css classes, one or an array of them.

Can a directive delete itself from a parent scope

Let's say I have the following code
<div ng-app="app" ng-controller="controller">
<div ng-repeat="instance in instances>
<customDirective ng-model="instance"></customDirective>
</div>
</div>
And my custom directive has an isolated scope, defined as:
app.directive('customDirective', function($log) {
return {
restrict: 'E',
templateUrl: './template.htm',
scope: {_instance:"=ngModel"},
link: function($scope) {
....
}
});
In this directive, I have to option to delete it. My question is how can I communicate back to the array instances in the parent scope and tell it to destroy this object and in effect remove the deleted instance from my DOM?
Hope that makes sense.
according to New Dev in a previous comment, this is the way:
var app = angular.module('app', [])
.directive('customDirective', function($log) {
return {
restrict: 'EA',
template: 'remove me {{model.n}}',
scope: {
model:"=",
onRemove:"&"
}
}
})
.run(function($rootScope) {
$rootScope.instances = [{n:1},{n:2},{n:3},{n:4}];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-repeat="i in instances">
<custom-directive model="i" on-remove="instances.splice($index,1)">
</custom-directive>
</div>
</div>
First, don't use ngModel as a DOM attribute. This is an AngularJS directive used to bind form inputs to scope variables.
I've renamed it to model and added an extra attribute called index.
<div ng-app="app" ng-controller="controller">
<div ng-repeat="instance in instances>
<customDirective model="instance" index="$index"></customDirective>
</div>
</div>
Now in your controller you can listen for events (such as a custom event you might title removeCustom) emitted by children using $scope.$on().
app.controller('controller',function($scope) {
$scope.instances = [.....];
$scope.$on('removeCustom',function($index) {
delete $scope.instances[$index];
});
});
Then in your custom directive you have to use $scope.$emit() to broadcast your removeCustom event up the scope hierarchy to the controller.
app.directive('customDirective', function($log) {
return {
restrict: 'E',
templateUrl: './template.htm',
scope: {
model:"=",
index:"="
},
link: function($scope,$el,$attr) {
// when you need to remove this
$scope.$emit('removeCustom',$scope.index);
}
});
FYI: A directive can always remove itself by calling $el.remove() in the link function, but since your directive is created via a ngRepeat it will just get recreated in the next digest. So you have to tell the controller to remove it from the instances array.

Pass additional parameter to template using AngularJS

I have the following directive:
app.directive("actTemplate", function() {
return {
restrict: 'A',
templateUrl: "/views/myTemplate.html"
};
});
how can i pass additional parameter to myTemplate so:
<div>
{{aditionalParam}}
...
</div>
takes the value?
Define
app.directive("actTemplate", function() {
return {
restrict: 'A',
templateUrl: "/views/myTemplate.html"
scope: {
foo: '=boo'
}
};
});
Template
<div>
{{foo}}
</div>
Use
<actTemplate boo="lalala" />
You have to specify that your directive should create it's inner scope. Scope variable could then be shared in single or double binding way (see directive scope binding doc)
app.directive("actTemplate", function() {
return {
restrict: 'A',
scope: {
additionalParam: '='
},
template: "<div>{{additionalParam}}</div>"
};
});
Then "call" your directive with this dashed syntaxe:
<div act-template additional-param="foobar">
You can have one-way binding (controller -> directive) like in this jsfiddle example.
Or two way data binding (controller <-> directive) , like in this one.

Categories

Resources