ng-click on img not working inside ng-repeat - angularjs

In my view I have a list of icons that when clicked, should call a function from the controller.
View
<div ng-controller="EditorController" class="main-div">
<aside>
<div ng-repeat="icon in EditorIcons">
<img ng-click="changeme()"
data-ng-src="{{icon.source}}"
alt="{{icon.name}}"/>
</div>
</aside>
</div>
Controller
app.controller('EditorController', function($scope) {
$scope.EditorIcons = [ ... ];
$scope.changeme = function() {
console.log("changing");
}
}
I've seen this question asked before, yet I still wasn't able to find out the problem here. What am I doing wrong?
UPDATE
I've found the problem. I had a z-index of -1 on the aside element

Assuming that EditorIcons is a collection in your controller, and changeme is a method inside of your controller, the you need t remove the $parent:
<div ng-repeat="icon in EditorIcons">
<img ng-click="changeme()"
ng-src="{{icon.source}}" alt="{{icon.name}}" />
</div>
Secondry you werte missing quotes " in your alt definition

Ok I just tried to make a fiddle and it works fine:
https://jsfiddle.net/pegla/8807dvrr/2/
<div ng-app>
<div ng-controller="SomeCtrl">
<aside>
<div ng-repeat="icon in EditorIcons">
<img ng-click="changeme()" data-ng-src="{{icon.source}}" alt="{{icon.name}}" />
</div>
</aside>
</div>
</div>
function SomeCtrl($scope) {
$scope.EditorIcons = [{
source: '',
name: 'icon-1'
}, {
source: '',
name: 'icon-2'
}];
$scope.changeme = function() {
console.log("changing");
}
};
So your problem has to be somewhere in declaration of controller or ng-app since code works, also check that data you have in editor icons is good.

Related

Controller doesn't get parent data

I'm doing a tutorial over angular 1.5 and I've gotten far into it and one of the sections seems broken concerning matching a current user to the author username. The class injects the User service and I think assumes I can inherit from a parent controller for the author but it comes up undefined. I tried injecting $scope then setting a variable to $scope.$parent.article (article is the object that has the author name in it) but this was still undefined. I checked the parent controller doing a console log on article and it does have the data that I am trying to get. Here is a link to my project if you want to look at the entire thing but I'll try to post just the relevant code below. https://github.com/RawleJuglal/flow_news_app/tree/front_end/src/js
Parent Controller (article.controller.js)
import marked from 'marked';
class ArticleCtrl {
constructor(article, $sce, $rootScope) {
'ngInject';
this.article = article;
console.log(this.article);
//THIS IS CONSOLE LOG
//{title: "Juglal For StackOverflow",
slug: "juglal-for-stackoverflow-ba400n",
body: "<p> Need the goods</p>",
createdAt: "2017-04-25T14:51:42.131Z",
updatedAt: "2017-04-25T14:51:42.131Z",
author:{
bio:"I'm a MEAN stack developer. But if I don't find a job in Oklahoma soon, I'll be learning C++/Sharp."
following:false
image:"https://media.licdn.com/mpr/mpr/shrinknp_200_200/p/6/000/1e9/0e2/3cd7175.jpg"
username:"RawleJuglal",....
}
// Update the title of this page
$rootScope.setPageTitle(this.article.title);
this.article.body = $sce.trustAsHtml(marked(this.article.body, { sanitize: true }));
}
}
export default ArticleCtrl;
Child Controller (article-actions.components.js)
class ArticleActionsCtrl {
constructor(Articles, User, $state) {
'ngInject';
this._Articles = Articles;
this._$state = $state;
//Code that causes the error because this.article.author.username is undefined
if (User.current) {
this.canModify = (User.current.username === this.article.author.username);
} else {
this.canModify = false;
}
}
}
let ArticleActions = {
bindings: {
article: '='
},
controller: ArticleActionsCtrl,
templateUrl: 'article/article-actions.html'
};
export default ArticleActions;
HTML(article.html) //Just in case this the problem
<div class="article-page">
<div class="banner">
<div class="container">
<h1 ng-bind="::$ctrl.article.title"></h1>
<article-actions article="$ctrl.article"></article-actions>
</div>
</div>
<div class="container page">
<div class="row article-content">
<div class="col-xs-12">
<div>
<div ng-bind-html="::$ctrl.article.body"></div>
</div>
<ul class="tag-list">
<li class="tag-default tag-pill tag-outline"
ng-repeat="tag in ::$ctrl.article.tagList">
{{ tag }}
</li>
</ul>
</div>
</div>
<hr />
<div class="article-actions">
<article-actions article="$ctrl.article"></article-actions>
</div>
<div class="row">
<div class="col-xs-12 col-md-8 offset-md-2">
<div>
<form class="card comment-form">
<div class="card-block">
<textarea class="form-control"
placeholder="Write a comment..."
rows="3"></textarea>
</div>
<div class="card-footer">
<img class="comment-author-img" />
<button class="btn btn-sm btn-primary" type="submit">
Post Comment
</button>
</div>
</form>
</div>
<div class="card">
<div class="card-block">
<p class="card-text">This is an example comment.</p>
</div>
<div class="card-footer">
<a class="comment-author" href="">
<img class="comment-author-img" />
</a>
<a class="comment-author" href="">
BradGreen
</a>
<span class="date-posted">
Jan 20, 2016
</span>
</div>
</div>
</div>
</div>
</div>
</div>
In fact, your example will work with angular 1.5 but not >1.6.
here is the reason :
Starting with angular 1.6, bindings are not yet set in the constructor. If you need them, move your code to the $onInit function.
Here is your new ArticleActionsCtrl :
class ArticleActionsCtrl {
constructor(Articles, User, $state) {
'ngInject';
this._Articles = Articles;
this._$state = $state;
this.User = User;
}
$onInit() {
if (this.User.current) {
this.canModify = (this.User.current.username === this.article.author.username);
} else {
this.canModify = false;
}
}
}
let ArticleActions = {
bindings: {
article: '='
},
controller: ArticleActionsCtrl,
templateUrl: 'article/article-actions.html'
};
export default ArticleActions;
I did not test it, do not hesitate to tell me if you have any problem with it.

Push To Angular JS Array

I'm running into a strange issue when executing the .push() method on an angular js collection. In the console I can see the object is added, but I cannot actually see it added to the list.
$scope.discountCodes.push({
discountCodeId: 0,
name: $scope.discountModel.name,
code: $scope.discountModel.code,
codeValue: $scope.discountModel.codeValue,
valueType: $scope.discountModel.valueType,
startDate: $scope.discountModel.startDate,
endDate: $scope.discountModel.endDate,
isActive: "True"
});
I have a simple repeater combined with a template
<div ng-repeat="discount in discountCodes" ng-include="getTemplate(discount)">
</div>
<script type="text/ng-template" id="display">
<div class="row">
<div class="col-md-3">
<span>Name:<br />{{discount.Name}}</span>
</div>
<div class="col-md-2">
<span>Code:<br />{{discount.Code}}</span>
</div>
<div class="col-md-2">
<span>Value:<br />{{discount.CodeValue}}</span>
</div>
<div class="col-md-2">
<span>Active:<br /></span>
<i class="icon-circle green-fill"
ng-show="discount.IsActive">
</i>
<i class="icon-circle red-fill"
ng-show="!discount.IsActive">
</i>
</div>
<div class="col-md-2"><br />
<i class="icon-edit icon-2x"></i>
</div>
</div>
</script>
This is the method:
$scope.discountModel.formSubmit = function (item, event) {
$scope.alertMessageContainerVisible = false;
if ($scope.frmDiscountForm.$valid) {
var dataObject = {
discountCodeId: 0,
name: $scope.discountModel.name,
code: $scope.discountModel.code,
codeValue: $scope.discountModel.codeValue,
valueType: $scope.discountModel.valueType,
startDate: $scope.discountModel.startDate,
endDate: $scope.discountModel.endDate,
isActive: "True"
};
action = "NEW";
$scope.discountCodes.push(dataObject)
});
}
}
Any ideas are helpful, I am new to Angular JS so be easy on me :)
I created a very simple version of this below:
http://plnkr.co/edit/qJDU7uiFleWIOjR5LYFh
It looks like you might be updating the collection outside of the Angular context. If so, you'll need to use $scope.$apply() for Angular to see your changes.
Turned out to be a stupid mistake! I had my controller defined in the body and in the div, so it was in two places. Now everything is working good!
Thanks all, it was good learning experience

Displaying an specific image for each item in ng-repeat

I have an ng-repeat which shows a list of sports and next to every sport name I'd like to display an specific image. I'd like to know how to go about displaying the correct png for every sport. How could I make it know what image to display? Here's how the code looks like.
<div ng-if="sport.leagues.length && sport.checked"
ng-repeat="sport in sports">
<ion-list class="list">
<div class="item">
<img src="" class="sport-image"> <!--Heres where img shoud be-->
<span>{{sport.name}}</span>
</div>
</ion-list>
</div>
I am supposing you want the image based on a key (like sport name). You can maintain a javascript object in your controller with key as name and value can be url of the image
obj = {football:'myurl'}
and use this object as
<div ng-if="sport.leagues.length && sport.checked"
ng-repeat="sport in sportsFilter = ( sports | sportsFiltered:query )">
<ion-list show-delete="sport.showStars"
class="list">
<div class="item">
<img ng-src="{{obj[sport.name]}}" class="sport-image"> <!--Heres where img shoud be-->
<span>{{sport.name}}</span>
</div>
You should use ng-src instead of src, so that you can pass the variable into it representing the current image to display, without risking an error 404 while the angular expression isn't parsed yet:
<img ng-src="{{sport.image}}" />
In JS part, you would merely have in your controller a kind of:
$scope.sport = {image: "http://myServer.com/mySuperSportImage.jpg"};
Note that this example doesn't deal with collections to simplify.
This ensures the binding.
You should create an object property for the image:
// HTML
<div ng-app="myApp" ng-controller="MyCtrl">
<div ng-if="sport.leagues.length && sport.checked"
ng-repeat="sport in sports">
<div class="item">
<img width="30" height="30" src="{{ sport.im }}" class="sport-image"> <!--Heres where img shoud be-->
<span>{{sport.name}}</span>
</div>
</div>
</div>
// JS
angular.module('myApp',[])
.controller('MyCtrl', function($scope) {
$scope.sport = {
leagues: ['premier', 'championship'],
checked: true
};
$scope.sports = [
{
name: 'soccer',
im: 'http://upload.wikimedia.org/wikipedia/en/e/ec/Soccer_ball.svg'
},
{
name: 'basketball',
im: 'http://upload.wikimedia.org/wikipedia/commons/7/7a/Basketball.png'
}
];
});
See this fiddle:
http://jsfiddle.net/uxu21n4v/1/
<img ng-src="http://www.gravatar.com/avatar/{{sport.imageLocation}}" class="sport-image"/>
Ref: https://docs.angularjs.org/api/ng/directive/ngSrc

$scope not defined inside function called from popup

i don't know why i can't access to $scope inside $scope.menuPopUpDelete, i need to delete the clicked trip in the popup.
$scope.trips is not a global variable inside the controller??
I would appreciate some help please!
HTML:
<body ng-controller="mytripsController">
<ul class="my-trips-list" style="padding-bottom: 100px;" ng-model="trips">
<li class="my-trip-item module" ng-repeat="trip in trips" >
<div class="list-image">
<img src="http://upload.wikimedia.org/wikipedia/commons/9/96/Beach_pano.jpg">
<h1>{{trip.title}}</h1>
</div>
<div class="list-content">
<div class="menu-points" ng-click="showMenuPopUp($event)">
<img src="images/menu-points.png"/>
<div class="popup-menu" style="display: none">
<div ng-click="menuPopUpDeleteClicked($event,$index)">Eliminar</div>
</div>
</div>
</div>
</li>
</ul>
</body>
JS:
var mytrips = angular.module('mytrips',[]);
mytrips.controller('mytripsController', function ($scope) {
$scope.trips = [
{
"id":"1",
"title":"Plan de viaje Mallorca",
"island": "Mallorca",
"duration": "3",
"startDate":"19/10/2014",
"endDate":"21/10/2014",
"image":"fotoTrips.jpg"
},
{
"id":"2",
"title":"Plan de viaje Mallorca2",
"island": "Mallorca",
"duration": "3",
"startDate":"19/10/2014",
"endDate":"21/10/2014",
"image":"fotoTrips.jpg"
}
];
$scope.showMenuPopUp = function($event){
var $popup = $($event.currentTarget).find('.popup-menu');
$popup.show('fast',function(){
$('body').click(function(){
$popup.hide();
$(this).unbind("click");
});
});
}
$scope.menuPopUpDeleteClicked = function($event,$index){
}
});
I think your problem is more with the way you have your HTML structured than anything else. You clicks are getting gobbled up and never executed.
Try this, which seems to work just fine.
<div class="menu-points" ng-click="showMenuPopUp('popup1')">
<img src="images/menu-points.png"/>
</div>
<div id='popup1' class="popup-menu" style="display: none">
<div ng-click="menuPopUpDeleteClicked($event,$index)">Eliminar</div>
</div>
$scope.showMenuPopUp = function(name){
var $popup = $('#'+name);
$popup.show('fast',function(){
$('body').click(function(){
$popup.hide();
$(this).unbind("click");
});
});
}
$scope.menuPopUpDeleteClicked = function($event,$index){
console.log("inside here");
console.log($scope.trips);
}
Note that the popup is taken OUT of the enclosing div as the click event was getting gobbled up. The name of the popup we are popping up is passed in instead of the event.

AngularJS: Access models using dynamic names in template

I have a template with a lot of what is essentially duplicate code. I'd like to use a directive to include a partial template which I can manipulate for each "block" of duplicate code.
The template currently looks something like this:
<div class="column book">
<div class="header">
<input type="text" id="book_query" ng-model="book_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="book in books | filter:book_query">
{{book.name}}
</div>
</div>
</div>
....
<div class="column game">
<div class="header">
<input type="text" id="game_query" ng-model="game_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="game in games | filter:game_query">
{{game.name}}
</div>
</div>
</div>
....
And the controller just gets the data and adds it to the scope e.g.
$scope.books = data.books;
$scope.games = data.games;
What I started doing was using a directive which takes in an argument (e.g. book, game etc) so I then knew which model(s) to use. The problem I have is how to use the argument to access the model in the template? The directive itself is, currently, very simple:
<div item-column item="book"></div>
<div item-column item="game"></div>
app.directive('itemColumn', function() {
return {
scope: {
item: '#'
},
replace: true,
templateUrl: 'item_column.html'
};
});
In item_column.html, I was hoping I could just substitute the item argument, which works fine for displaying the value of the arg but not for replacing where 'book' or 'game' is used for the models e.g.
<div class="column {{item}}">
<div class="header">
<input type="text" id="{{item}}_query" ng-model="{{item}}_query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in ??? | filter:{{item}}_query">
{{item.name}}
</div>
</div>
</div>
Can someone show me the best way of doing this? I don't doubt I'm going the complete wrong way about it!
EDIT: The original issue above is now pretty much fully solved using JoseM's answer below. The one outstanding issue is the on-click functions on each element no longer firing the parent scope from within the isolated scope.
My controller is laid out like so:
app.controller('ItemsCtrl', ['$scope', '$http', 'CONFIG', function($scope, $http, CONFIG) {
var items = ['books', 'games'];
items.forEach(function(item) {
$scope[item] = [];
$scope['selected_'+item] = null;
})
$scope.getItem = function(item) {
$http.get('?action=get_item&id='+item.id+'&type='+item.type)
.success(function(data) {
// update model
})
.error(function(data, status) {
// do something
});
}
}]);
$scope.getItem is no longer accessible when clicking on the item in the view, which looks similar to the following after implementing JoseM's answer:
<div class="row" ng-repeat="item in array | filter:query">
<div class="text" ng-click="getItem(item)">
{{item.name}}
</div>
</div>
Is there a simple way of making the parent scope functions available from within the isolated scope? Or is there a better place for these functions? Apologies for (what I feel are) the very basic questions - I'm still trying to get my head around Angular!
One way to accomplish what you want is by using a child scope in your directive and then doing your own "linking" of the parent scope variables using a watch on the parent scope value.
in your directive:
app.directive('itemColumn', function() {
return {
scope: true,
templateUrl: 'item_column.html',
link: function(scope,elem,attrs) {
var varName = scope.varName = attrs.item;
var parScope = scope.$parent;
parScope.$watch(varName + 's', function(newVal){
scope.theArray = newVal;
});
parScope.$watch(varName + '_query', function(newVal){
scope.theQuery = newVal;
});
}
};
});
in your template:
<div class="column {{varName}}">
<div class="header">
<input type="text" ng-attr-id="{{varName}}_query" ng-model="theQuery.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in theArray | filter:theQuery">
{{item.name}}
</div>
</div>
</div>
If you want to use an isolated scope, you could it, but then you would have to supply at least 3 attributes if you are using the same properties as above. I personally believe that using an isolated scope is a better way of doing it. See below how the isolated version is simpler:
isolated version of directive
app.directive('itemColumn2', function() {
return {
scope: {
label: '#',
array: '=',
query: '='
},
templateUrl: 'item_column2.html'
};
});
isolated version of template
<div class="column {{label}}">
<div class="header">
<input type="text" ng-attr-id="{{label}}_query" ng-model="query.name" />
</div>
<div class="content">
<div class="row" ng-repeat="item in array | filter:query">
{{item.name}}
</div>
</div>
</div>
usage
<div item-column2 label="book" array="books" query="book_query"></div>
<div item-column2 label="game" array="games" query="game_query"></div>
And finally here is a sample plunker: http://plnkr.co/edit/OyEHR4ZhzYKvs4jeDfjD?p=preview

Resources