Adding class to an element on its click event - angularjs

I am new to Angular Js. I need to add a class to an element on its click event. I tried the following code. But it is not working.
<html>
<head>
<style>
.active{color:red;}
</style>
<script src="js/lib/jquery.min.js"></script>
<script src="js/lib/angular.min.js"></script>
</head>
<body ng-app="MyApp" ng-controller="MyController">
<div ng-repeat="data in datas">
<p ng-click='selectMe()'>{{data.name}}</p>
</div>
<script>
var app = angular.module('MyApp',[]);
app.controller('MyController',function($scope){
$scope.datas = [{name:"first"},{name:"second"}];
$scope.selectMe = function (){
$(this).addClass('active');
}
});
</script>
</body>
</html>
What is the problem in this code? Is it necessary to use ng-class ? How to do it?

You can pass $event to click
<p ng-click='selectMe($event)'>{{data.name}}</p>
//code:
$scope.selectMe = function (event){
$(event.target).addClass('active');
}

The Angular way (MVVM actually) to do this is to update the model through the click event and then paint according to the model, e.g.:
app.controller('MyController',function($scope){
$scope.datas = [{name:"first", selected:false},{name:"second",selected:false}];
$scope.selectMe = function(data) {
var i;
for( i=0; i < $scope.datas.length; i++ ) {
$scope.datas[i].selected = false;
}
data.selected = true;
};
}
And the HTML:
<div ng-repeat="data in datas">
<p ng-click='selectMe(data)' ng-class="{selected: data.selected}>{{data.name}}</p>
</div>

I know this question already has an answer, but I was shocked when I realized that the chosen best answer (which I had already implemented in some of my code) doesn't align with the AngularJS documentation.
According to AngularJS Documentation:
Do not use controllers to:
Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. Angular has databinding for most cases and directives to encapsulate manual DOM manipulation.
huston007's answer works great, however, it does not follow this recommendation.
With this as your data input:
$scope.peeps = {
'0': {
'id': 0,
'first_name': 'Tony',
'last_name': 'Martin'
},
'1': {
'id': 1,
'first_name': 'Gerald',
'last_name': 'Johanssen'
},
'2': {
'id': 2,
'first_name': 'Bobby',
'last_name': 'Talksalot'
}
};
And this your html:
<ul>
<li ng-repeat="peep in peeps"
ng-click="addOrRemoveClassFromMe(peep.id)"
ng-class="{selected: selectedPeeps[peep.id]}">
{{peep.first_name}} {{peep.last_name}}
</li>
</ul>
My suggested solution uses an array of objects with the person's id as the key and a booleon as the value. This is linked to in the DOM through the ngClass directive.
//-- create the selected peeps array
$scope.selectedPeeps = {};
//-- add the id to an array with a true-ey value
$scope.addOrRemoveClassFromMe = function(id) {
//-- Set selected peeps as true/false
if($scope.selectedPeeps[id]) {
$scope.selectedPeeps[id] = false;
} else {
$scope.selectedPeeps[id] = true;
}
};
You can check out my codepen here.
I also have another version that removes all of this logic from the controller, leaving only the variable definition, but I thought that might be out of the scope of this question. (codepen.io/joshwhatk/pen/bwmid)
Hope this helps.

I know this is an old question but just came across it and think there's a simpler answer to any of the ones given here:
<div ng-repeat="data in datas">
<p ng-click="active=!active"
ng-class="{'active': active}">{{data.name}}</p>
</div>
No code needed in the controller other than to create your datas array. This works because the active variable is created within the child scope of each div created by the ng-repeat rather than in the controller's scope.
I've created an updated version of #joshwhatk's codepen here

Related

(newbie) Get data from array of objects using ng-repeat and for loop

I have to access some objects inside an array:
.success(function(data) {
$scope.activities = data.result[0].attributes
});
And I can access this particular index, as expected, inside my view with
<div ng-repeat="x in activities">
<p>{{x.name}}: {{x.value}}</p>
</div>
Now obviously, this only returns the first object in the array, at index 0. (Right?). So I figure I have to somehow loop the function..
.success(function(data) {
for(var i = 0; i < data.result.length; i++) {
$scope.activities = data.result[i].attributes;
}
});
As far as I can tell, the actual for loop is working.. but I need help in the next step exposing this to my view.
Here is my plunker: https://plnkr.co/edit/2ZukY3Oq8vYvghfCruHx?p=preview
(although the data is not available, I have added what the response looks like in the comments)
Well, since the JSON data you pasted in plnkr is invalid, I don't know what attributes means, if it's just an object or it's an array of objects, anyway I made it as a single object.
EDIT
Since now I know attributes is an Array of objects, you can achieve what you want using special repeats.
Here's a snippet working:
(function() {
"use strict";
angular.module('app', [])
.controller('mainCtrl', function($scope) {
$scope.getLeadActivities = function() {
var url = 'xxxxxxxx.mktorest.com';
var endpoint = '/rest/v1/activities.json';
var activityTypeIds = '46';
var access_token = $scope.access_token;
var leadIds = $scope.leadId;
var nextPageToken = $scope.pagingToken;
// This is your data from API
var data = {
"requestId":"14213#155c8de2578",
"success":true,
"nextPageToken":"EOXAPD2V5UOJ5B3S5GGP7NUCX6UI6BFXCWHPJXF245PN2QTGMF3Q====",
"moreResult":false,
"result":[
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName",
"value":"myDataValue"
}
]
},
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName2",
"value":"myDataValue2"
}
]
},
{
"id":75843491,
"leadId":5334578,
"activityDate":"2016-07-06T06:45:11Z",
"activityTypeId":46,
"primaryAttributeValue":"Web",
"attributes":[
{
"name":"myDataName3",
"value":"myDataValue3"
}
]
}
]
};
// You'll put it inside the THEN method of your $http.get
$scope.activities = data.result;
}
});
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
<button ng-click="getLeadActivities()">Get Lead Activities</button>
<div ng-repeat-start="activity in activities"></div>
<ul ng-repeat-end ng-repeat="attr in activity.attributes">
<li ng-bind="attr.name + ': ' + attr.value"></li>
</ul>
</body>
</html>
As per my understanding of your issue, you can solve it by using two ng-repeat clauses.
Your controller code,
.success(function(data) {
$scope.ativitiesArray = data.result;
});
Your html code,
<div ng-repeat="activities in activitiesArray track by $index">
<div ng-repeat="x in activities.attributes track by $index">
<p>{{x.name}}: {{x.value}}</p>
</div>
</div>
I've added track by $index phrase to ng-repeat as it will increase performance. Also, as per suggestions in the comments, please avoid using .success(). It is recommended to use .then().
Hope this solves the issue.

$scope.$watch with filtered scopes as output

This question is based in the answer provided here
I want to output the result from a lodash function applied to a scope ($scope.names) when another scope ($scope.switch) is set to true, and to output the result of that lodash function applied to a filtered scope when $scope.switch is set to false.
So my ng-repeat would be like this:
<ul>
<li ng-repeat="things in (filtered=(names | filter:filterType))">{{things}}</li>
</ul>
I use two ng-click to change the state of $scope.switch and to apply/unapply the filter:
<a ng-click="switch = false; filterType=''">unswitch - No Filters</a><br><br>
<a ng-click="switch = true; filterType={name:'!Jimmy'}">switch - Not Jimmy</a>
And I used this code in order to watch the changes in $scope.switch:
$scope.$watch('switch', function() {
$scope.thingScope =$scope.switch ? _.map($scope.names,"styles") : _.map($scope.filtered,"styles");
});
Here is the working plunkr with all the code (it's much easier to notice the problem here)
The problem is that the output of $scope.thingScope doesn't correspond to the values showed in the ng-repeat (right now every time I click, it shows the values of the previous one, that is, it goes one step behind). Thanks in advance!
Ok, I found few problems.
0 - You are working with a generated list element (Filtered. which is the result of apply the filter over names). When the switch change, the generated list is not finished. So, you will not find the elements.
1 - You have a lot of elements Trying to work over the same (the Swtich/Filter over names).
2 - You are showing using: $scope.thingScope = $scope.switch ? _.map($scope.names,"styles") : _.map($scope.filtered,"styles"); So, you are showing $scope.names when $scope.switch == true. So wrong conditional.
3 - You are applying first the change of the switch and then the filterType in the ng-click.
So, you can do 2 things.
Work with duplicated filters in 2 places (Not recommended.)
The ng-repeat work with the processed-list. And work all the filters in the controller. As #shaunhusain did it.
P.S. I think it could be cleaner with an active to filter. And not by Name, check this Plnkr: http://plnkr.co/edit/4uJ5AxP9xntgBe1hQrBW?p=preview
You can select by Name (only clicking) but you must click again in update filters to apply changes.
Think your logic in the view is getting to the slightly heavy and not making sense due to all the watching that goes on with things bound in the view... this is easier to solve if you instead do it procedurally.
http://plnkr.co/edit/ZExywkLtDxuT82Ud6gYl?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
</head>
<body>
<div ng-controller="myCtrl" ng-app="myApp">
0. <b>Switch</b>: {{switch}} <br/><br/>
1. <b>Output</b>: {{thingScope}} <br/><br/>
2. <b>Filtered List</b>:
<ul>
<li ng-repeat="things in filtered">{{things}}</li>
</ul>
<a ng-click="changeSwitch(false, '')">unswitch - No Filters</a><br><br>
<a ng-click="changeSwitch(true, {name:'!Jimmy'})">switch - Not Jimmy</a>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $filter) {
$scope.switch = false;
// set the default sort type
$scope.filterType = '';
var names=[{name:"Johny", styles:["one","two","three"]},{name:"Jimmy", styles:["two","three","four"]},{name:"Kevin", styles:["three","four","five"]}];
// Option B:
function filterData(searchObj){
$scope.filtered = $filter('filter')(names, searchObj);
};
filterData();
$scope.changeSwitch = function(newVal, filterType){
$scope.switch = newVal;
filterData(filterType);
$scope.thingScope = !$scope.switch ? _.map(names,"styles") : _.map($scope.filtered,"styles");
}
});
</script>
</body>
</html>

Something like "with variable" in AngularJS?

Sample controller data:
$scope.items = [{ id: 1, name: 'First'}, { id: 2, name: 'Second'}];
Is there something in angular to make the following code work like "with variable"?
<ul>
<li data-ng-repeat="items">{{id}} {{name}}</li>
</ul>
Instead of:
<ul>
<li data-ng-repeat="i in items">{{i.id}} {{i.name}}</li>
</ul>
Please feel free to make a more understandable title/question.
Referring to Angular ngRepeat document, currently only followings expressions are supported.
variable in expression
(key, value) in expression
variable in expression track by tracking_expression
variable in expression as alias_expression
This means, you can't simply use ng-repeat="items" to iterate the collection.
BTW, ng-repeat will create a separate scope for each element and bind variable or (key, value) to the newly created scope. So "with variable" you refer to is not Angular built-in. You need to create a customized directive for this functionality.
My preferred answer would be "don't do this" but failing that, and because it's interesting, here's a proof of concept, assisted by this question and mostly adapted from this blog post:
app.directive('myRepeat', function(){
return {
transclude : 'element',
compile : function(element, attrs, linker){
return function($scope, $element, $attr){
var collectionExpr = attrs.myRepeat;
var parent = $element.parent();
var elements = [];
// $watchCollection is called everytime the collection is modified
$scope.$watchCollection(collectionExpr, function(collection) {
var i, block, childScope;
// check if elements have already been rendered
if(elements.length > 0){
// if so remove them from DOM, and destroy their scope
for (i = 0; i < elements.length; i++) {
elements[i].el.remove();
elements[i].scope.$destroy();
};
elements = [];
}
for (i = 0; i < collection.length; i++) {
// create a new scope for every element in the collection.
childScope = $scope.$new();
// ***
// This is the bit that makes it behave like a `with`
// statement -- we assign the item's attributes to the
// child scope one by one, rather than simply adding
// the item itself.
angular.forEach(collection[i], function(v, k) {
childScope[k] = v;
});
// ***
linker(childScope, function(clone){
// clone the transcluded element, passing in the new scope.
parent.append(clone); // add to DOM
block = {};
block.el = clone;
block.scope = childScope;
elements.push(block);
});
};
});
}
}
}
});
And then this will do what you want:
app.controller("myController", function($scope, $http) {
$scope.items = [
{a: 123, b: 234},
{a: 321, b: 432}
];
});
With the HTML structure you want:
<div ng-controller="myController">
<ul>
<li my-repeat="items">
{{ a }} {{ b }}
</li>
</ul>
</div>
Notice that given the attributes are copied into the child scopes, rather than referenced, if changes are made to the view, they won't affect the model (ie. the parent items list), severely limiting the usefulness of this directive. You could hack around this with an extra scope.$watch but it'd almost certainly be less fuss to use ng-repeat as it's normally used.
I can't see why other users are telling you that what you want has to be done via a new directive. This is a working snippet.
angular.module("Snippet",[]).controller("List",["$scope",function($scope){
$scope.items = [{ id: 1, name: 'First'}, { id: 2, name: 'Second'}];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="Snippet" ng-controller="List as list">
<ul>
<!-- Iterating the array -->
<li ng-repeat="item in items">
<!-- Iterating each object of the array -->
<span ng-repeat="(key,value) in item">{{value}} </span>
</li>
</ul>
</body>
Simply, you need to iterate the elements of the array via ng-repeat, then you can do what you want with the object item retrieved. If you want to show its values, for example, as it seems for your question, then a new ng-repeat gets the job done.

Angular model doesn't scope to variable within child DOM element?

I have this markup:
<div data-ng-model="currentUser.attributes">
<div>{{username}}</div>
</div>
And this is a stripped down version of my controller:
$scope.username = "Alice";
$scope.currentUser = {
attributes: {
username: "Bob"
}
};
I want Bob to display, but instead, I am getting Alice. It works just fine if I use this:
{{currentUser.attributes.username}}
But I don't want to have to scope down to this variable's properties every time I want to access something. How can I get the element to exist within the scope of currentUser.attributes?
While I don't think you should really do this, it is what you're asking for. You can essentially mimic with by using ng-repeat on an array that you populate with the relevant object. For example:
<div ng-repeat="user in [currentUser.attributes]">
{{ user.username }}
</div>
Working plunker: http://plnkr.co/edit/svwYEeWMQXjuAnLkr9Vz?p=preview
Other possible solutions would be to have a service or controller that has functions to get the attributes and return them, cleaning up the syntax of your HTML and making it easier to change backend stuff without breaking your frontend. Your choice.
Edit: I noticed you actually expect to be able to do {{ username }} and get the relevant info, if that's really what you want then I suggest my second proposal. Create functions that return the relevant info.
<div>
{{ getCurrentUserName() }}
</div>
$scope.getCurrentUserName = function() {
return $scope.currentUser.attributes.username;
};
Your call, take it or leave it.
If you want Bob just do the the following in your HTML.
<div>{{current user}}</div>//IGNORE THIS
<div>{{currentUser.attributes.username}}</div>//UPDATED CORRECTED
UPDATED based on clarification.
So in Knockout you do this
<p data-bind="with: currentUser.attributes">
<div data-bind="text: userName></div>
<div data-bind="text: login></div>
<div data-bind="text: bhalBlah></div>
<div data-bind="text: yaddaYadda></div>
</p>
<script type="text/javascript">
ko.applyBindings({
currentUser: {
attributes: {
userName : 'Bob',
login : 't#e',
blahBlah : 'ttttt',
yaddaYadda: 'x'
}
}
});
</script>
Same thing in AngularJS would be
<p ng-controller="myCtrl">
<div>{{currentUser.attributes.userName}}</div>
<div>{{currentUser.attributes.login}}</div>
<div>{{currentUser.attributes.blahBlah}}</div>
<div>{{currentUser.attributes.yaddaYadda}}</div>
</p>
<script type="text/javascript">
angular.module('myApp',[]).controller('myCtrl',function($scope){
$scope = {
currentUser: {
attributes: {
userName : 'Bob',
login : 't#e',
blahBlah : 'ttttt',
yaddaYadda: 'x'
}
};
});
</script>
In this the question is how to avoid how not to repeat the part the full property paths between ** as shown below in angular.
**currentUser.attributes.**userName
**currentUser.attributes.**login
**currentUser.attributes.**blahBlah
**currentUser.attributes.**yaddaYadda
Here is one way see plnkr using ng-init which reduces 'currentUser.attributes' to just 'attr'.
With just attr.<properties> repeated
{{attr.userName}}
{{attr.login}}
{{attr.blahBlah}}
{{attr.yaddaYadda}}
Another way is you restructure your object and flatten it on the $scope.
This is not recommended because now you are putting primitives on to the $scope and are widening the scope with $scope.userName = currentUser.attributes.username. Also your 'repetitive' code is still there just in the Javascript.
In lieu of ng-init
ng-init="attr = currentUser.attributes"
You could also do this in controller
$scope.attr = currentUser.attributes;
This post really got me thinking. I had a theory on how to accomplish this using a directive.
Came up with a proof of concept on plnkr: http://embed.plnkr.co/OJDhpJ1maEdSoPvlbiRA/
If I understand correctly, you want to only display the properties within a given block of your struct.
Given the following struct:
$scope.currentUser = {
attributes: {
username: 'Batman',
age: '99',
address: {
street: 'Bat Cave'
}
}
};
You want to scope things down with something like:
<div scope-with="currentUser.attributes">
Username: {{username}}<br />
Age: {{age}}
<div scope-with="address">
Street: {{street}}
</div>
</div>
Directive:
angular.module('mymodule', [])
.directive('scopeWith', function($interpolate){
return {
restrict: 'A',
scope: {
scopeWith: '='
},
transclude: 'element',
compile: function(tElement, tAttrs, linker) {
return function( scope, element, attr) {
var childScope,
parent = element.parent(),
withBlock = null
;
scope.$watch('scopeWith', function(val){
childScope = scope.$new();
angular.forEach(val, function(val, prop){
childScope[prop] = val;
});
if(withBlock) {
withBlock.el.remove();
withBlock.scope.$destroy();
}
linker(childScope, function(clone){
withBlock = {};
parent.append(clone);
withBlock.el = clone;
withBlock.scope = childScope;
});
}, true);
};
}
};
Use {{currentUser.username}} to show Bob.
The ng-model on the div is irrelevant as it only applies to input elements.

Can I use one ng-app inside another one in AngularJS

I have two ng-app
like ;
<div ng-app="app1" >
somexpression
<div ng-app="app2">
some more expression
</div>
</div>
is there any way to make it work?
when I make a nested ng-app it doesn't work
I know that I can use two different controller but I don't want to use two controllers
---- EDIT -----
The thing is;
angular.module('AppName', [
'angular-carousel'
])
SO I need somehow to change this ng-app to directive
From the AngularJS document, the answer is no
http://docs.angularjs.org/api/ng/directive/ngApp
AngularJS applications cannot be nested within each other.
And if not nested, then it's OK, someone already asked this question, refer here:AngularJS Multiple ng-app within a page
and the AnguarJS document
http://docs.angularjs.org/api/ng/directive/ngApp
Only one AngularJS application can be auto-bootstrapped per HTML document. The first ngApp found in the document will be used to define the root element to auto-bootstrap as an application. To run multiple applications in an HTML document you must manually bootstrap them using angular.bootstrap instead.
I found one tricky solution for this problem. The idea is that the "host" application have to somehow jump over the root element of nested application. I used directive for this:
angular.module("ng").directive("ngIsolateApp", function() {
return {
"scope" : {},
"restrict" : "AEC",
"compile" : function(element, attrs) {
// removing body
var html = element.html();
element.html('');
return function(scope, element) {
// destroy scope
scope.$destroy();
// async
setTimeout(function() {
// prepare root element for new app
var newRoot = document.createElement("div");
newRoot.innerHTML = html;
// bootstrap module
angular.bootstrap(newRoot, [attrs["ngIsolateApp"]]);
// add it to page
element.append(newRoot);
});
}
}
}
});
Example simple app:
// module definition
angular.module("testMod1",[])
.service("moduleService", function ModuleService() {
this.counter = 0;
this.getCounter = function() {
return this.counter;
};
this.incCounter = function() {
this.counter += 1;
}
})
.controller("ModuleCtrl", function(moduleService) {
this.getValue = function() {
return moduleService.getCounter();
};
this.incValue = function() {
moduleService.incCounter();
};
});
Now in the markup we can use the ng-isolate-app:
<!-- App instance 1 -->
<body ng-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
<!-- App instance 2 -->
<div ng-isolate-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
<!-- App instance 3 -->
<div ng-isolate-app="testMod1">
<div ng-controller="ModuleCtrl as ctrl">
{{ctrl.getValue()}}
<button ng-click="ctrl.incValue()">Click</button>
</div>
</div>
</div>
</div>
</div>
</body>
Working example on plnkr
This works in simple cases, I do not know how this will work on complex applications.
You can't use one ng-app inside another one in angularjs.
because AngularJS applications cannot be nested within each other.
https://docs.angularjs.org/api/ng/directive/ngApp

Resources