Angularjs how to pass in data using a directive - javascript

What I am trying to do is make a function so I can change the height of my ng-grid column width. That is irrelevant besides the fact that the scope from my controller needs to communicate with the scope in my directive.
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
And I just want to be able to go into my html and say hey for this div I want the height 20
<div getWidth = '20'></div>
I have looking around and I couldn't find anything doing with this exact thing. and by the way, in my QuotesCtrl i initialized the row height like so
$scope.theRowHeight;
Any suggestions?

Try something like this:
.directive('getWidth', function(){
return{
controller: 'QuotesCtrl',
link: function(scope){
console.log(scope.theRowHeight);
},
scope: {
'theRowHeight': '='
}
}
});
Markup:
<div the-row-height="20"></div>

Directives are amazing! You can pass in what is called an isolate scope, and with that you can pass in values as strings or references to your controller scope. There are 3 options on the isolate scope that you should look into. = # & See the link below the example to the docs.
Here is a working JSFiddle
.directive('getHeight', function(){
return{
scope: {
"rowHeight": '='
},
controller: 'QuotesCtrl',
link: function(scope){
scope.smRowHeight = function(the value i want){
scope.theRowHeight = the value i want;
}
}
}
})
You would need to update your html to pass in the new scope value.
<div get-height row-height='20'></div>
More Info on Directives

Related

$compile in Directives

Hey guys i was planning out a directive i was making which would essentially be a popup with a timer on it. Basically the plan was to pass in an object which could configure the properties to construct the message. The directive would contain the html template and we would append the message/html based on the properties set in a service. For Example:
$rootScope.timer = 'recursive time fn goes here'
obj = {
message : '<span ng-click="connect()">Custom message goes here {{ timer }} </span>'
}
Popup.pop(obj);
etc. The point of the question is the $rootScope timer needs to tick down (which is simple to do in a controller) but the directive sets html as a string if interpolated and will not update the value if i'm correct. My question is how do i get the directive to render the timer ticking down inside the directive. would i need to use $compile in the directive? if so how? Furthermore how would i pass an ng-click function from this service if i ever needed one? Sorry if its confusing pls ask questions.
Try this
//you can add your custom messge and time function returning value to the way u want
// this is the basic way to do
var testing = angular.module('testing', [])
testing.directive('mydir', function ($compile, $rootScope) {
var template = '<span ng-click="connect()">custom message</span>'
return {
restrict: 'E',
link: function (scope, ele, attribute) {
scope.connect = function () {
alert('popup' + new Date().getTime());
}
var content = $compile(template)(scope);
ele.append(content)
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<div ng-app="testing">
<mydir></mydir>
</div>
</body>

Nested directives/controllers in angular

Just getting my head around Angular - failing to understand a few concepts as I come from the Backbone school of thought.
I've picked a random project to get started: a card game.
Let's say that I wanted to define a hand controller and a card controller. For simplicity, I want to have them as directives.
Here is the card directive:
app.directive('card', function(){
return {
restrict:'E',
templateUrl:'card.html',
controller:function($scope){
this.suit = 'clubs';
this.rank = 'a';
this.suitClass = function(){
return this.suit + '-' + this.rank;
}
},
controllerAs:'card'
};
});
And here is the hand directive:
app.directive('hand', function(){
return {
restrict:'E',
template:'hand.html',
controller:function($scope){
this.cards = [
{suit:'clubs', rank:'a'},
{suit:'spades', rank:'10'},
{suit:'hearts', rank:'2'},
{suit:'diamonds', rank:'k'}
];
},
controllerAs:'hand'
}
});
With the following plunker, I was expecting to be able to simply drop in the <hand></hand> element and have angular do all the work for me. In my minds eye there should be cards representing different suits nested within the <hand> directive. What am I missing? Currently, as you can tell in the plunker, the nested controller/directive does not instantiate the view properly.
Am I thinking in too much of an MVC way? Is OOP haunting me? Or is angular just badly designed?
I am not 100% sure that I understand your question but I think that this is a better way to write it:
var app = angular.module('app', []);
app.directive('card', function(){
return {
restrict:'E',
templateUrl:'card.html',
replace: true,
link: function ($scope, element, attrs){
$scope.suit = 'clubs';
$scope.rank = 'a';
$scope.suitClass = function(){
return this.suit + '-' + this.rank;
}
}
};
});
app.directive('hand', function($compile){
return {
restrict:'E',
templateUrl:'hand.html',
link:function($scope, element, attrs){
$scope.cards = [
{suit:'clubs', rank:'a'},
{suit:'spades', rank:'10'},
{suit:'hearts', rank:'2'},
{suit:'diamonds', rank:'k'}
];
}
}
});
And the html can be something like these:
(hand directive template)
<div>
<card ng-repeat="card in cards"></card>
</div>
And (card directive template)
<div ng-class="card.suitClass()">
{{ suit }}
</div>
I will explain the problem by going top down through the order of elements/objects that will be called:
hand directive:
The directive is ok so far. But the $compile parameter and the $scope parameter are not used an should be removed. To be more clear I applied this to a variable hand, but it does not change the behaviour of the application.
app.directive('hand', function(){
return {
restrict:'E',
templateUrl:'hand.html',
controller:function() {
var hand = this;
hand.cards = [
{suit:'clubs', rank:'a'},
{suit:'spades', rank:'10'},
{suit:'hearts', rank:'2'},
{suit:'diamonds', rank:'k'}
];
},
controllerAs:'hand'
}
});
hand.html:
You never passed the current card of the ng-repeat to the card directive.
That way you only produce the card templates times the number of card but never using the actual values.
I removed the obsolete div tag and enhanced the hand.html to this:
<card ng-repeat="card in hand.cards" card-model="card"></card>
This way I get every card from the hand view in the card directive.
card directive:
First I remove the $scope variable because it is never used and won't be used here.
This function is rather incomplete. At least it is missing the card values you want to use. But a major problem in here is that the context of this is bound to the caller. To be more precise, you are using this inside of the suitClass function, but you want to use the suit and rank values of the controller. this does not point to the controller function but to the newly created suitClass function which doesn't know any of these values. For that problem you should introduce a variable that holds the context and access the values that way. And I add the scope variable cardModel that is bound to the element attribute to get the desired values. And I add the bindToController: true to access the passed in model as card.cardModel instead of the pure cardModel:
app.directive('card', function(){
return {
restrict:'E',
scope: {
cardModel: '='
},
templateUrl:'card.html',
controller:function(){
var card = this;
console.log(card.cardModel)
card.suitClass = function(){
return card.cardModel.suit + '-' + card.cardModel.rank;
}
},
controllerAs:'card',
bindToController: true
};
});
card.html:
This view is okay. I only applied my changes:
<div ng-class="card.suitClass()">{{ card.cardModel.rank }}</div>
I hope it is still useful for anybody.

Dynamically Create and Load Angular Directive

In my application i have a list of custom directive names.
$scope.data =["app-hello","app-goodby","app-goodafter"];
each name in this array is one directive that im created.
var app = angular.module('app',[]).controller('mainCtrl',function($scope){
$scope.data =["app-hello","app-goodby","app-goodafter"];
}).directive('appHello',function(){
return {
restrict:'EA',
template:'<h1>Hello Directive</h1>'
};
}).directive('appGoodbye',function(){
return {
restrict:'EA',
template:'<h1>GoodBye</h1>'
};
}).directive('appGoodafter',function(){
return{
restrict:'EA',
template:'<h1>Good Afternoon</h1>'
};
});
now i want to load directive with ng-repeat in the view for example because i used EA restrict for directive can create directive in ng-repeat like this :
<div ng-repeat="d in data" >
<div {{d}}></div>
</div>
but this way it doesn't work. so the real question is if i have list of directive how to load this directive with ng-repeat.for this scenario i create a jsbin .
thanks.
You need a "master" directive that $compiles the HTML (optionally containing directives) into an Angular-aware template and then links the compiled element to a $scope:
app.directive('master', function ($compile) {
return {
restrict: 'A',
link: function postLink(scope, elem, attrs) {
attrs.$observe('directive', function (dirName) {
if (dirName) {
var compiledAndLinkedElem =
$compile('<div ' + dirName + '></div>')(scope);
elem.html('').append(compiledAndLinkedElem);
}
});
}
};
});
<div master directive="{{dir}}" ng-repeat="dir in ['dir1', 'dir2', 'dir3']"></div>
See, also, this short demo.
You can do it in this way:
Directive:
app.directive('compile',function($compile){
return{
restrict:'A',
template: '<div></div>',
link:function(scope,elem,attrs){
scope.name = attrs.compile;
elem.children('div').attr(scope.name,'');
$compile(elem.contents())(scope);
}
};
});
HTML:
<div ng-repeat="d in data" compile="{{d}}">
</div>
Jsbin: http://jsbin.com/wofituye/4/edit
I actually prefer to create templates, that just contain the directive. Then you can use ng-include this then enables you to easily pass scope variables into the dynamically chosen directives too.
Here is my widget code fore example:
<div ng-repeat="widget in widgets track by $index" ng-include="widget.url" class="widget-container" ng-class="widget.widget_type.config.height +' ' + widget.widget_type.config.width">
</div>
Then I set the widget.url to a template containing just the right directive.
I can then in my directive do this:
<custom-widget ng-attr-widget="widget"></custom-widget>
Then I have access to the dynamic variable too, so I can access configuration specifics too, without having to dynamically generate HTML strings and compile them. Not a perfect solution, but personally I used to use the other approach mentioned, and discovered that this fit my needs much better.

Data from directive not displaying within ng-repeat

I have broken this problem down into it's simplest form. Basically I have a directive that, for the demo, doesn't yet really do anything. I have a div with the directive as an attribute. The values within the div, which come from an object array, are not displayed. If I remove the directive from the div, they are displayed OK. I am clearly missing something really obvious here as I have done this before without any problems.
Here's the Plunk: http://plnkr.co/edit/ZUXD4qW5hXvB7y9RG6sB?p=preview
Script:
app.controller('MainCtrl', function($scope) {
$scope.tooltips = [{"id":1,"warn":true},{"id":2,"warn":false},{"id":3,"warn":true},{"id":4,"warn":true}];
});
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
}
};
});
HTML
<div ng-repeat="tip in tooltips" class="titlecell" cm-tooltip="true">
A div element: {{ tip.id }}
</div>
<br><br>
Just to prove it works without the directive:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.id }}
</div>
There is a hack to make it working in earlier versions of angular by making use of transclusion, like that:
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
transclude: true,
template : '<div ng-transclude></div>'
};
});
PLNKR
As by Beyers' comment above and below, the behaviour the question is about no longer exists in at least 1.2.5
To be clearer; this has nothing to do with ng-repeat, you can remove it and there still will be no tip ( or tooltips ).
See this question on what the = and other configs mean and what it is doing for you.
Basically for your situation when you use = the scope of the directive will be used in the underlying elements, you no longer have your controller's scope. What this means for you is that there is no {{ tip.id }} or not even tip. Because the directive doesn't supply one.
Here's a plunker that demonstrates what you can do with it.
Basically all i did was
app.directive("cmTooltip", function () {
return {
scope: {
cmTooltip: "="
},
link: function($scope){ // <<
$scope.tip = { id: 1 }; // <<
} // <<
};
});
This creates the tip object on the scope so it has an id.
For your situation you would probably just not use = and look at this question for your other options depending on what you want.
In my opinion this isn't the way to go.
I would use Objects.
JS code:
function tooltip(id,warn){
this.id = id;
this.warn = warn;
}
tooltip.prototype.toString = function toolToString(){
return "I'm a tooltip, my id = "+this.id+" and my warn value = "+this.warn;
}
$scope.tooltips = [new tooltip(1,true),new tooltip(2,false),new tooltip(3,true),new tooltip(4,true)];
HTML:
<div ng-repeat="tip in tooltips" class="titlecell">
A div element: {{ tip.toString() }}
</div>

Angularjs watch for change in parent scope

I'm writing a directive and I need to watch the parent scope for a change. Not sure if I'm doing this the preferred way, but its not working with the following code:
scope.$watch(scope.$parent.data.overlaytype,function() {
console.log("Change Detected...");
})
This it logged on window load, but never again, even when overlaytype is changed.
How can I watch overlaytype for a change?
Edit: here is the entire Directive. Not entirely sure why I'm getting a child scope
/* Center overlays vertically directive */
aw.directive('center',function($window){
return {
restrict : "A",
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
var watchForChange = function() {
return scope.$parent.data.overlaytype;
}
scope.$watch(watchForChange,function() {
$window.setTimeout(function() {
resize();
}, 1);
})
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
If you want to watch a property of a parent scope you can use $watch method from the parent scope.
//intead of $scope.$watch(...)
$scope.$parent.$watch('property', function(value){/* ... */});
EDIT 2016:
The above should work just fine, but it's not really a clean design. Try to use a directive or a component instead and declare its dependencies as bindings. This should lead to better performance and cleaner design.
I would suggest you to use the $broadcast between controller to perform this, which seems to be more the angular way of communication between parent/child controllers
The concept is simple, you watch the value in the parent controller, then, when a modification occurs, you can broadcast it and catch it in the child controller
Here's a fiddle demonstrating it : http://jsfiddle.net/DotDotDot/f733J/
The part in the parent controller looks like that :
$scope.$watch('overlaytype', function(newVal, oldVal){
if(newVal!=oldVal)
$scope.$broadcast('overlaychange',{"val":newVal})
});
and in the child controller :
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//any other action can be perfomed here
});
Good point with this solution, if you want to watch the modification in another child controller, you can just catch the same event
Have fun
Edit : I didn't see you last edit, but my solution works also for the directive, I updated the previous fiddle ( http://jsfiddle.net/DotDotDot/f733J/1/ )
I modified your directive to force it to create a child scope and create a controller :
directive('center',function($window){
return {
restrict : "A",
scope:true,
controller:function($scope){
$scope.overlayChanged={"isChanged":"No","value":""};
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//whatever you need to do
});
},
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
You should have the data property on your child scope, scopes use prototypal inheritance between parent and child scopes.
Also, the first argument the $watch method expects is an expression or a function to evaluate and not a value from a variable., So you should send that instead.
If you're looking for watching a parent scope variable inside a child scope, you can add true as second argument on your $watch. This will trigger your watch every time your object is modified
$scope.$watch("searchContext", function (ctx) {
...
}, true);
Alright that took me a while here's my two cents, I do like the event option too though:
Updated fiddle
http://jsfiddle.net/enU5S/1/
The HTML
<div ng-app="myApp" ng-controller="MyCtrl">
<input type="text" model="model.someProperty"/>
<div awesome-sauce some-data="model.someProperty"></div>
</div>
The JS
angular.module("myApp", []).directive('awesomeSauce',function($window){
return {
restrict : "A",
template: "<div>Ch-ch-ch-changes: {{count}} {{someData}}</div>",
scope: {someData:"="},
link : function(scope,elem,attrs){
scope.count=0;
scope.$watch("someData",function() {
scope.count++;
})
}
};
}).controller("MyCtrl", function($scope){
$scope.model = {someProperty: "something here");
});
What I'm showing here is you can have a variable that has two way binding from the child and the parent but doesn't require that the child reach up to it's parent to get a property. The tendency to reach up for things can get crazy if you add a new parent above the directive.
If you type in the box it will update the model on the controller, this in turn is bound to the property on the directive so it will update in the directive. Within the directives link function it has a watch setup so anytime the scope variable changes it increments a counter.
See more on isolate scope and the differences between using = # or & here: http://www.egghead.io/

Categories

Resources