AngularJS directive not binding attributes correctly - javascript

I'm relatively new to angular and this is my first involved directive and I'm a little lost. I'm trying to create a re-usable list of items on separate tabs in my app. The list acts the same with the exception of how the items are displayed (handled by separate partials). I'm passing in attributes of many different types into the scope and I'm trying several different things based on what I've been reading. No matter what I've tried so far, I'm still having issues with the attributes binding correctly.
Below is my code and I'll try to explain it as best as possible, hopefully someone can tell me where I went wrong. The only things that appear to have bound correctly are the strings, the objects and functions are missing.
UPDATE: Turns out I needed to bind $scope.currentPage to the directive scope. Now ng-repeat is running, but other parts of the page that require access to the controller scope aren't working. I've updated the code below and continue looking into how to give access to the template.
Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function(scope, element, attributes){
scope.$watch("query.value", function(){
scope.filterFunction(); //pretty sure this never gets called on search
});
}
return {
restrict: 'E',
replace: 'true',
templateUrl: 'partials/directives/list-tab.html',
link: linkFunction,
scope: {
filterFunction: "&filterFunction",
searchPlaceholder: "#searchPlaceholder",
pagedItems: "=pagedItems",
clickFunction: "&clickFunction",
classString: "#classString",
infoTemplate: "#template",
currentPage: "=currentPage"
}
}
});
index.html
//pagedCars is an array of nested objects that gets used by the template to display the information
//filterCars is a function
//carSelected is a function
<div class="available-items">
<item-list filter-function="filterCars" search-placeholder="Search Cars" paged-items="pagedCars" current-page="currentPage" click-function="carSelected" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
list-tab.html
<div class="form-group">
<div class="search-field">
<label for="searchField" id="searchLabel">Search</label><br/>
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}"/>
</div>
<table class="table table-hover>
<tbody>
//currentPage is on the controller scope there's a separate control that allows the user to page through the pagedItems by updating the currentPage which would be reflected here
<tr ng-repeat="item in pagedItems[currentPage]" ng-click="clickFunction($index) ng-class="classString">
<td ng-include="infoTemplate"></td>
</tr>
</tbody>
</table>
</div>
cars-template.html
<div class="row form-inline" id="{{item.carId}}">
<div class="col-md-2">
//this uses a method on the controller scope to format the url
<img ng-src="{{retrieveIcon(item.iconUrl)}}" height="75px" width="75px"/>
<div class="col-md-10">
<div details-pane" id="carDetails" ng-include="'partials/cars/car-full-details.html'"></div>
<div class="item-title">{{item.name}}</div>
//the rest is just a table with more information about the item. item.description, item.mileage, etc...
</div>
</div>

Try passing through your functions with their parentheses
<div class="available-items">
<item-list filter-function="filterCars()" search-placeholder="Search Cars" paged-items="pagedCars" click-function="carSelected()" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
Also FYI if your variable has the same name in your HTML as you want in your directive's scope you can just use the pass method. e.g.
scope: {
filterFunction: "&",
searchPlaceholder: "#",
pagedItems: "#",
clickFunction: "&",
classString: "#",
infoTemplate: "#template"
}

Related

How to get passed attributes from custom directive and collect them in an array to show into template

I have created a custom directive which should display a slider with the data entered to the custom directive. I need to be able to display the image Url and redirect Link via directive attributes for example:
<div class="sliderBanner" imgUrl="http://example.com/img1.jpg" imgLink="example.com"></div>
<div class="sliderBanner" imgUrl="http://example.com/img2.jpg" imgLink="example.com"></div>
<div class="sliderBanner" imgUrl="http://example.com/img3.jpg" imgLink="example.com"></div>
Now I want to collect those data and place them inside an array within directive scope and use ng-repeat inside directive template to show them.
e
PS: I'm using Swiper angular directive for slider purpose.
var app = angular.module('APP',['ksSwiper']);
app.directive('sliderBanner',function($http){
return{
scope:true,
restrict:'C',
link: function(scope,element,attr){
scope.data = [];
scope.data.push({
"id": attr.id,
"imgUrl": attr.imgUrl,
"imgRef": attr.imgRef
});
},
templateUrl: 'http://www.lajmislam.com/wp-content/themes/Newspaper/ng-templates/sliderBanner.html'
}
});
This is my directive template:
<ks-swiper-container autoplay="3000" show-nav-buttons="true" pagination-is-active="true" swiper="swiper">
<ks-swiper-slide ng-repeat="item in data">
{{item.id}},{{item.imgUrl}}
</ks-swiper-slide>
</ks-swiper-container>
<div ng-repeat="item in data">
{{item.id}},{{item.imgUrl}}
</div>
What errors are you seeing?
What you have here appears to be working. I tested it out here: https://github.com/styonsk/StackOverflowSolutions/tree/master/35997232

How to apply directive conditionally in AngularJS?

I want to apply a simple directive conditionally using ngAttr. I don't understand why my directive is always displayed. So if I have an undefined / false variable I want to apply my directive: dirr.
When using ngAttr, the allOrNothing flag of $interpolate is used, so if any expression in the interpolated string results in undefined, the attribute is removed and not added to the element.
My code pen
<div ng-app="myApp" ng-controller="MainController" class="container-fluid">
<h2 ng-bind="currentVersion"></h2>
<hr>
<div ng-attr-dirr="hidden || undefined">Default div</div>
</div>
angular.module('myApp',[])
.directive('dirr', function(){
return {
restrict:'AE',
template:'<div>Div from directive</div>'
}
})
.controller('MainController',['$scope', function($scope){
$scope.currentVersion = 'Angular 1.3.6';
$scope.hidden = undefined;
}])
;
You can make use of AngularJS's inbuilt directive ng-if to check for the condition and execute it conditionally.
Example:
<div ng-if="{some_condition}">
<dirr></dirr> <!--Execute the directive on the basis of outcome of the if condition-->
</div>
Form documentation
All of the Angular-provided directives match attribute name, tag name, comments, or class name
so whenever angular matches a diretive with attribute name,it compiles the template and renders the html irrespective of attribute value.
anyway you can use scope in directive template.so use ng-hide with scope's hidden property
angular.module('myApp',[])
.directive('dirr',function(){
return{
restrict:'AE',
template:'<div ng-hide="hidden">Div from directive</div>',
}
})
.controller('MainController',['$scope', function($scope){
$scope.hidden=false;
}]);
The answers are true and the sample code you provided works for small issues, but when it comes resolving this problem on large applications you may want to take this approach:
Updated Pen
<div ng-app="myApp" ng-controller="MainController" class="container-fluid">
<h2 ng-bind="currentVersion"></h2>
<hr>
<div dirr value="{{hidden}}">Default div</div>
</div>
.directive('dirr', function($log){
var directiveInstance = {
restrict:'AE',
scope:{
value:'#'
},
link: function (scope, element, attrs, ctrl) {
if(attrs.value === 'true'){
console.log('directive on !!');
$log.debug('element',element);
element[0].innerHTML = '<div>hello ' + attrs.value + '</div>';
}
else {
console.log('directive off !!');
}
}
}
return directiveInstance;
})
This is more tidy and you may not want to duplicate your code using ngIf or ngSwitch directives in seperate divs when you have something like:
<table>
<thead dirr value="{{statement}}">
<tr>
<th>
CrazyStuffHere...
</th>
<th>
CrazyStuffHere...
</th>
....
</tr>
</thead>
</table>

How to access functions of a controller from within another controller via scope?

I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.

Angular JS breaking two way binding on isolate scope when binding to a primitive and using ng-include to dynamically load a template

I have a bit of a strange scenario that is a little different to the other childscope and two way binding issues I have seen on Stackoverflow.
I have a field generation directive that receives a configuration object and some data and dynamically creates the correct type of field on screen and populates the data.
directive.js
.directive('myField', function () {
var stringTemplate = "scripts/directives/templates/my-string.tpl.html";
var textTemplate = "scripts/directives/templates/my-text.tpl.html";
var selectTemplate = "scripts/directives/templates/my-select.tpl.html";
var linker = function ($scope, elem, attrs) {
// Function to dynamically select the correct template
$scope.getTemplateUrl = function () {
var template = '';
if ($scope.options) {
if ($scope.options.optionList) {
template = selectTemplate;
} else {
switch ($scope.options.type) {
case 'String':
template = stringTemplate;
break;
case 'Text':
template = textTemplate;
break;
}
}
return template;
}
};
return {
restrict: 'E',
replace: true,
scope: {
options: '=',
data: '=',
fieldName: '#',
fieldWidth: '#',
labelWidth: '#',
},
link: linker,
template: '<ng-include src="getTemplateUrl()"/>'
}
});
I then have the corresponding template... I'm showing just the string template in this case.
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" ng-model="data" tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
An example of how this might then be used would be
controller.js
$scope.person.firstName = "John";
$scope.person.lastName = "Doe";
$scope.options.person.firstName.type = "String";
index.html
<div class="row">
<my-field options="options.person.firstName" data="person.firstName" field-name="firstName" label-width="small" field-width="medium"></my-field>
The problem is the usual one, my-field directive has an isolated scope with a "data" property that is two-way bound to the controller. Because I am then using ng-include to dynamically load the correct template I am creating a further child scope that due to prototypical inheritance still populates correctly as it doesn't have its own data property so reaches to the parent. However when I modify the field, a shadow property is created on my child scope called data that doesn't propagate upwards the way that two way binding should.
I hope you are still with me
controller > my-field
ng-include causes the following scopes to exist
controller > my-field > ng-include
From reading around I understand that what I need to do to rectify this is pass an object rather than a primitive, however as there is effectively an intermediate layer between my controller and my final directive this is not straightforward.
I thought about changing the isolate scope in my-field to look like this
scope: {
....
data: {value: '=data'}
....
}
and then updating the template to refer to the object
my-string.tpl.html
<div class="form-group col-md-12">
<label for="{{fieldName}}" class="{{labelWidth}}">
{{options.label}}
</label>
<div class="{{fieldWidth}}">
<input type="text" class="form-control input-sm" id="{{fieldName}}" placeholder="{{options.watermark}}" **ng-model="data.value"** tooltip="{{options.tipText}}" ng-disabled="options.editable === false">
</div>
</div>
but this kills angular.
I have successfully got it to work by reaching back to the controller scope for binding by using
ng-model="$parent.$parent.data"
but I am not really happy with this as a solution as A it is ugly and B it involves knowing the depth of scope you are at which could vary.
Really stumped with this. Any help would be appreciated.

Problems with angular validation in directive

I am fighting with the validation in an angular directive without success.
The form.name.$error object seems to be undefined, when I submit the name property to the directive template. If i use a fixed name-attribute inside the template, the $error object is fine, but of course identical for all elements.
The html is:
<form name="form" novalidate>
<p>
<testvalidation2 name="field1" form="form" field="testfield4" required="true">
</testvalidation2>
</p>
</form>
The directive looks like this:
app.directive('testvalidation2', function(){
return {
restrict: 'E',
scope: {
ngModel: '=',
newfield: '=field',
required: '=required',
form: '='
},
templateUrl: 'template2.html',
link: function(scope, element, attr){
scope.pattern = /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/;
scope.name = attr.name;
}
} // return
});`
and finally the template:
<div>
<input name="{{name}}" type="text" ng-model="newfield" ng-required="required" ng-pattern="pattern"> {{FIELD}}</input>
<span ng-show="form.name.$error.required">Required</span>
<span ng-show="form.name.$error.pattern"> Invalid </span>
<p>Output {{form.name.$error | json}}</p>
</div>
I have created a plunker for my Angular Validation Problem
and would be happy, if someone would help me to win the fight.
Michael
I don't have a fix for this but I can tell you what the problem is.
Firstly in your html form="form" should have name of the form form="form2".
Secondly Since you are creating a new scope in the directive, the scope created is a isolated scope which does not inherit from parent, which means that the the template input control that you add would not get added to the parent scope form2.
The only way out currently i can think of is to not use isolated scope.

Categories

Resources