AngularJS - Adding shortcut to object in the scope - javascript

In my highest level scope, I fetch some data from the server for use throughout the page like this: $scope.lotsOfData = $http.get("lotsOfData");. So now my scope holds a promise object for lotsOfData. Then, in my HTML, I have directives that are only concerned with a small set of the lotsOfData object. One such directive could look something like this:
<div>
{{lotsOfData.foo.blah[source].bar[id].someData}}<br>
{{lotsOfData.foo.blah[source].bar[id].otherData}}<br>
{{lotsOfData.foo.blah[source].bar[id].differentData}}
</div>
where source and id are being set through attributes on the directive. My HTML page then looks something like this:
<data-subset source="1" id="1" />
<data-subset source="1" id="2" />
<data-subset source="2" id="1" />
<data-subset source="3" id="1" />
I hate having to repeat lotsOfData.foo.blah[source].bar[id] throughout the directive. Is there any way to set in the scope so my directive could look more like this?
<div>
{{currObj.someData}}<br>
{{currObj.otherData}}<br>
{{currObj.differentData}}
</div>
Not only does this clean up the HTML, but if we ever restructure that lotsOfData object, there's be only one place to change how it's getting the currObj object. In the link function for my directive I tried this:
link: function(scope, element, attrs) {
scope.currObj = scope.lotsOfData.foo.blah[attrs.source].bar[attrs.id];
}
However, since lotsOfData is a promise object, it doesn't have a property called foo. I don't know a whole lot about how the promise object works, so maybe I just need to know how I can get to the properties I need.
I hope what I'm trying to accomplish here makes sense and someone could point me in the right direction as to how to make this work. Thanks.

link: function(scope, element, attrs) {
scope.$watch('lotsOfData.foo.blah['+attrs.source+'].bar['+attrs.id+']', function(newVal, oldVal) {
scope.currObj = newVal;
}
}
This should fix the problem, you will simply watch for changes on your data and set the currObj with the new value.

This is how I accomplished what I wanted to. I created an outer directive to the one mentioned above and then created an isolated scope for the inner directive that bound to the data I wanted.
So my outer directive HTML looks like this:
<div>
<data-subset curr-obj="lotsOfData.foo.blah[source].bar[id]"></data-subset>
</div>
And the javascript looks like this:
myModule.directive('outer-data', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'outerData.html',
link: function(scope, element, attrs) {
scope.id = attrs.id;
scope.source = attrs.source;
},
scope: true
}
});
Then the javscript for my dataSubset looks like this:
myModule.directive('dataSubset', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'dataSubset.htm',
scope: {
currObj: '='
}
}
});
So then in my top-level HTML file I have something like this:
<outer-data source="1" id="1" />
<outer-data source="1" id="2" />
<outer-data source="2" id="1" />
<outer-data source="3" id="1" />
And I can just reference currObj on my dataSubset like I wanted.

Related

How to loop over directives, compiling, and attaching to DOM?

I have a number of directives that I would like to compile and attach to the DOM. For example:
mod.controller("ctrl, ["$scope", "$compile", function($scope, $compile) {
$scope.tools = [
{
title: "foo",
directive: $compile("<foo-bar></foo-bar>")($scope)
},
{
title: "qux",
directive: $compile("<qux-bar></qux-bar>")($scope)
}
...
];
Then in HTML:
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
{{tool.directive}}
</div>
</div>
I would like each directive to be compiled and injected into the DOM. But nothing happens. I expect because I am calling $compile too late. Is there a better way to do this?
FWIW, if I compile the directive and "manually" append it to the DOM, it works:
$('body').append($compile('<foo-bar></foo-bar>')($scope));
You cannot do it this way; the {{...}} bindings do not accept elements. They can be made to accept HTML, but this HTML is static - uncompiled.
If you want dynamic directives, you have to do it yourself. One option is with an auxiliary directive, e.g. the container-directive below:
<div class="tool" container-directive>
<h3>{{tool.title}}</h3>
<placeholder style="display: none"></placeholder>
</div>
It takes the tool from its context, $compiles it, and replaces the dummy placeholder element. Suppose the tools are defined as:
this.tools = [
{ title: 'foo', directive: 'foo-bar' },
{ title: 'qux', directive: 'qux-bar' }
];
Then a very simple implementation would be:
app.directive('containerDirective', function($compile) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.find('placeholder')
.replaceWith($compile('<' + scope.tool.directive + '></' + scope.tool.directive + '>')(scope));
}
};
});
See a fiddle: http://jsfiddle.net/kxj60cbo/
This code demonstrates the general idea. It definitely will need some adjustment to fit your needs. E.g. the directive is tightly coupled with the name of the iteration variable - tool - maybe using isolated scope would be better.
I wouldn't inject I would just qualify what to show
<div ng-repeat="tool in tools">
<div class="tool">
<h3>{{tool.title}}</h3>
<foo-bar ng-if="tool.title = 'foo'"></foo-bar>
<qux-bar ng-if="tool.title = 'qux'"></qux-bar>
</div>
</div>

Pass data to transcluded element

I want to create a directive that organizes a displays data grouped by date. I also want to be able to specify a directive that will display the individual rows. In a perfect world it would look something like this (but nice and pretty)
Friday, Oct 28
[some directive html]
[some directive html]
[some directive html]
Saturday, Oct 29
[some directive html]
Sunday, Oct 30
[some directive html]
[some directive html]
...
This obviously doesn't work, so if you have a better approach please tell me, but I was hoping to be able to do something along these lines:
app.directive('dateOrganized', [function(){
return {
template:
'<div>' +
'<div ng-repeat="organizedDate in organizedDate">' +
'<div>{{organizedDate.date | date}}</div>' +
'<div ng-repeat="item in organizedDate.items">' +
'{{rowDirectiveHtml}}' +
'</div>' +
'</div>' +
'</div>',
scope: {
organizedDates: '=',
rowDirectiveHtml: '='
}
...
};
}])
app.directive('itemRow', [function(){
return {
template: '<div>{{item.data}}</div>',
scope: {
item: '='
}
};
}]);
then use it like this:
<div data-organized organized-dates="stuff" row-directive-html="<div item-row item=\"item\" />" />
I know this is super ugly (and doesn't work, but I'm sure I could get it working with a few tweaks) so what I am really asking, is there a better way to do this?
This question is more complicated than might appear, so let's break it down.
What you are building is a directive that accepts a partial template - <div item-row item="item" /> - and that template uses (or linked against a scope with) an inner variable - item - that is not defined in the outer scope by the user; its meaning is defined by your directive and your user "discovers" it by reading the documentation of your directive. I typically name such "magic" variables with a prefixed $, e.g. $item.
Step 1
Instead of passing a template as an HTML-as-string via attribute binding, pass it as contents and transclude that content. Transcluding allows you to bind the transcluded content against an arbitrary scope:
<foo>
<div>my item is: {{$item}}</div>
</foo>
.directive("foo", function(){
return {
scope: {},
transclude: true,
template: "<h1>I am foo</h1><placeholder></placeholder>",
link: function(scope, element, attrs, ctrls, transclude){
scope.$item = "magic variable";
transclude(scope, function(clonedContent){
element.find("placeholder").replaceWith(clonedContent);
});
}
};
});
The above will place the template <div>my item is: {{$item}}</div> (could be any template you specify) where the directive foo decides, and will link against a scope that has $item defined.
Step 2
But the added complexity of your directive is that it uses ng-repeat, which by itself accepts a template, and the template your directive receives needs to be used as a template of ng-repeat.
With just the approach above, this would not work, since by the time link runs, ng-repeat will have already transcluded its own content before you had a chance to apply yours.
One way to address that is to manually $compile the template of foo instead of using the template property. Prior to compiling, we will have a chance to place the intended template where needed:
.directive("foo", function($compile){
return {
scope: {},
transclude: true,
link: function(scope, element, attrs, ctrls, transclude){
scope.items = [1, 2, 3, 4];
var template = '<h1>I am foo</h1>\
<div ng-repeat="$item in items">\
<placeholder></placeholder>\
</div>';
var templateEl = angular.element(template);
transclude(scope, function(clonedContent){
templateEl.find("placeholder").replaceWith(clonedContent);
$compile(templateEl)(scope, function(clonedTemplate){
element.append(clonedTemplate);
});
});
}
};
});
Demo

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.

Angularjs how to pass in data using a directive

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

how to write custom 'row' and 'col' element for angularjs

I'm trying to write a little dsl around the grid elements outlined here: http://foundation.zurb.com/docs/grid.php
basically what I wish to do is to
<row>
<column two mobile-one>{{myText}}</col>
<column four centered mobile-three><input type="text" ng-model="myText"></input></col>
</row>
transform into:
<div class="row">
<div class="columns two mobile-one">{{myText}}</div>
<div class= "columns four centered mobile-three"><input type="text" ng-model="myText"></input></div>
</div>
Ideally, I wish to write something that can take arbitrary nesting: row -> col -> row -> col -> row.....
I am having trouble getting the first step right - nesting the elements because I can't quite figure how how to get the child elements into another template without seriously compromising the compilation process.
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = tElement.children();
tElement.replaceWith(
$('', {class: 'row',}).append(content));
}
}
});
just does not do anything. The failed attempt is shown here - http://jsfiddle.net/ZVuRQ/
Please help!
I was hoping not to use ng-transclude because I found that it added an additional scope.
Here is a directive that does not use ng-transclude:
app.directive('row', function() {
return {
restrict: 'E',
compile: function(tElement, attrs) {
var content = angular.element('<div class="row"></div>')
content.append(tElement.children());
tElement.replaceWith(content);
}
}
});
You may want to use tElement.contents() instead of tElement.children().
You don't need jquery at all in your example (but you need to include it on your page/jsFiddle):
var app = angular.module('lapis', []);
app.directive('row', function(){
return {
restrict: 'E',
template: '<div class="row" ng-transclude></div>',
transclude: true,
replace: true
};
});

Categories

Resources