ng-repeat of array of object on custom directive not working - angularjs

I am trying to use ng-repeat on array of below object to repeat my custom directive.
My array looks something like below.
var Competitors = [];
Competitors.push({
key: 1,
value: {}//Blank object
});
Competitors.push({
key: 2,
value: {}//Blank object
});
Competitors.push({
key: 3,
value: {}//Blank object
});
My custom directive.
module.directive('miChart', function () {
return {
restrict: 'E',
template: '<div></div>',
scope: {
chartData: "=value",
chartObj: "=?"
},
replace: true,
link: function ($scope, $element, $attrs) {
$scope.$watch('chartData', function (value) {
if (!value) {
return;
}
$scope.chartData.chart.renderTo = $scope.chartData.chart.renderTo || $element[0];
$scope.chartObj = new Highcharts.Chart($scope.chartData);
});
}
}
});
Here is the code through which i am trying to create multiple chart with my directive.
<div ng-repeat="(key,value) in Competitors">
<mi-chart value="Competitors[key]" chart-obj="Competitors[key]"></mi-chart>
</div>
When i debug my code, the directive is not getting exact value and giving Competitors[key] as a value and chart-obj. So it is not replacing the values on ng-repeat.
Can anybody tell what's wrong i am doing and what is the solution to this problem.

I modified your html like this:
<div ng-repeat="competitor in Competitors track by competitor.key">
<mi-chart value="competitor" chart-obj="competitor"></mi-chart>
</div>
which results in correct binding of the elements as you can see in the code snippet below.
The track by component is optional but because you already have a unique key value available it would be wise to use it. It improves performance in case some entries get updated.
However I am not sure if that is what you are looking for.
var module = angular.module('app',[]);
module.controller('ctrl', function($scope){
$scope.Competitors = [];
$scope.Competitors.push({
key: 1,
value: {}//Blank object
});
$scope.Competitors.push({
key: 2,
value: {}//Blank object
});
$scope.Competitors.push({
key: 3,
value: {}//Blank object
});
});
module.directive('miChart', function () {
return {
restrict: 'E',
template: '<div>-----------<br/>chartData - key: {{chartData.key}} value: {{chartData.value}}<br/>chartObj - key: {{chartObj.key}} value: {{chartObj.value}}</div>',
scope: {
chartData: "=value",
chartObj: "=?"
},
replace: true,
link: function ($scope, $element, $attrs) {
$scope.$watch('chartData', function (value) {
if (!value) {
return;
}
//$scope.chartData.chart.renderTo = $scope.chartData.chart.renderTo || $element[0];
//$scope.chartObj = new Highcharts.Chart($scope.chartData);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
Entries:
<div ng-repeat="competitor in Competitors track by competitor.key">
<mi-chart value="competitor" chart-obj="competitor"></mi-chart>
</div>
</div>

Related

How to make custom directive run function in angularjs 1.x?

How do I make a directive run a function like the other built in directives in angular?
In example:
<div ng-repeat="someId in myList" my-directive="runFunctionInScope(someId, divHtmlElement)" />
myApp.directive('myDirective', function ()
{
return function (scope, element, attrs)
{
//??
}
}
You can try something like the below code snippet. Also please check this plunker for working example of your given scenario.
Template:
<div ng-repeat="someId in myList" my-method='theMethodToBeCalled' my-id='someId.id' />
Controller:
app.controller('MainCtrl', function($scope) {
$scope.myList=[{
id: 1,
value: 'One'
}, {
id: 2,
value: 'Two'
}];
$scope.theMethodToBeCalled = function(id) {
alert(id);
};
});
Directive:
app.directive("myMethod",function($parse) {
var directiveDefinitionObject = {
restrict: 'A',
scope: {
method:'&myMethod',
id: '=myId'
},
link: function(scope,element,attrs) {
var expressionHandler = scope.method();
expressionHandler(scope.id);
}
};
return directiveDefinitionObject;
});

Angularjs: Create a new scope property inside a directive

The value is never shown on the DOM and i'm just trying to see if this way works..
What i am trying to do is create a new scope value inside the directive and show it to the DOM
app.directive("rest", ["restFactory", function($rest){
return {
restrict: "A",
scope: {
uri: "#rest",
},
link: function(scope){
scope.$rest = [];
$rest.batch(scope.uri)
.then(function(data){
scope.$rest = data;
});
}
}
}]);
the data i am trying to show comes from a function that returns a promise, and with the promise comes the data i want to use in the DOM
the HTML is coded like this:
<div rest="accounts">
<div ng-repeat="data in $rest">
{{data.id}}
</div>
</div>
this is the first directive i ever did.
I made this plunker to explain why your directive doesn't work.
<div rest="accounts">
<!-- You can't access the directive variable $rest from here
The directives variables are only available on it's template. -->
<div ng-repeat="data in $rest">
{{data.id}}
</div>
</div>
This will work:
app.directive("restTwo", function() {
return {
restrict: "A",
scope: {},
// It will work. I put the template in here.
// You can only access the directive variables from it's template
template: '<div ng-repeat="data in $rest">{{data.id}}</div>',
link: function(scope, el, attr) {
console.log(attr.rest); // output = accounts
//data
scope.$rest = [{
'id': 1,
'name': 'John Doe'
}, {
'id': 2,
'name': 'Johana Doe'
}];
console.log(scope.$rest);
}
}
});
I suggest you to make a factory and make your api call's in it like this:
app.factory('myFactory', function($http) {
// GET example
this.get = function(string) {
return $http({
method: 'GET',
url: 'https://api.github.com/search/repositories?q=' + string
});
}
// Your request here
this.yourRequest = function(uri) {
// return $rest.batch(uri);
}
return this;
});
And in your controller:
app.controller('MainCtrl', function($scope, myFactory) {
$scope.myData = [];
myFactory.get('tetris').then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
$scope.myData = response.data.items;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
});
View:
<div ng-repeat="data in myData">
{{data.id}}
</div>
If you REALLY want to use a directive for this (I do not recommend):
Directive:
app.directive("restThree", function() {
return {
restrict: "A",
scope: {
'data': '='
},
link: function(scope, el, attr) {
//console.log(attr.rest); // output = accounts
//data
scope.$rest = [{
'id': 1,
'name': 'John Doe'
}, {
'id': 2,
'name': 'Johana Doe'
}];
scope.data = scope.$rest;
}
}
});
View:
<div rest-three="accounts" data="directiveData">
<div ng-repeat="data in directiveData">
{{data.id}}
</div>
</div>

Two way binding in a directive that triggers a watch even after the data changes again?

I'm writing a directive that will lookup a value from a list of data and always display that value. Why when I click the change data button in this plunkr example does it not change the result? I've tried with a $watch but it only fires once.
Plunk: http://plnkr.co/edit/9A3z0KMm8EaBNgYv1NmH?p=preview
For example, here is the usage:
<lookup-binder lookup="lookupList" find="data.id" result="lookupList.description"></lookup-binder>
Here is what the directive looks like:
app.directive('lookupBinder', function() {
return {
restrict: 'E',
scope: {
lookup: '=',
find: '=',
result: '='
},
controller: function($scope){
console.log('controller fired');
},
link: function(scope, element, attrs, tabsCtrl) {
console.log('link fired')
scope.$watch(scope.find, function(newValue, oldValue) {
console.log('watch fired');
var el = _.find(scope.lookup, { id: scope.find });
if(el != null){
scope.result = el.description;
}
});
},
template: 'lookup Binder: <br/> lookupList: {{lookup}} <br/> find: {{find}} <br/> result: {{result}} <br/>'
};
});
Controller:
var appName = angular.module('app', []);
angular.module('app').controller('testController', TestController);
TestController.$inject = ['$scope'];
function TestController($scope) {
$scope.gradeLevels = [{
id: '1',
description: 'kindergarden'
}, {
id: '2',
description: 'first grade'
}];
$scope.gradeLevel = {
gradeLevelID: '1',
record: 'test1'
};
console.info('gradeLevels[0].id = ' + $scope.gradeLevels[0].id);
console.info('gradeLevel.gradeLevelID = ' + $scope.gradeLevel.gradeLevelID);
}
scope's $watch function takes either a string or a function, so change it to either one of them:
scope.$watch('find', function(newValue, oldValue) {
...
}
Also, the result of _.random is a number, and your ids are strings, so change the random to a string:
$scope.data.id = _.random(1,4).toString();
Check this plunker

Can directive isolated scope be used inside directive for calculations

If there is a directive defined can the scope passed to it through attributes, defined on it, be used inside this directive to get needed results for usage in template? i.e. I have such directive
var carAuction = angular.module('carAuction');
carAuction
.controller('mainCtrl', function($scope)
{
var car = {
comments: []
};
car.comments.push({
comment: 'Ok car',
rating: 1
});
car.comments.push({
comment: 'Nice car.',
rating: 2
});
car.comments.push({
comment: 'Awesome car!',
rating: 3
});
$scope.car = car;
})
.directive('carCommentRaiting', function()
{
return
{
restrict: 'E',
templateUrl: 'path/to/template.html',
scope:
{
value: '=value',
maxValue: '=max-value'
}
};
})
.filter('range', function()
{
return function(input, total)
{
total = parseInt(total);
for (var i=1; i<=total; i++)
{
input.push(i);
}
return input;
};
});
In html part I have
<div>
<div ng-repeat="comment in car.comments">
Rating: <car-comment-raiting value="comment.rating" max-value="10"></car-comment-raiting>
</div>
</div>
template.html
<div>
<ul class="list-inline">
<li ng-repeat="n in [] | range:value"><span class="glyphicon glyphicon-star"></span></li>
</ul>
</div>
And I want to pass additional value to the template which should be calculated as maxValue - value. Haven't found any example describing that. Thought about using link property, but description tells, that it is used for other purpose.
UPD:
I was able to fix it with
return {
restrict: 'E',
templateUrl: 'path/to/template.html',
scope:
{
value: '=',
maxValue: '='
},
controller: function($scope)
{
$scope.calculated = $scope.maxValue - $scope.value;
}
};
but for some reason it doesn't work all the time. One time it works and the other time calculated variable is null.
All calculations must be done inside a direcitve link function or in controller.
Here is example with directive:
.directive('carCommentRaiting', function() {
return {
restrict: 'E',
template: 'path/to/template.html',
scope: {
value: '=value',
maxValue: '=max-value'
},
link : function(scope, element, attr) {
scope.calculated = scope.maxValue - scope.value;
/// watch value to update calculated on value update:
scope.$watch('value', function(newValue){
scope.calculated = scope.maxValue - newValue;
});
}
};
});

Angular dynamic polymorphic directives

I'm a beginner to Angular but am poking into some slightly more advanced corners to ensure it has the features I need.
Specifically I need to:
render a sequence of widgets of different types with each implemented as an independent Angular directive
the widget type is determined from the data, not by markup
widgets are each defined in a separate file
set the scope of the directive to the data for that widget instance
I think I have solved the requirement described below and implemented at http://jsfiddle.net/cUTt4/5/
Questions:
Is this correct, best practice, and reasonably fast?
Any improvements I should add?
It would be better if the widget directives had no explicit reference { item : '=' } to obtain their isolated scope, but their sub-scopes should be built by the renderform directive. How do I do that?
My solution:
HTML
(Note the Angular templates are in script here due to limitations of jsfiddle)
<div ng-app="myApp">
<script type="text/ng-template" id="widget-type-a">
<div>
<label>{{ item.label}} </label>
<input type="text" ng-model="item.val" >
</div>
</script>
<script type="text/ng-template" id="widget-type-b">
<div>
<label>{{ item.label}}</label>
<input type="text" ng-model="item.val" >
</div>
</script>
<div ng-controller="FormCtrl">
<renderform></renderform>
</div>
</div>
main.js :
var app = angular.module('myApp', []);
function FormCtrl($scope) {
items = [
{
type: 'widget-type-a',
label : 'Widget A instance 1',
val: 1
},
{
type: 'widget-type-b',
label : 'Widget B instance 1',
val : 2
},
{
type: 'widget-type-a',
label : 'Widget A instance 2',
val : 3
}
];
$scope.items = items
}
app.directive('renderform', function($compile) {
function linkFn(scope, element) {
var item,
itemIdx,
templStr = '',
newParent,
data,
newEl;
newParent = angular.element('<div></div>')
for(itemIdx in scope.items) {
item = items[itemIdx];
templStr += '<div ' + item.type + ' item="items[' + itemIdx + ']"></div>';
}
newEl = angular.element(templStr);
$compile(newEl)(scope);
element.replaceWith(newEl);
}
return {
restrict: 'E',
link:linkFn
};
});
app.directive('widgetTypeA', function() {
return {
restrict: 'A',
templateUrl: 'widget-type-a',
scope: { item: '=' }
};
});
app.directive('widgetTypeB', function() {
return {
restrict: 'A',
templateUrl: 'widget-type-b',
scope: { item: '='}
};
});
sorry fast answer, not tested :
<div data-ng-repeat="item in items">
<div data-ng-switch data-on="item.type">
<div data-ng-switch-when="widget-type-a" data-widget-type-a="item"></div>
<div data-ng-switch-when="widget-type-b" data-widget-type-b="item"></div>
</div>
</div>
If this is what you're looking for, please improve this answer.
I have been thinking about this problem for some time now and, although the ng-switch option work for simple cases, it introduces quite a maintenance overhead.
I've come up with a solution which allows for a single point of maintenance. Consider the following:
var app = angular.module('poly', []);
app.controller('AppController', function ($scope) {
$scope.items = [
{directive: 'odd-numbers'},
{directive: 'even-numbers'},
{directive: 'odd-numbers'}
];
});
app.directive('component', function ($compile) {
return {
restrict: 'E',
controller: function () {
},
controllerAs: 'component_ctrl',
link: function (scope, element, attrs) {
var child_directive = scope.$eval(attrs.directive);
var child_element = $compile('<' + child_directive + ' data="data"></' + child_directive + '>')(scope);
element.append(child_element);
}
}
});
app.directive('oddNumbers', function ($interval) {
return {
restrict: 'E',
link: function (scope) {
scope.number = 0;
$interval(function () {
scope.number += 2;
}, 1000);
},
template: '<h1>{{ number }}</h1>'
}
});
app.directive('evenNumbers', function ($interval) {
return {
restrict: 'E',
link: function (scope) {
scope.number = 1;
$interval(function () {
scope.number += 2;
}, 1000);
},
template: '<h1>{{ number }}</h1>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="poly" ng-controller="AppController">
<div ng-repeat="item in items">
<component directive="item.directive" data="item.data"></component>
</div>
</section>
This allows for the components to be specified in the controller in an ad hoc way and the repeater not having to delegate responsibility via a switch.
NB I didn't implement how the data is passed between components

Resources