angularjs: setting a scope variable from directive - javascript

First what I want:
I have a series of thumbnails. When I click on one, I want that specific thumbnail to be shown in a bigger div, with its description. (For later: should be animated).
Now I have the directive and the controller, but I don't know how to set the appropriate variable!
So some code:
First HTML: here is the root .jade file for this section. I have a directive called product.
section
.detail-view(ng-show="vm.showDetail")
.prod-desc(ng-bind="vm.detailPic")
.prod-img(ng-bind="vm.detailDesc")
product.col-xs-12.product_tile(ng-repeat="item in vm.products", item="::item")
As you can see, the product directive is part of an ng-repeat; for this reason, the div I want to show the resized image is outside the iteration (.detail-view).
The product directive:
'use strict';
ProductDirective.$inject = ['ShopCart', '$animate', 'User'];
function ProductDirective(ShopCart, $animate, User) {
return {
restrict: 'E',
scope: {
item: '='
},
template: require('./product.html'),
link: link
};
function link(scope, element) {
scope.toggleDetails = toggleDetails;
}
function toggleDetails() {
if (!scope.isSelected && isBlurred()) return;
scope.vm.detailPic = scope.item.photo;
scope.vm.detailDesc = scope.item.prod_description;
scope.vm.isSelected = !scope.isSelected;
scope.showDetail = !scope.showDetail;
var action = scope.isSelected ?
}
}
Now the div I want to update with the image in big is outside the iteration - and hence outside the scope of the directive. How can I set the value of showDetail, showDesc and showPic?
As I am using controllerAs with value vm, I thought I could just do scope.vm.detailPic = scope.item.photo;, as in other solutions I have seen that when setting a property on a root object, it would be propagated...but I get
Cannot set property 'detailPic' of undefined

For now, what works (but looks a bit odd to me) is this, in toggleDetails()
scope.$parent.$parent.vm.detailPic = scope.item.photo;
scope.$parent.$parent.vm.detailDesc = scope.item.prod_description;
scope.$parent.$parent.vm.showDetail = !scope.$parent.$parent.vm.showDetail

Related

how to avoid calling `$parent` scope in directive

app.directive('hidefileId',['$document','documentService',function($document,documentService){
return{
scope: false,
restrict: 'AE',
link : function($scope,element,attrs){
element.on('click',function(e){
angular.element('#fileId').removeClass("errorhilight");
angular.element('#docerrormsg').html('');
})
}
}
}]);
I have many directives in my js controller, when I use the following directive I am getting the parent scope in
$scope.$parent.$parent.$parent.$parent.// here I am getting the scope of my controller.
even I use scope: false, it is creating new scope.
I want to use the my controller scope only
You can use the require directive option. This will give you access to a parent controller(s). The directive with throw an exception if the controller(s) can not be found. You will have access to anything you put on that controller. This is a great method to use if you need a parent child relationship where a child directive needs access to the parent. It also allows for more modular code.
e.g.
I have written a table directive with an exposed API to add formatting to cells. This is very useful as you can write many additional table directives that can be used interchangeably throughout an application. As an example I will show you a child HoverTitle directive that uses the parent table directive to add a hover tooltip in a given cell.
function HoverTitle(CellProcessService){
'ngInject';
return{
require: '^cTable', // require c-table
link: function(scope, element, attrs, cTable){
cTable.addCellRenderProcess(renderCell, CellProcessService.priorities["HOVER-TITLE"]);
function renderCell(curr, column, row){
if(angular.isFunction(cTable.hoverClass)){
var hoverTitle = cTable.hoverClass(row, curr, column);
if(hoverTitle){
var placement = cTable.placement || 'right';
var tag = column.link ? "span" : 'a'
return '<' + tag + ' tooltip-placement="'+placement+'" uib-tooltip="' + hoverTitle + '">' + curr +'</ ' + tag + '>';
}
}
return curr;
}
}
}
}
Here is how it could be used:
<c-table
table-sort
hover-title
data="$ctrl.data"
columns="$ctrl.columns">
</c-table>
https://docs.angularjs.org/guide/directive#creating-directives-that-communicate

angular directive: switch between two templates dynamically

I am trying to create a directive named availableTo that can switch between two different templates depending on some message. For example, if the field is an input with the ng-model directive, I would first need to change it to read-only using the <span> tag. So far, my code can switch the view to read-only, but I cannot seem to switch it back to input:
var directive = {
restrict: 'A',
require: '?ngModel',
link: linkerFn,
replace: true
};
function linkerFn(scope, element, attrs, ngModelCtrl) {
var clonedElement = angular.copy(element);
var preOuterHTML = clonedElement[0].outerHTML; //this can save the <input> field html code
scope.$on('mode_changed', function() {
var curUserRole = userservices.getUserRole();
if (attrs.availableTo == curUserRole) {
var e = $compile(preOuterHTML)(scope);
element.replaceWith(e);
} else {
var template = '<span>' + ngModelCtrl.$viewValue + '</span>';
var e = $compile(template)(scope);
element.replaceWith(e);
}
}); //scope.$on
} //linkerFn
For an input field:
<input name="test1" class="form-control" ng-model="name" placeholder="Name 1" available-to="ADMIN"/>
I also noticed that once I change the template in the else block above, the element re-renders, and the preOuterHTML does not contain the original element html any more. This seems to be mission impossible to me, but I would like to hear some expert opinions. Thanks
element.replaceWith(e); Don't do that. In Angular, if you find yourself attempting to modify the DOM directly, you are by definition doing it wrong. You gotta sit back and let Angular do the work.
If you need to replace a directive's entire template, a fairly straightforward approach is to use ng-include with a scope variable containing the desired conditional templateUrl, e.g.
var directive = {
// ...
template: '<div ng-include="myTemplateUrl"></div>',
link: function(scope, el) {
if (/* whatever */) {
scope.myTemplateUrl="templates/foo.html";
} else {
//...etc
}
},
};
(This does add an extra DOM node to the tree, but that's generally harmless.)
It sounds like in your case you may not need to go that far, though; a simple ng-if inside your template is probably enough to swap between your read-only <span> and <input>.

angularJS how to change attrs in directive's link

I set a progress in my app
I want to controll The progress in angular's directive
but how can I change data-value and data-total in directive's link func?
app.html
<div class="ui indicating small progress" data-value="39" data-total="50" plan-progress>
<div class="bar">
<div class="progress"></div>
</div>
</div>
In this html, I want change data-value and data-total
I try this:
app.js
todoApp.directive('planProgress', function() {
return {
link: function(scope, elem, attrs) {
attrs.value = 10
attrs.total = 20
elem.progress();
}
};
});
But it doesn't work
so I want to know how to change it in my directive?
Use attrs.$set() in your link function and recompile the element. Also, don't forget to inject the $compile service to your directive.
In your html you've added the directive as an attribute but didn't mention it in the restrict value in your directive definition. You need to mention it in directive definition.
See the code bellow:
todoApp.directive('planProgress', function($compile) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
attrs.$set('value', 10);
attrs.$set('total', 20);
$compile(elem)(scope);
}
};
});
Simply use :
attrs["data-value"] = 10;
attrs["data-total"] = 20;
You don't want to use attrs.data-total = 20 because the - will force a subtraction.
It's always legal in javascript to use x[keyName] instead of x.keyName, and you must use this second notation when keyName is a strange key such as **$^ùjsls* or data-value. A more useful case is when the key is a variable.
On last thing : as you do, you will always rewrite the coder's inputs. It may have sense, but it's not very elegant.

How to repurpose my directive here to work in 2 different scopes?

http://plnkr.co/edit/39FGMocKB5GtQWnI1TFw?p=preview
I have sidebar which contains a list of tags, when you click on a tag I use the TagDetailsFactory to send a tag into the scope of the view controller.
Everything works great except for when you hover over a tag in the TagDetailsFactory scope.
The tagDetails template does not show up, however if you hover over the same tag in the sidebar scope, the tagDetails shows up in both. This is wrong.
Hovering over a tag in the sidebar should only show the tag details for that tag and the same for the tags inside the view scope.
Hovering over a tag in the view scope, doesn't display it's details
Hovering over a tag inside of the sidebar scope, should only show details for it's tag, and not the tag in the view scope, like it does here:
Steps:
- The first tags Array is in the cnt controller
- When you click on a tag, it gets stored in the TagDetailsFactory service
- I then broadcast an event to the view controller to then call the getTagDetails function in TagDetailsFactory to retrieve the saved tags and store them into the viewTags array in the view controller.
// Code goes here
angular.module('app', [])
.directive('tagDetails', function() {
return {
restrict: "E",
link: function($scope, el, attrs) {
// console.debug($scope, attrs);
},
scope:{
tag:'='
},
template: '<div ng-show="tag.showDetails">{{tag.details}}</div>'
};
})
.factory('TagDetailsFactory', function() {
var savedTags = [];
var saveTagDetails = function(tag) {
savedTags.push(tag);
}
var getTagDetails = function() {
return savedTags;
}
return {
saveTagDetails : saveTagDetails,
getTagDetails : getTagDetails
};
})
.controller('sidebar', function($scope,
$rootScope,
TagDetailsFactory) {
$scope.tags = [];
for(var i = 0; i < 10; i++) {
$scope.tags.push(
{ name: 'Foo Bar ' + i, details: 'Details' + i }
);
}
$scope.showTagDetails = function(t) {
t.showDetails = true;
}
$scope.leaveTag = function(t) {
t.showDetails = false;
}
$scope.sendTag = function(t) {
TagDetailsFactory.saveTagDetails(t);
$rootScope.$broadcast('updateView');
}
})
.controller('view', function($scope,
$rootScope,
TagDetailsFactory) {
$scope.viewTags = [];
$scope.$on('updateView', function() {
$scope.viewTags = TagDetailsFactory.getTagDetails();
});
$scope.showTagDetails = function(v) {
v.showDetails = true;
}
$scope.leaveTag = function(v) {
v.showDetails = false;
}
});
Do I have to create a 2nd directive here? To be the template for the tag details in the view scope? Or can my current tagDetails directive be repurposed somehow in an Angular way?
I forked your Plunker with a working copy, and I'll explain the changes I made.
You have two issues with the code here. The first is a simple typo, which is causing your header to not reference the correct function for mouseover. Your functions are calling showTagDetailsView(v)and leaveTagView(v), but they are named showTagDetails and leaveTag on the controller.
The second issue is with the way that the items are added to the savedTags[]. In JavaScript, objects are passed by reference. when you call savedTags.push(tag);, you are pushing a reference to the same object into the new array. Any changes made to the object in one array will be reflected in the other array.
Instead, what you want is a separate copy of the object in the savedTags[]. This can be accomplished by using angular.copy. Note that I also reset tag.showDetails = false; before making the copy, else the new copy will have it set to true, and the details will be showing the instant the copy appears, even though you are hovering over the other element when you click it.
var saveTagDetails = function(tag) {
tag.showDetails = false;
savedTags.push(angular.copy(tag));
}
Just a side note, you might also have an issue with CSS here, as hovering seems to change the position of the lists, and in some cases the hover actually causes the tag to move itself out of the hover, causing a bounce effect.

Passing variable from directive to controller in AngularJS

So I have an nvd3 graph in my index.html whose height is set to {{varyingHeight}} like so (code snippet):
<nvd3-line-plus-bar-chart data="data"
showXAxis="true" showYAxis="true"
tooltips="true" interactive="true"
showLegend="true"
height='{{varyingHeight}}'
>
</nvd3-line-plus-bar-chart>
Now in my directive, I have a code which identifies when the height change takes place, and what the new height is:
app.directive('test', function () {
return {
restrict: 'EA',
scope: {},
link: function (scope, element) {
scope.$on('split.resize', function() {
console.log('I got resized');
console.log(element.height());
});
}
};
});
In my controller, I now want to be able to set the new height like so:
$scope.$apply(function(){
$scope.varyingHeight = h;
})
I'm new to angularjs so I can't figure what the best way is to do this. I have seen answers which show how to do this the other way round, i.e from the controller to the directive but this hasn't helped me. How do I pass a element.height() to variable h from the directive to the controller? Or is my code structured wrong in the first place?
You do it by binding the height attribute to the value passed from the controllers scope. Here's an example: http://jsbin.com/vapipizu/1/edit
The important part is that you replace height="{{varyingHeight}}" with height="varyingHeight" and that your directive binds the height attribute like this:
scope: {
height: '='
}
I also faced this issue, I resolved it by passing the height as attribute to the directive
My sample directive
App.directive('aBc',function () {
return {
restrict : 'AE',
scope : {
gridHeight : '#'
},
template : '<div style= "height : {{gridHeight}}px" >'
+'<p>sdtyhdrtydrt-- {{gridHeight}} </p>'
+ '</div>'
};
});
pass the height through directive tag
directly you can pass the height
<div a-bc grid-height="200"></div>
<div a-bc grid-height="500"></div>
<div a-bc grid-height="1000"></div>
or you can set it from your controller
<div a-bc grid-height="someHeight"></div>
initialize someHeight in controller like
$scope.someHeight = 500;

Categories