There's this template that I call multiple times on the same page:
<div ng-controller="templateController">
<div class="source">
<div ng-repeat="item in info">
<div class="content" data-value="{{item.ID}}">{{item.name}}</div>
</div>
</div>
<br style="clear:both" />
<div class="receiver"></div>
</div>
and then I want to select all the elements with class="content" within each template scope in order to manipulate them.
How can I achieve this using JS?
EDIT :
Plunker
In this planker the console.log should print "1" twice and its printing "1" and then "2" when the template loads the second time
After more explanation here is a working example:
https://plnkr.co/edit/n5GOd6MDLyvG4ZAsuLvf?p=preview
The main idea is creating 2 lists and iterating over both and just moving data around between them on click.
angular.module("demo", []);
angular
.module("demo")
.controller("demoController", ["$scope", function($scope) {
}]);
angular
.module("demo")
.controller("templateController", ["$scope", "$timeout", function($scope, $timeout) {
$scope.sourceList = [
{
name: "john",
ID: 1
},
{
name: "Edward",
ID: 0
},
{
name: "Carl",
ID: 2
}
];
$scope.receiverList = [
{
name: "Bob",
ID: 1
}
];
$scope.moveToReceiver = function(item){
$scope.receiverList.push(item);
$.each($scope.sourceList, function(i){
if($scope.sourceList[i].name == item.name){
$scope.sourceList.splice(i, 1);
return false;
}
});
}
}]);
Most of the time you do not want to do DOM manipulation in Angularjs and instead hook into events with your controller. If you have to do DOM manipulation in AngularJS you would use directives
Docs on Creating a Directive that Manipulates the DOM
You could then use your link function to grab the children of your directive's element
function link(scope, element, attrs) {
var content = angular.element(element[0].querySelector('.content'));
}
https://stackoverflow.com/a/17329630/2033671
Related
I have a need to show a list of check boxes on my site listing products and other items. The checklist-model attribute directive works well for this because I can bind it to a list of items that relates to the items selected.
All this works fine when I simply use this code in my angular controller. However, I have several list boxes that need to be displayed the same way with the same "select all" and "select none" buttons for each list. I don't want to repeat this code and layout so I've created my own directive for the entire list.
The problem is when I use my own directive it doesn't bind correctly back to my data, the select all only works once, and the select none doesn't work at all.
I suspect it has something to do with how I'm passing the scope around, and the two directives are not working well together.
Why does this not work inside a directive?
Here is a jsfiddle: https://jsfiddle.net/fande455/m9qhnr9c/7/
HTML
<section ng-app="myApp" ng-controller="couponEditController">
<script type="text/ng-template" id="checkboxlist.template.html">
<div>
<div class="form-input form-list">
<label ng-repeat="item in valuelist | orderBy:value">
<input type="checkbox" checklist-model="model" checklist-value="item" /> {{item[value]}}
<br />
</label>
</div>
<button class="btn btn-default" style="float:left; margin-bottom: 5px;margin-left: 10px;margin-right:10px" ng-click="selectNone()">Select None</button>
<button class="btn btn-default" style="float:left; margin-bottom: 5px;" ng-click="selectAll()">Select All</button>
<div class="cleared"></div>
</div>
</script>
<div>
<checkboxlist model="coupon.Products" value="Name" valuelist="productsList"></checkboxlist>
</div>
</section>
JS
var myApp = angular.module('myApp', ['checklist-model']);
myApp.directive('checkboxlist', [function() {
return {
restrict: 'E',
templateUrl: 'checkboxlist.template.html',
controller: 'checkboxlistController',
scope: {
model: "=",
value: "#",
valuelist: "="
},
require: '^checklist-model'
}
}]);
myApp.controller('checkboxlistController', ['$scope', function($scope) {
$scope.selectAll = function() {
$scope.model = angular.copy($scope.valuelist);
};
$scope.selectNone = function() {
$scope.model = [];
};
}]);
myApp.controller('couponEditController', ['$scope', function($scope) {
$scope.coupon =
{"Id": 1,
"Name": "My Coupon",
"Products": []
}
;
$scope.productsList = [{
"Id": 1,
"Name": "Product 1"
}, {
"Id": 2,
"Name": "Product 2"
}];
}]);
From documentation:
Instead of doing checklistModelList = [] it is better to do
checklistModelList.splice(0, checklistModelList.length)
In your code you should do
$scope.selectAll = function() {
$scope.selectNone();
$scope.model.push.apply($scope.model, $scope.valuelist);
};
$scope.selectNone = function() {
$scope.model.length = 0;
};
Here's the updated fiddle: https://jsfiddle.net/m9qhnr9c/9/
The idea is not to replace the array with a new one, but modify only its elements.
I am trying to make a dynamic framework with angularjs. I load sections with a webapi, that have data about the directive that it uses and the data that should be used in that directive. The data that I send can look like this:
[
{
id: "section1",
directive: "<my-directive></my-directive>",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "<my-other></my-other>",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
]
When I load this data, I convert the directives in to element with $compile.
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
}
So I can actually load this data in the view, like this:
<div ng-repeat="section in sections">
{{section.directive}}
</div>
First of all, this doesn't show up in my view, so how do I fix this?
Then the second issue I have. When I actually get this directive loaded into the view, how will I be able to access the data that should be used in this directive? I do have an id added to the sections.This is what I tried:
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
var parent = angular.element('#sectionsparent'); //The parent element has this id
parent.append(value.directive);
}
This way the section elements show up, but I cannot access the data that should be loaded inside the directive.
Thank you for your help in advance, let me know if you need more information.
EDIT:
When the directive is eventually loaded, I want to be able to access the data that belongs to that section. So if we take first section in the sample data, I want to be able to do the following in the template of the directive:
<!-- This file is myDirectiveTemplate.hmtl -->
<div id="{{id}}>
<h1>{{title}}</h1>
<p>{{text}}</p>
</div>
I don't care if I have to access these properties through a viewmodel object, so it would be {{vm.id}} instead of {{id}}. But I prefer to not have any function calls inside my template to actually get data.
Alright. There may be another way to accomplish this, or perhaps using includes instead of directives, but here's one way at least.
Taking your example code, you can follow your second route with $compile and append but you also need to pass an html-attribute for the isolate scope's content and bind it with a new $scope with the section added. (You also need to wrap in a $timeout so querying the DOM happens after it's initially rendered).
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope, $compile, $timeout) {
$scope.sections = [
{
id: "section1",
directive: "my-directive",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "my-other",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
];
// Need to timeout so rendering occurs and we can query the DOM.
$timeout(() => {
angular.forEach($scope.sections, (section) => {
let newScope = $scope.$new();
newScope.content = section.content;
let dir = section.directive;
let compiled = $compile(`<${dir} content="content"></${dir}>`)(newScope);
let parent = angular.element(document.querySelector('#' + section.id));
parent.append(compiled);
});
});
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<p>{{content.text}}</p>
</div>`,
};
});
app.directive('myOther', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<ul>
<li ng-repeat="item in content.list">{{item}}</li>
</ul>
</div>`,
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="AppCtrl">
<div ng-repeat="section in sections" id="{{section.id}}"></div>
</div>
Brief intro to my problem
I have a directive that dynamically shows a list of checkboxes. It has a parameter called options that should be an array like the following, in order to show the list of checkboxes correctly. For example:
var options = [
{
id: 1,
label: 'option #1'
},
{
id: 2,
label: 'option #2'
},
{
id: 3,
label: 'option #3'
}
];
So, by passing this array to my directive, a group of three checkboxes would be shown.
Also, the directive requires ngModel that will have the result of checking/unchecking the checkboxes (this object is always passed initialized). For example:
var result = {
"1": true,
"2": true,
"3": false
};
This case means that the first and second checkboxes (options with id=1 and id=2) are checked and the third (option with id=3) is unchecked.
My directive
template.html
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]">
{{ ::option.label }}
</label>
</div>
</div>
directive.js
angular
.module('myApp')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
};
};
What I want to achieve
Wherever I use my directive, I want to be able to trigger a function whenever the ngModel changes. Of course, I would like to achieve this using ngChange. So far I have the following:
<my-directive
name="myName"
options="ctrlVM.options"
ng-model="ctrlVM.result"
ng-change="ctrlVM.selectionChanged()">
</my-directive>
but the .selectionChanged() function is not triggered whenever the model changes. Anyone has any idea why this is not working as I am expecting it to work?
First thing first, please try to provide jsfiddle, codepen etc code snippet link so that it will be easy for others to answer your question.
The problem in your case is that you are never updating the ctrlVM.result object as you are passing the object's reference and that reference never change even if you manually update the model by calling ngModel.$setViewValue().
To solve the problem, just update the model by manually calling ngModel.$setViewValue() and pass in the new Object so that the reference changes and that will trigger the ngChange directives logic.
I've added the logic to do that and it will successfully trigger the change. Look at the code below:
angular
.module('myApp', [])
.directive('myDirective', myDirective)
.controller('MyController', function($timeout) {
var vm = this;
vm.options = [{
id: 1,
label: 'option #1'
}, {
id: 2,
label: 'option #2'
}, {
id: 3,
label: 'option #3'
}];
vm.result = {
"1": true,
"2": true,
"3": false
};
vm.selectionChanged = function() {
vm.isChanged = true;
$timeout(function() {
vm.isChanged = false;
}, 500)
}
});
function myDirective() {
var directive = {
templateUrl: 'template.html',
restrict: 'E',
require: 'ngModel',
scope: {
options: '='
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModel) {
scope.result;
ngModel.$render = setResult;
function setResult() {
scope.result = ngModel.$viewValue;
};
scope.updateValue = function(val) {
ngModel.$setViewValue(Object.assign({}, val))
}
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<script type="text/ng-template" id="template.html">
<div ng-repeat="option in options track by $index">
<div class="checkbox">
<label>
<input type="checkbox"
ng-model="result[option.id]" ng-click="updateValue(result)">
{{ ::option.label }}
</label>
</div>
</div>
</script>
<div ng-controller="MyController as ctrlVM">
<my-directive name="myName" options="ctrlVM.options" ng-model="ctrlVM.result" ng-change="ctrlVM.selectionChanged()">
</my-directive>
<div> Data: {{ctrlVM.result}} </div>
<div> isChanged: {{ctrlVM.isChanged}} </div>
</div>
</div>
#Gaurav correctly identified the problem (ng-change is never called because the object reference does not change). Here is a simpler solution that doesn't require manually cloning into the controller's model:
Add a binding for the ng-change attribute:
scope: {
options: '=',
ngChange: '&' // Add this, creates binding to `ctrlVM.selectionChanged()`
}
Add an ng-change to your checkbox template:
<input type="checkbox"
ng-model="result[option.id]" ng-change="ngChange()">
Now, when any checkbox changes it will automatically call the outer ng-change function without the intermediate step of cloning into the model.
I need to access different arrays based on the users choice and then run through the array with a ng-repeat.
Controller:
$scope.allbooks={
book1:{price:"3.00",type:"non-fiction",chapters:book1chapters},
book2:{price:"4.00",type:"fiction",chapters:book2chapters},
};
$scope.pick = function(selectedBook) {
$rootScope.choice = selectedBook;
}
$scope.book1chapters=[
{title:"it begins"},
{title:"another one"}
];
$scope.book2chapters=[
{title:"hello"},
{title:"calling from the otherside"}
];
HTML:
<button ng-click="pick(allbooks.book1)">Book 1</button>
<button ng-click="pick(allbooks.book2)">Book 2</button>
<div ng-repeat:"m in choice.chapters"><-----this does not work
Chapter: {{m.title}}
</div>
This is a very simplified example just to make it easier to look at :) I don't know how t reference another array from inside an array. Thanks
It seems you did not define book1chapters and book1chapters for collection allbooks, instead you defined them in $scope which is not correct. Also change $rootScope to $scope since rootScope is not injected. The following code is working:
var book1chapters = [{
title: "it begins"
}, {
title: "another one"
}];
var book2chapters = [{
title: "hello"
}, {
title: "calling from the otherside"
}];
$scope.allbooks = {
book1: {
price: "3.00",
type: "non-fiction",
chapters: book1chapters
},
book2: {
price: "4.00",
type: "fiction",
chapters: book2chapters
},
};
$scope.pick = function(selectedBook) {
$scope.choice = selectedBook;
}
The code on plunker: http://plnkr.co/edit/83Ujp4R8BjIe39ROmp6n?p=preview
Short Answer
Basically you have incorrect ng-repeat syntax. It should have = instead of : before writing expression in front of ng-repeat directive like we do for value attribute
Markup
<div ng-repeat="m in choice.chapters"><-----this does not work
Chapter: {{m.title}}
</div>
Suggestions
You should not pollute $rootScope for sharing variables. For that you could create a shareable service which can share a data among-est various components of your app like controllers, directives, service, etc.
HTML
<button ng-click="sharableData.choice = 'book1'">Book 1</button>
<button ng-click="sharableData.choice = 'book2'">Book 2</button>
<div ng-repeat = "m in allbooks[sharableData.choice].chapters">
Chapter: {{m.title}}
</div>
Service
app.service('sharableData', function(){
var sharableData = this;
sharableData.sharedData = {
choice: undefined
};
});
Controller
app.controller('myCtrl', function($scope, sharableData){
//you other controller code
//add this additional line to expose service variable on html
$scope.sharableData = sharableData;
});
I have problem with updating view by using ng-repeat.
When click on text, it doesnt update but it overwrites below. (I want have panel with names(links) and show its description on view)
I have searched everything and couldnt find answer or something useful what would help me.
html:
<body>
<div ng-app="myApp">
<div ng-controller="myCtrl">
<ul>
<li><a ng-repeat="item in items" ng-click="getInfo(item)" > {{item.name}} <br> </a></li>
</ul>
</div>
<hr>
<div ng-controller="myInfo">
<div ng-repeat="info in item" >
<h3>Name: {{info.name}}</h3>
<p> ID: {{info._id}}</p>
<p> temp: {{info.temp}} </p>
</div>
</div>
</div>
</body>
js
var app = angular.module('myApp', [])
app.controller('myCtrl', function($scope, $http, shareDataService) {
$http.jsonp('data.json').success(function(data) {
$scope.items = data;
});
$scope.getInfo = function(item) {
shareDataService.addItem(item);
}
});
app.controller('myInfo', function( $scope, shareDataService ){
$scope.item = shareDataService.getItem();
});
app.service('shareDataService', function() {
var myItem = [];
var addItem = function(newObj) {
myItem.push(newObj);
}
var getItem = function(){
return myItem;
}
return {
addItem: addItem,
getItem: getItem
};
});
json
angular.callbacks._0([
{
"_id": 1,
"temp": "asdgdf",
"name": "name1"
},
{
"_id": 2,
"temp": "asdasdasd",
"name": "name2"
},
{
"_id": 3,
"temp": "asasdasdadgdf",
"name": "name3"
}
]);
Plunker: http://plnkr.co/edit/X65oH0yAkRnN8npKnFY2?p=preview
You have an error in your console. Just add track by to your ng-repeat:
<div ng-repeat="info in item track by $index">
ng-repeat needs a unique id to track the items in order to be able to update them. If you add the same item twice, ng-repeat sees the same item twice, ans loses its mind. Using $index (which is unique) resolves that issue.
Keep in mind that using $index is adequate here, but it's preferred to use a unique id from the object if you can.
EDIT:
If your issue is that you want to see only the one element you clicked on in your view, then the issue is that you are adding your item to an array, when you should just be setting a value in your service. And, obviously, no need of a ng-repeat in your view.
http://plnkr.co/edit/Wfg9KhCWKMDreTFtirhR?p=preview
JS:
app.controller('myCtrl', function($scope, $http, shareDataService) {
//[...]
$scope.getInfo = function(item) {
shareDataService.setItem(item);
}
});
app.controller('myInfo', function( $scope, shareDataService ){
$scope.$watch(function () {
return shareDataService.getItem();
}, function (value) {
$scope.info = value;
})
});
app.service('shareDataService', function() {
var myItem;
return {
setItem: function(newObj) {
myItem = newObj;
},
getItem: function(){
return myItem;
}
};
});
HTML:
<div ng-controller="myInfo" ng-show="info">
<h3>Name: {{info.name}}</h3>
<p> ID: {{info._id}}</p>
<p> temp: {{info.temp}} </p>
</div>
If you only want to display information of the item that you just have clicked, then we don't need the second ng-repeat (as jlowcs said).
We also don't have to defined myItem as a array, it's just unnecessary, I think.
Edit:
how embarrassing i am, my answer look exactly to same as jlowcs's. I guess I took to much time to figure out the answer.
One thing to add up:
Why do I need a $watch in myInfo controller?
Because at the first time, we use ng-repeat, this component do the watch part for us. Then when I remove ng-repeat, I need to watch for data changing by myself.