What I am doing: I am creating two attribute directives: One moves an element to the left, and another centers an element on the page. See code below. Notice that I'm manipulating ng-style to set those css properties (moving the element to the left, and centering on the page).
The Problem:
When I use both directives on an element, the second directive's scope.style={} obviously overwrites the first one.
This means that I'm not able to apply both ng-style/attributes at the same time.
My question:
What is an easy way to instantiate the scope.style object so that I can use it in both directives without recreating the object?
I'm trying to find a simple solution that can be scaled easily for multiple element directives without writing tons of messy code. (I don't want to create a special controller inside every directive to accommodate sharing the scope.style object).
Please advise. Thanks a lot!
Here is the html:
The data is from a json file, but that's not important here.
<!-- The Element: -->
<started-box center-page keepleft screen="screen[3]"></started-box>
<!-- The Template: -->
<div id="{{screen.id}}" ng-style="$parent.style">
Some content goes here.
</div>
Here are the AngularJS code snippets:
// Two Attribute directives:
app.directive("centerPage", function () {
return function (scope, element, attrs) {
var winW = window.innerWidth;
var winH = window.innerHeight;
var boxW = 370;
var boxH = 385;
scope.style = {};
scope.style.left = (winW / 2) - (boxW / 2);
scope.style.top = (winH / 2) - (boxH / 2);
}
});
app.directive("keepleft", function () {
return function (scope, element, attrs) {
scope.style = {};
scope.style.cursor = 'default';
scope.style.transform = 'translateX(-60%)';
}
});
// Directive for the template:
app.directive("startedBox", [function () {
return {
restrict: "E",
replace: true,
scope: {
screen: '='
},
templateUrl: dir + 'templates/directives/started-box.html'
};
}]);
Create a style service and inject it into both directives. Services are excellent for sharing states between directives/controllers.
Related
I have used bootstrap to make the website it was responsive until I added below scope to the directive to make attribute for the defined pictureFrame element:
bootApp.directive('pictureFrame', function(){
return{
restrict:"E",
templateUrl:"src/templates/directives/pictureFrame.html",
scope:{
picture:"#picture",
border:'#border'
}
};
});
and this is how I used in the html page :
<picture-frame picture="src/img/screen.jpg" border="yellow" >
</picture-frame>
After using these codes my website is not responsive anymore.
What could be the reason?
I guess you would like to have a bidirectional binding to your scope attributes. But you got a one way binding. If this is what you mean by no "more responsive" than just change your scope to:
scope:{
picture:"=picture",
border:'=border'
}
Do you mean like this Plunk?
Your directive would become this (using the code of the plunk):
app.directive('pictureFrame', function(){
return{
restrict:"E",
templateUrl:"pictureFrame.html",
scope:{
picture:"=",
imageClass:"="
},
};
});
The core elements of the controller:
$scope.index = 1;
var pictures = [
"http://us.123rf.com/450wm/bagotaj/bagotaj1205/bagotaj120500016/13546752-coffee-cup-concept.jpg?ver=6",
"http://us.123rf.com/450wm/peshkova/peshkova1208/peshkova120800337/14768439-young-man-and-business-tags.jpg?ver=6",
"http://us.123rf.com/450wm/dimro/dimro1205/dimro120500005/13554732-coffee-cup-stylish-shape.jpg?ver=6"
];
var classes = ["yellow", "red", "blue"];
$scope.SetPicture = function() {
$scope.picture = pictures[$scope.index - 1];
};
$scope.SetClass = function() {
$scope.imageClass = classes[$scope.index - 1];
};
And you would set it like so:
<picture-frame picture="picture" image-class="imageClass"></picture-frame>
I've used image-class instead of border for ease of use.
PS: I see no use in providing a full identifier ("=picture") if the name is the same.
PPS: Obviously the Plunk is an idealised version that would need real-world adaptation.
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.
I'm new to AngularJS and already on the brink of despair :) My app looks like this (Not-working example on Plnkr):
HTML
<div ng-app="myApp" ng-controller='controllers.content' >
<h2 id="fixed">{{ header }}</h2>
<ul>
<li ng-repeat="item in db" scroll-stop="item">{{ item.name }}</li>
</ul>
</div>
JS
angular.module('myApp.controllers',[]);
var app = angular.module('myApp', [
'myApp.controllers'
]);
var app = angular.module('myApp.controllers').controller('controllers.content', ['$scope', function($scope){
$scope.db = [
{ name: "20 February 2014", header: "Today" },
// ...
];
$scope.header = "Today";
}]);
Now basically I want a scroll spy that changes the contents of the h2 (assume it's fixed to the top) to the header property of the item in db whenever that item is currently at the top of the viewport:
app.directive('scrollStop', function() {
return {
restrict: 'A',
scope: {
scrollStop:'='
},
link: function (scope, element, attrs) {
var $page = angular.element(window)
var $el = element[0]
var last_top = $el.getBoundingClientRect().top - 60;
$page.bind('scroll', function () {
var current_top = $el.getBoundingClientRect().top - 60;
if (last_top >= 0 && current_top < 0) {
// HERE!
console.log(scope.scrollStop.header);
scope.header = scope.scrollStop.header;
}
last_top = current_top;
})
}
}
});
However, I can't figure out how to talk to the content controller from within the directive to edit $scope.header from within the directive. I understand that the scope is a different to the controller's $scope, and have a vague feeling that I should use require somewhere, but can't figure out how to get it right. Partly also because there seem to be 5 different shorthands for doing anything in AngularJS and they don't always go well together. Any help appreciated, including stylistic guidance.
You are using isolated scope for you directive. So you canot access to the scope controller. You could comunicate with the controller using a service or using events.
If you want to be able to access to the controller scope directly from your directive, you have to use shared scope.
I hope that it helps. You can find more information about scopes in directives Here.
-----------EDIT----------------
I am not sure what you want to do, but in the next code you can see as using share scope, you can acces the data from de parent and making the view refresh with the new data.
angular.module('app').directive('scrollStop', function() {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
var $page = angular.element(window)
var $el = element[0]
var last_top = $el.getBoundingClientRect().top - 60;
$page.bind('scroll', function () {
var current_top = $el.getBoundingClientRect().top - 60;
if (last_top >= 0 && current_top < 0) {
// HERE!
scope.$parent.header=scope[attrs.scrollStop].header;
scope.$parent.$apply();
}
last_top = current_top;
})
}
}
});
I created a directive for my AngularJS app which checks the $scope to see if a profile image is present. If it is the directive shows the image by appending a new DIV, if not it shows a default via a css class. The directive looks like so... and it works in the app.
.directive('profileImage',function() {
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="profileImage" class="profileImage">');
}
}
};
})
;
now I wish to write a unit test for this (I know I should have done that first but I am just learning about tests). I have written the following:
it('should display a specific image', inject(function($rootScope, $compile) {
//$rootScope.profileImage = "srcOfImage";
$scope = $rootScope;
$scope.profileImage = "srcOfImage";
var element = angular.element('<div profile-image="profileImage"></div>')
element = $compile(element)($scope)
console.log(element);
var imgElem = element.find('img');
expect(imgElem.length).toBe(1);
expect(imgElem.eq(0).attr('src')).toBe('srcOfImage');
}));
Now the test fails in this scenario (other tests where no $scope.profileImage is available has passed). When I out put the element I have created I get the following:
LOG: {0: <div profile-image="profileImage" class="ng-scope ng-isolate-scope"><img ng-src="profileImage" class="profileImage"></div>, length: 1}
So everything works but the profile-image="profileImage" in the var element = angular.element('<div profile-image="profileImage"></div>') is being taken as a literal string. It is not showing the value 'srcOfImage'. What am I doing wrong?
The error looks like so:
PhantomJS 1.9.2 (Mac OS X) Profile Image Directive should display a specific image FAILED
-src="prExpected undefined to be 'srcOfImage'.>, length: 1}
Big thanks for any advice / help / explanations
This is expected behavior because:
element.append('<img ng-src="profileImage" class="profileImage">');
appends a normal DOM image without compilation, so the properties are added as they are.
Try:
element.append('<img ng-src="profileImage" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
Remember to declare the $compile service in your directive:
.directive('profileImage',function($compile) {
One more problem is you have to use ng-src with {{}} like this:
ng-src="{{profileImage}}"
Your final directive looks like this:
.directive('profileImage',function($compile) { //declare $compile service.
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="{{profileImage}}" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
}
}
};
})
;
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