AngularJS Directive template not rendering - javascript

I have a simple directive that just displays some text for now:
app.directive("exampleText", function() {
return {
restrict: "E",
template: '<div>hello!</div>'
}
});
In my index.html I have this:
<div class="container" ng-app="customerPortalApp">
<div ui-view></div>
<exampleText></exampleText>
</div>
exampleText is outside my ui-view as thats to do with my routes and works correctly. But its my understanding the directive template should render as is. Have I missed something?

With a directive named:
app.directive("exampleText", ...
HTML should be:
<example-text></example-text>
From documentation:
Angular normalizes an element's tag and attribute name to determine
which elements match which directives. We typically refer to
directives by their case-sensitive camelCase normalized name (e.g.
ngModel). However, since HTML is case-insensitive, we refer to
directives in the DOM by lower-case forms, typically using
dash-delimited attributes on DOM elements (e.g. ng-model).

As tasseKATT noted the directive name should stay as "exampleText"
and the html element should be <example-text>
I thought a demo may help
demo
the template:
<div ng-app="myApp">
<sample-text></sample-text>
</div>
the directive:
var app = angular.module('myApp', []);
app.directive('sampleText', function () {
return {
restrict: "E",
template: '<div>Some sample text here.</div>'
};
});

Related

AngularJS Directive Isolated scope inside nested ng-transclude

I've been looking all over the internet for something like this and I still can't find the answer.
I have a directive that is reused throughout my application, it allows the user to sort and search through lists of items. I have multiple kinds of items that can be passed in to the directive (html templates that I pass in) as well as multiple uses for those templates. I.e, sometimes I want a certain button on that template, but sometimes I want another. (This will make more sense in a minute).
Therefore I have created multiple directives with transclusion in order to achieve this. However, I'm having serious issues with scoping and I can't seem to figure out exactly how to pass the isolated scope to the child directive.
Below is my code:
Item List Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function (scope, element, attributes, ctrl, transclude) {
//Things that modify the scope here. This scope is what I want to pass down to the child directives
//NOTE: I do not currently have a working transclude function which is why I didn't include it here because I have no idea what to do with it
scope.pagedItems = groupItemsToPages(items);
scope.currentPage = 0;
};
return {
restrict: 'E',
replace: 'true',
transclude: true,
templateUrl: 'partials/directives/item-list.html',
link: linkFunction,
scope: {
searchPlaceholder: "#",
items: "=",
control: "="
}
};
});
item-list.html
<div class="form-group">
<!-- I won't put all of the html here, just some to show you what i'm going for -->
<div class="search-field">
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}/>
</div>
<table class="table table-hover">
<tbody>
<tr ng-repeat="item in pagedItems[currentPage]">
<td ng-transclude></td>
</tr>
</tbody>
</table>
</div>
Here's the directive that simply returns the URL of whatever template is passed to it. This is so that I can add in an extra html through further nested transclusions.
item-template.js
var app = angular.module('main');
app.directive('itemTemplate', function() {
return {
restrict: 'AE',
replace: 'true',
transclude: true,
templateUrl: function(tElement, tAttrs){
return tAttrs.templateUrl;
}
};
});
Here's an example template (extremely simplified again, just to show you the layout)
profile-template.html
<div>
<p>item.name</p>
<p>item.description</p>
</div>
<div ng-transclude></div>
Here's an example of the HTML that calls this code
tab.html
<div class="tab">
<div class="available-items">
<item-list control="profileControl" search-placeholder="Search Profiles" items="profileControl.profiles">
<item-template template-url="partials/profile-template.html">
<button type="button" ng-click="launchProfile(item.id)">Launch Profile</button>
</item-template>
</item-list>
</div>
</div>
So now that you've seen the code. The issue I'm having is that my profile-template.html inclusion isn't getting the scope from the directive above it even though I've tried cloning the scope to it. All the examples I've seen require you to remove the template key from the directive and assume you're only returning the code you have in your transclusion. In my case, I have other html that I want to display in the template.
Any help would be appreciated
Instead of trying to pass the scope between your directives, you can make use of the $parent attribute and get access to higher scopes.
For instance, in your item-template controller you could gain access to the higher scope from item-list with a code like this:
if ($parent.searchPlaceholder === '') {
...
}

I am getting error Error: [$compile:tplrt] while using directive in angularjs

In angularJS 1.3.14
var app = angular.module('myApp',[]);
app.controller('myController',['$scope',function($scope){
$scope.name = 'world';
}]);
//here i created directive of name helloWorld
app.directive('helloWorld',function(){
return {
replace:true,
restrict:'AE',
template :'<h3>Hello world<h3/>'
}
});
<html ng-app='myApp'>
<body ng-controller = "myController">
<hello-world/>
</body>
</html>
Error is :
Error: [$compile:tplrt] Template for directive 'helloWorld' must have
exactly one root element.
How to solve this error?
Quick fix
Root cause(replace: true)
<hello-world></hello-world>
change directive template to close h3 tag properly template :'<h3>Hello world</h3>'
Explanation
There are two problem in your code.
You should close your directive custom element like <hello-world><hello-world/>. If you do not close the tag, first occurrence will work fine but after that rest of the thing will not work. See here.
Other thing is your template of directive has wrong template
Directive template
<h3>Hello world<h3/>
should be
<h3>Hello world</h3>
You have template in directive like <h3>Hello world<h3/> which has not closing the h3 tag properly.
So that gets rendered on page like below, which has two h3 element.
<h3>Hello world</h3>
<h3></h3>
So the render html has two elements which are individual. So while passing them to $compile service to compile the content of template, it is throwing [$compile:tplrt] which means your template should have single root element, so the angular will compile that element.
comment the replace: true.
var app = angular.module('myApp',[]);
app.controller('myController',['$scope',function($scope){
$scope.name = 'world';
}]);
//**here i created directive of name helloWorld**
app.directive('helloWorld',function(){
return {
restrict:'AE',
//replace:true,
template :'<h3>Hello world<h3/>'
};
});
or
//**here i created directive of name helloWorld**
app.directive('helloWorld',function(){
return {
restrict:'AE',
replace:true,
template :'<div><h3>Hello world<h3/></div>'
};
});
You are getting the error because in your directive you are using replace:true and the template is enclosed in h3 tag winch is not closed properly (you should close h3 tag using </h3> not <h3/>).
You should enclose the template in a root tag properly like <h3>Hello world</h3>.
When a directive is declared with template (or templateUrl) and
replace mode on, the template must have exactly one root element. That
is, the text of the template property or the content referenced by the
templateUrl must be contained within a single html element. For
example, <p>blah <em>blah</em> blah</p> instead of simply blah <em>blah</em> blah. Otherwise, the replacement operation would result
in a single element (the directive) being replaced with multiple
elements or nodes, which is unsupported and not commonly needed in
practice.
Reference : Angular Docs

In angular js, How do I pass data from a parent controller to a child controller?

I have a little widget I'd like to use over and over on a single page. It has its own controller. Problem is it needs a piece of data to operate (basically a key), and each key is contained in the parent controller.
Here is an example (which is obviously wrong)
http://plnkr.co/edit/VajgOr1LqpLDnbEJcvor?p=preview
script:
angular.module('myApp', [])
.controller('ParentCtrl', ['$scope',
function($scope) {
$scope.keyForChartABC = "somekey1";
$scope.keyForChartXYZ = "somekey2";
$scope.keyForChartLALA = "somekey3";
}
])
.controller('ChartCtrl', ['$scope',
function($scope) {
//todo: have $scope.key assigned from parent somehow
//not shown: use $scope.key to pull data and format chart data
}
])
index:
<!-- ng-init like this is quite wrong -->
<div ng-init="key = keyForChartABC"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartXYZ"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartLALA"
ng-include="'chartwidget.html'"></div>
chartwidget:
<div ng-controller="ChartCtrl">
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
As you can see in the plunker, what I tried here with ng-init doesn't work - key for all the sub-controllers end up with the same value.
I've gotten this to work with ng-repeat and an array of data in the parent, somehow $index gets set in each child to the right index and stays that one value. But I'd like to avoid using ng-repeat in this case so I can have more control of the layout.
Creating re-usable widgets is exactly the purpose of Directives. You can create a directive which handles the output of your widget quite easily.
I forked your plunker and modified it to change it to use a directive.
Here are a few highlights:
First, your template no longer needs the controller defined within it.
<div>
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>
Next, the directive is defined, with an isolate scope which is unique to each instance of the directive:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html'
}
})
Lastly, the directive is declared in the HTML. Note the camel-case name of the directive in the JavaScript, but the hyphenated name in the HTML:
<div>
<chart-widget key="keyForChartABC"></chart-widget>
<hr>
<chart-widget key="keyForChartXYZ"></chart-widget>
<hr>
<chart-widget key="keyForChartLALA"></chart-widget>
</div>
Edit
I updated the plunker to show binding the directive property to an inner controller. This method uses the ControllerAs syntax to define the controller, and binds the directive's scope to the controller scope.
Relevant changes:
.directive('chartWidget', function(){
return {
restrict: 'E',
scope: {
key: '='
},
templateUrl : 'chartwidget.html',
controller: 'chartWidgetController',
controllerAs: 'ctrl',
bindToController: true
}
})
.controller('chartWidgetController', function(){
console.log(this.key);
})
And a small change to the template to support ControllerAs:
<div>
<p>Drawing chart for data: {{ctrl.key}}</p>
<p>some chart directive here</p>
</div>
Note that trying to use ng-controller= in the template will cause the template to have a different scope object from the scope object created for the directive, and the controller would not have access to the properties defined on the directive.
Also note, bindToController is a feature of angular 1.3.x or higher. in angular 1.2.x or earlier, your only option was to use $scope.$watch to monitor the isolate scope for changes.

Accessing directive scope from a separate controller

Let's say i have a <lightbox> directive in AngularJS. The lighbox has two params: title and image_source, which in turn are referenced in its HTML with {{title}} and {{image_source}}.
Because I use Bootstrap's modal, the directive must say on the outmost scope of the page (if I insert it in the inner DOM elements it can have display problems).
I understand that if the directive is in a "parent" controller, there are many ways to interact:
<div ng-controller="ParentCtrl">
<lightbox src='mySrc' ... >
</div>
but I am forced to pull it out:
<div ng-controller="ParentCtrl">
</div>
<lightbox ...>
(Edit: to clarify: i am forced to put the modal OUT of the controller (which is a template called by a route) because it interacts badly if it is not. Another way would be to wrap all the page in a Main controller, but I'm not sure it's elegant.)
But how do I access the internal directive scope and change dynamically the two values?
(Edit: thought of using a service, not sure if it is the right way, though)
this is method to call rootscope to directive
angular.module("directiveAPP", [])
.controller("directiveController", function ($scope, $rootScope) {
$rootScope.src = "http://picbook.in/wp-content/uploads/2014/07/image_123.jpg";
$rootScope.title = "hai";
}).directive("lightBox", function () {
return {
restrict: 'E',
template: '<div><img src="{{imageSrc}}" title="{{imageTitle}}" width="100px" style="border:solid #ddd 1px;" /></div>',
scope: {
imageSrc: '=',
imageTitle: '='
}
};
});
this one use of directive
<div ng-app="directiveAPP">
<div ng-controller="directiveController">
<light-box image-src="src" image-title="title"></light-box>
</div>
<light-box image-src="src" image-title="title"></light-box>
</div>
FIDDLE LINK

In Angular JS - how to use ng-include to make my code generic and reusable?

Using the AngularJS,
I want to use ng-include to make my code generic.
So here is my problem:
I want to have my 'ang-attribute.html' file used in the ng-include to be as generic as possible, and for that I want to get the attribute name outside of the html.
With something like this:
<div data-field-name="display_name">
<div ng-include="'ang-attribute.html'"></div>
</div>
In my html file I would like to use the data-field-name, and then use it again with a different value.
I tried to get it via DOM... and couldn't find a way to do that.
So in my ng-include html file I have the following line:
<div ng-controller="attributeCtrl">
<div class="my-profile-constant-text">{{displayAttribute}}:</div>
....
</div>
Maybe there is a way to pass a constructor to the controller and then I could pass my data-field-name into it?
Or any other solution?
Thanks!
I believe you want to be looking at directives, this has an attributes object easily accessible after the dom has rendered to re-use.
Html
<my-directive field-name="display_name"></my-directive>
Directive
angular
.module("myApp", [])
.directive("myDirective", function() {
return {
restrict: "E",
template: "<div>{{exposeAttribute}}</div>",
//templateUrl: "mytemplate.html",
link: function(scope, element, attr) {
scope.exposeAttribute = attr.fieldName;
}
};
});
http://jsfiddle.net/mhCaD/

Categories