Angular ng-repeat issue - angularjs

I've this controller:
app.controller('HomeController', function($scope) {
$scope.buttonList = [
{
href: "http://ciao.html",
cssClass: "",
iconBeforeCssClass: "",
labelCssClass: "",
labelText: "ciao",
iconAfterCssClass: "",
},
{
href: "ciao2.html",
cssClass: "",
iconBeforeCssClass: "",
labelCssClass: "",
labelText: "ciao2",
iconAfterCssClass: "",
}
];
});
This directive:
app.directive('widgetButtonList', function() {
var directive = {};
directive.restrict = 'E';
directive.replace = false;
directive.templateUrl = 'modules/directives/widget-button-list.html';
directive.scope = {
additionalCssClass: "#",
buttons : "#",
};
return directive; });
The template is the follow:
<div class="ap-block ap-button-list-block {{additionalCssClass}}">
<ul>
<li ng-repeat="btn in buttons track by $index">
<a href="{{btn.href}}" class="{{btn.cssClass}}">
<i class="ap-button-list-before-icon {{btn.iconBeforeCssClass}}" ></i>
<span class="ap-button-list-label {{btn.labelCssClass}}">{{btn.labelText}}</span>
<i class="ap-button-list-after-icon {{btn.iconAfterCssClass}}" ></i>
</a>
</li>
</ul>
And the view is like this:
<div ng-controller="HomeController">
<widget-button-list buttons="{{buttonList}}"></widget-button-list>
But otherwise to render two times the template button as i expected, it print 250 time the widget's template without binding nothing. Can someone help me??

You'll want to pass the buttonsList using a different isolate scope attribute instead of using text-binding. The # symbol on the directive definition object indicates you'll be passing in a string, where you're actually passing in an array of objects. Try this:
directive.scope = {
additionalCssClass: "#",
buttons : "=" //instead of #
};
<widget-button-list buttons="buttonList"></widget-button-list>
Plunker Demonstration
And just for the sake of completeness, you could pass in the buttonsList as a string, but you'll have to be aware that in the directive you'll be recieving a JSON string. Then you'll need to parse it inside the directive:
directive.scope = {
additionalCssClass: "#",
buttons: "#"
};
directive.controller = function($scope) {
$scope.btns = JSON.parse($scope.buttons);
}
I don't suggest this second method, but here's the Plunker Demonstration of that as well.

Related

angularjs $compile html template in forEach not update variables

I want to dynamically generate html. I have generateHtml function contains loop for items, currently it is not displaying proper variables added in template. It is always display the last items data on all the iteration on compiled html.
Following is the controller & template code
This is my controller code
angular.module('app').controller('PageController', ['$scope', '$sce', '$compile','$templateRequest',
function ($scope, $sce, $compile,$templateRequest) {
$scope.itemsHtml = '';
// Array contains dynamic data
vm.items = [{
id: 1,
name: 'abc',
}, {
id: 2,
name: 'pqr',
}, {
id: 3,
name: 'stu',
}, {
id: 4,
name: 'xyz',
}];
vm.currentItem = [];
let templateUrl = $sce.getTrustedResourceUrl('/views/item.html');
$templateRequest(templateUrl).then(function(template) {
vm.itemTemplate = template;
}, function() {
});
vm.generateHtml = function() {
items.forEach(function (item, key) {
vm.currentItem = item;
let compiledTemplate = $compile(vm.itemTemplate)($scope).html();
/* Append compiled dynamic html */
$scope.itemsHtml += compiledTemplate;
});
}
function init() {
vm.generateHtml();
}
init();
}
]);
This is template view
<script type="text/ng-template" id="item.html">
<div className="item-wrapper">
<div className="item-inner">
{{ pageCtrl.currentItem.name }}
</div>
<div className="action-inner">
<div className="btn-action"
role="button"
ng-click="pageCtrl.edit(
pageCtrl.currentItem.id
)">
<i className="fa fa-plus"></i>
</div>
</div>
</div>
</script>
I got the solution for this issue.
Actually when we use compile after that we have to interpolate the compiled template
compiledTemplate = $interpolate(compiledTemplate)($scope);
let compiledTemplate = $compile(vm.itemTemplate)($scope).html();
/* Here interpolated compiled template */
compiledTemplate = $interpolate(compiledTemplate)($scope);
/* Append compiled dynamic html */
$scope.itemsHtml += compiledTemplate;

Angular directive with dynamic content

I have created an accordion toggle for ionic angular in this punkr.
What i intend to achieve is to translate it to an directive that can be used as follow, where the content is dynamic based on the html that user insert. It could be a form, a text or simply a button. How could it be done??
<custom-accordion title="Header 1">
Content 1
</custom-accordion>
<custom-accordion title="Header 2">
Text: <input type="text" style="background: grey;" /><br>
Number: <input type="number" style="background: grey;" /><br>
</custom-accordion>
You can create a directive for the accordion and then load the content dynamically based on a scope variable. You might have to create separate HTML files for the content that you desire. Here is a plunkr for the same.
Directive
angular.module('starter.directives', [])
.directive("dynamicAccordion", function() {
return {
restrict:"A/E",
scope: {
content: "#"
},
template:"<div ng-include=getContent()></div>",
link: function(scope) {
scope.getContent = function() {
return scope.content;
},
scope.toggleContent = function() {
scope.isShow = !scope.isShow;
},
scope.isShow = true;
}
}
});
HTML
<ion-view title="Dashboard">
<ion-content class="has-header padding">
<dynamic-accordion content="accordionbutton.html"></dynamic-accordion>
<dynamic-accordion content="accordionform.html"></dynamic-accordion>
</ion-content>
</ion-view>
EDIT
This plunkr exposes the model from each form to the controller.
Directive
angular.module('starter.directives', [])
.directive("dynamicAccordion", function() {
return {
restrict:"A/E",
scope: {
content: "#",
model: "="
},
template:"<div ng-include=getContent()></div>",
link: function(scope) {
scope.getContent = function() {
return scope.content;
},
scope.toggleContent = function() {
scope.isShow = !scope.isShow;
},
scope.isShow = true;
}
}
});
HTML
<form>
{{ form | json }}
<dynamic-accordion content="accordionbutton.html" model="model1"></dynamic-accordion>
<dynamic-accordion content="accordionform.html" model="model2"></dynamic-accordion>
</form>
<button ng-click="checkModel()">Check Model</button>
Controller
$scope.model1 = {
text: "Default - 1",
number: 0
};
$scope.model2 = {
text: "Default - 2",
number: 0
};
$scope.checkModel = function() {
console.log("Text_1 "+$scope.model1.text +" Number_1 "+$scope.model1.number);
console.log("Text_2 "+$scope.model2.text +" Number_2 "+$scope.model2.number);
}

How to pass a object into a directive

I have a items array which is used by ng-repeat to render the menu,
and on click of Add to cart button addItem() is called.
Currently i pass the name of the selected item as the name attribute in item-container directive.
How shall i pass an entire object through the attribute to the directive
HTML snippet
<p ng-repeat = "item in items">
<item-container
startcounter = 1
resetter = 'reset'
item = 'item'
name = {{item.name}} >
{{item.name}}
</item-container><br><br>
</p>
JS snippet
.directive('itemCounter',function(){
return {
controller: function() {return {}},
restrict:'E',
scope:{
item:"=",
resetter:"="
},
transclude:true,
link:function(scope,elem,attr){
scope.qty = attr.startcounter
scope.add = function(){
scope.qty++;
}
scope.remove = function(){
scope.qty--;
}
scope.addItem = function(){
console.log(attr.item);
scope.$parent.addMsg(scope.qty,attr.name)
console.log("value when submitted:" + scope.qty + "name:"+ attr.name);
scope.qty = attr.startcounter;
scope.$parent.resettrigger();
}
scope.$watch(function(attr){
return attr.resetter
},
function(newValue){
if(newValue === true){
scope.qty = attr.startcounter;
}
});
},
template:"<button ng-click='addItem();'>Add to cart</button>&nbsp&nbsp"+
"<button ng-click='remove();' >-</button>&nbsp"+
"{{qty}}&nbsp" +
"<button ng-click='add();'>+</button>&nbsp&nbsp"+
"<a ng-transclude> </a>"
}
Currently you actually aren't even passing in the name it seems. All the passing in magic happens in this part:
scope:{
resetter:"="
},
As you can see, there is no mention of name. What you need to do is add a field for item and just pass that in:
scope:{
resetter:"=",
item: "="
},
Then you can just do
<p ng-repeat = "item in items">
<item-container
startcounter = 1
resetter = 'reset'
item = item >
{{item.name}}
</item-container><br><br>
</p>
Also I'm fairly sure you dont want to be using transclude here. Look into templateUrl

Updating ng-include from directive in Angular

I am trying to click on a list item that is created via a repeater and update the template value that is being used by ng-inlude. The initial value that is being set in the controller is working fine (shows up in DOM), however when i change the value in the directive, it is not updating the DOM (stays as initial included DOM). When i use fire bug on the directive it looks as though the scope has changed, although I'm not sure if I am missing something, or if i should be doing this some other way.
This is my partial
<div ng-controller="menuCtrl" >
<ul class="sub-menu">
<li ng-repeat="item in menuItems.left" menu-repeater-directive>
<span>{{item.name}}</span>
<a class="reset-menu-btn">×</a>
</li>
</ul>
<div id="lft-wrapper" class="a-wrapper">
<div ng-include="template.url" id="lft-scroller" class="a-scroller"></div>
</div>
</div>
This is my Menu Control
angular
.module('simApp')
.controller("menuCtrl", function($scope, $element) {
$scope.menuItems = {
left: [
{
name: "Table of Context",
url: "static/partials/tree.html"
},
{
name: "Index",
url: "static/partials/dictionary.html"
},
{
name: "Exercises",
url: "static/partials/exercises.html"
},
{
name: "Search Results",
url: "static/partials/results.html"
}
],
right: [
{
name: "About Writer's Help",
url: "static/partials/about.html"
},
{
name: "Tags & Tools",
url: "static/partials/tools.html"
},
{
name: "Your Class",
url: "static/partials/class.html"
},
{
name: "Top Ten",
url: "static/partials/top10.html"
}
]
}
$scope.template = $scope.menuItems.left[0];
});
This is my directive
angular
.module('simApp')
.directive("menuRepeaterDirective", function() {
return function(scope, element, attrs){
var self = this;
this.scope = scope;
this.element = element;
var $ele = $(element);
$ele.find("span").fastClick(function(ev){
//angular specific
var index = $(ev.currentTarget).parent().index();
self.scope.template = self.scope.menuItems.left[index];
//end angular specific
....some other Jquery stuff
});
}
});
Try:
self.scope.$apply(function(){ //Use $apply to let angular aware of the changes.
var index = $(ev.currentTarget).parent().index();
self.scope.$parent.template = self.scope.menuItems.left[index]; //accessing self.scope.$parent instead of self.scope
});
DEMO
Explanation why we have to access self.scope.$parent:
self.scope is the scope of the current item generated by ng-repeat while your ng-include is binding to menuCtrl's scope. self.scope.$parent is menuCtrl's scope in this case.

The "with" binding of KnockoutJS in AngularJS?

I have just switched from KnockoutJS to AngularJS and I am not able to find the KnockoutJS's "with" data-bind in AngularJS.
Here is the piece of code in KnockoutJS. The "with" binding creates a new binding context, so that descendant elements are bound in the context of a specified object.
<h1 data-bind="text: city"> </h1>
<p data-bind="with: coords">
Latitude: <span data-bind="text: latitude"> </span>,
Longitude: <span data-bind="text: longitude"> </span>
</p>
<script type="text/javascript">
ko.applyBindings({
city: "London",
coords: {
latitude: 51.5001524,
longitude: -0.1262362
}
});
</script>
Does AngularJS have anything like context?
Nothing like with that I know of.. this is the best I could do:
<h1>{{city}}</h1>
<p ng-repeat="c in [coords.or.possibly.deeper.in.tree]">
Latitude: {{c.latitude}},
Longitude: {{c.longitude}}
</p>
Create a custom directive that loops through the source object and creates corresponding properties on the directive's scope that are getter/setter references to the source object.
Check out this plunker.
directive module:
angular.module('koWith', [])
.directive('koWith', function () {
return {
controller: function ($scope, $attrs) {
var withObj = $scope.$parent[$attrs.ngWith];
function getter(prop) {
return this[prop];
}
function setter(val, prop) {
this[prop] = val;
}
for (var prop in withObj) {
if (withObj.hasOwnProperty(prop)) {
Object.defineProperty($scope, prop, {
enumerable: true,
configurable: true,
get: getter.bind(withObj, prop),
set: setter.bind(withObj, prop)
});
}
}
},
restrict: 'A',
scope: true
};
});
app module:
angular.module('myApp', [])
.controller('myController', function ($scope) {
$scope.customer = {
name: "Timmeh",
address: {
address1: "12 S Street",
address2: "",
city: "South Park",
state: "CO",
zipCode: "80440"
}
};
});
html:
<div ko-with="customer">
<h2>{{name}}</h2>
<div ko-with="address">
{{address1}}<br>
{{address2}}<br>
{{city}}, {{state}} {{zipCode}}
</div>
</div>
Explanation
In KnockoutJS, bindings keep the bindingContext and data separated so creating the with binding is trivial since it only needs to create a new child bindingContext from the current one and use the with object as its data value.
In AngularJS, a directive's scope is basically the bindingContext and data object rolled into one. When a new scope is created, in order to get the with-like behavior, the properties of the with object have to be referenced onto the newly created scope object.
Here is solution based on #nwayve, but it supports expressions in koWith and also it watches for updating property/expression specified in koWith:
.directive('koWith', function () {
return {
restrict: 'A',
scope: true,
controller: function ($scope, $attrs, $parse) {
var ScopePropertyDesc = function (prop) {
var self = this;
self.propName = prop;
self.parsed = $parse(prop);
self.enumerable = true;
self.configurable = true;
//self.writable = true;
self.get = function () {
var withObj = $scope.$parent[$attrs.koWith];
var res = self.parsed($scope.$parent, withObj);
return res;
};
self.set = function (newValue) {
var withObj = $scope.$parent[$attrs.koWith];
self.parsed.assign(withObj, newValue);
};
};
$scope.$parent.$watch($attrs.koWith, function (oldVal, newVal) {
var withObj = $scope.$parent[$attrs.koWith];
(function copyPropertiesToScope(withObj) {
for (var prop in withObj) {
if (withObj.hasOwnProperty(prop)) {
Object.defineProperty($scope, prop, new ScopePropertyDesc(prop));
}
};
})(withObj);
});
}
};
});

Resources