I have a directive that displays cards for the user. Each card has an 'aww' or 'naww' button. AngularJS goes through the ng-repeat and generates each card. When the user clicks that button I want the 'aww' or 'naww' value to increment for that particular card. Unfortunately, when I click the buttons now, nothing happens and the values remain at zero. How would I get the aww and naww values to increment for each individual card?
view1.html
<div class="container">
<div ng-repeat="animal in animals" my-animal="animal"></div>
</div>
view1.js
'use strict';
angular.module('myApp.view1', ['ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}])
.controller('View1Ctrl', ['$scope',function($scope) {
$scope.animals = [{'name':'Perry','animal':'Bird','awws':0,'nawws':0,
'image-url': 'https://goo.gl/Vtcvk5'},
{'name':'Max','animal':'Cat','awws':0,'nawws':0,
'image-url':'https://goo.gl/bqOQci'
},
{'name': 'Julian','animal':'Duck','awws':0,'nawws':0,
'image-url':'https://goo.gl/v9GyTz'
}];
$scope.add = function(item){
item = item + 1;
};
}])
.directive('myAnimal', function() {
return {
scope: {
item: '=myAnimal'
},
restrict: 'EA',
templateUrl: 'view1/card-template.html'
};
});
cardtemplate.html
<div class="card">
<img class="card-img-top img-responsive" ng-src="{{item.image-url}}" alt="Cute animal">
<div class="card-block">
<h4 class="card-title">{{item.name}}</h4>
<p class="card-text">{{item.animal}}</p>
<button type="button" ng-click="add(item.awws)" class="btn btn-success">Aww +1 </button>
{{item.awws}}
<button type="button" ng-click="add(item.nawws)" class="btn btn-danger">Naww -1 </button>
{{item.nawws}}
</div>
</div>
This is happening because '$scope.add' function of your controller isn't inside the scope of your myAnimal directive(which has an isolate scope).
Also, you are using the directive in a wrong way. my-animal is your directive and not an attribute for your directive. First, change your directive calling template to:
<div class="container">
<div ng-repeat="animal in animals" my-animal animal="animal" add="add"></div>
</div>
directive to:
.directive('myAnimal', function() {
return {
scope: {
item: '=animal',
add: '&' //method binding here
},
restrict: 'EA',
templateUrl: 'view1/card-template.html'
};
});
As you can see I have added another attribute which binds the 'add' function in your controller to the directive, thus making it available in the directive scope. & is used for achieving this.
Issue is you are passing literal value of aww in ng-click function, where as you should pass the item object and increment its item.awws property.
<div class="card">
<img class="card-img-top img-responsive" ng-src="{{item.image-url}}" alt="Cute animal">
<div class="card-block">
<h4 class="card-title">{{item.name}}</h4>
<p class="card-text">{{item.animal}}</p>
<button type="button" ng-click="add(item)" class="btn btn-success">Aww +1 </button>
{{item.awws}}
<button type="button" ng-click="add(item.nawws)" class="btn btn-danger">Naww -1 </button>
{{item.nawws}}
</div>
</div>
and in js...
$scope.add = function(item){
item.awws += 1;//it might be required to find the item in the collection and then increment it
};
}])
Or, the easiest would be to do it directly in HTML
<div class="card">
<img class="card-img-top img-responsive" ng-src="{{item.image-url}}" alt="Cute animal">
<div class="card-block">
<h4 class="card-title">{{item.name}}</h4>
<p class="card-text">{{item.animal}}</p>
<button type="button" ng-click="item.awws = item.awws + 1" class="btn btn-success">Aww +1 </button>
{{item.awws}}
<button type="button" ng-click="item.nawws = item.nawws - 1" class="btn btn-danger">Naww -1 </button>
{{item.nawws}}
</div>
</div>
Related
I'm creating a set of widgets with AngularJS 1.5's new components. The problem is, when using the same widget multiple times, they somehow share their controller or scope. I thought one of the things about components was that their scope is completely isolated?
My main html template which hold the widgets:
<widget-list
title="Books"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Movies"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Albums"
class="col-xs-12 col-md-4">
</widget-list>
My widget template:
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
My widget component:
app.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title : '#',
},
controllerAs: '$widget',
controller: function($timeout) {
$widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
});
When running my code, this is what my console looks like:
Title on init: Books
Title on init: Movies
Title on init: Albums
(3) Title after 3 seconds: Albums
Also after rendering, all three widgets display No content in their template. But, when clicking the doSomething() button in either one of the three widgets, only the content of the last widget updates to Something.
What is happening here? Why are my components not 'isolated'?
Looks like you have a global variable called $widget here, try this:
var $widget = this;
instead of
$widget = this;
It creates a mess since the $widget variable holds a reference to the controller that has been recently initialized, in this case to the controller of the third component.
The problem with your code is that you are declaring the $widget on window scope, that's why your controller prints the last value, bacause it was being overridden every time the controller was getting instantiated. Use a var $widget instead and your code will work fine.
The following snippet solves this issue:
angular.module('app', [])
.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title: '#',
},
controllerAs: '$widget',
controller: WidgetListController
});
function WidgetListController($timeout) {
var $widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.min.js"></script>
<widget-list title="Books" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Movies" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Albums" class="col-xs-12 col-md-4">
</widget-list>
<script type="text/ng-template" id="template/widget/widget-list.html">
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
</script>
Trying to delete a list with an ng-click but the html and controller do not appear to communicate.
<div class="list-group">
<span ng-repeat="list in vm.lists"
class="list-group-item">
<a class="btn btn-primary pull-right" ng-click="vm.elim(list)">
<i class="glyphicon glyphicon-trash"></i>
</a>
<a class="btn btn-primary pull-right" ui-sref=
"lists.view({ listId:list._id })">
<i class="glyphicon glyphicon-edit"></i>
</a>
<h4 class="list-group-item-heading" ng-bind="list.name"></h4>
</span>
</div>
function elim(list) {
alert("works");
if ($window.confirm('Are you sure you want to delete?')) {
vm.list.$remove($state.go('lists.list'));
}
}
There should at least be an alert when the icon is clicked, but nothing happens. Suggestions?
Like Esteban said,Your elem function should be a part of scope variable like
$scope.vm.elem = function(){};
It looks like your function is not inside the $scope variable. Using
$scope.vm.elim = function(list){
...
}
should be enough.
Does your controller has below lines of code?
var vm = this;
vm.elm = function (list) {
alert("works");
if ($window.confirm('Are you sure you want to delete?')) {
vm.list.$remove($state.go('lists.list'));
}
};
If possible show us your controller code.
Here is a working example with ng-click:
<html >
<div ng-app="app" ng-controller="ctrl">
<div ng-repeat="obj in objs">
{{obj.name}}
<button ng-click="click(obj.id)">Delete</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<script>
var app = angular.module('app', []);
app.controller('ctrl', function($scope){
$scope.objs = [{id: 1, name:"Darin"},{id: 2, name:"Jake"},{id: 3, name:"Todd"}]
$scope.click = function(id){
alert(id);
}
});
</script>
</html>
If you using controller as syntax in directive or in html.
e.g. <div ng-controller="YourController as ctrl">
<!--some html code-->
</div>
Or
angular.directive('test',function test() {
return {
restrict: 'E',
template: '<button ng-click="click">{{text}}</button>',
controller: YourController,
controllerAs: 'ctrl'
}
});
then in html view you can access controller properties as
<div ng-controller="YourController as ctrl">
<p>{{ctrl.someProperty}}</p>
</div>`
If you are not using controller as syntax then just remove vm from "vm.elim(list)" and "vm.lists"
Here is the great article for the same : https://toddmotto.com/digging-into-angulars-controller-as-syntax/
Assuming you want to keep the vm pattern just add on the controller,
vm.elim = elim;
At least should alert now.
I am trying to use directive in mu page ..I am trying to send data from controller to directive .I am trying to display data using ng-repeat.
here is I am applying the ng-repeat .
<div class="container">
<!--Row with two equal columns-->
<div class="row " ng-repeat='d in data'>
<div class="col-sm-3">
<div class="demo-content">{{d.Location}}</div>
</div>
<div class="col-md-7">
<div class="demo-content bg-alt description-ellipse">{{d.description}}</div>
</div>
<div class="col-md-1">
<div class="demo-content bg-alt">
<button type="button" class="btn btn-default view-now-button">VIEW NOW</button>
</div>
</div>
</div>
</div>
I make a directive like this
.directive('listComponent', function() {
return {
restrict: 'E',
scope: {
data:'='
},
templateUrl: 'list.html',
link: function(s, e, a) {
}
}
})
I am sending data like that
data:'=' and from here <list-component data='h.data'></list-component>
here is my code
http://plnkr.co/edit/Q0GQC2Pik7m25HxQIW8H?p=preview
It should be:
<div class="row " ng-repeat='d in data.jobs'>
in list.html
Or, as per comment:
<list-component data='h.data.jobs'></list-component>
Updated Plunker
I'm trying to build an image gallery where any image when clicked, opens up in an expanded modal box (Bootstrap 3). I've created the div that handles the thumbnail view and the modal dialogue as a template wherein the values are filled using a custom script written in AngularJS.
The problem is, I'm unable to pass the values into the modal dialogue. The thumbnail part of the code works fine but upon clicking the individual images, the values aren't read from the script. This is the HTML code I'm working with:
<div class="isotope-container row grid-space-20">
<div class="col-sm-6 col-md-3 isotope-item int-design" ng-repeat="photopost in postCntrl.posts">
<div class="image-box">
<div class="overlay-container">
<img ng-src="{{photopost.photoThumbnailSrc}}" class="img-responsive" alt="">
<a class="overlay" data-toggle="modal" data-target="#project-1">
<i class="fa fa-search-plus"></i>
<span>{{photopost.photoCategory}}</span>
</a>
</div>
<a class="btn btn-default btn-block" data-toggle="modal" data-target="#project-1">{{photopost.photoSubTitle}}</a>
</div>
<!-- Modal -->
<div class="modal fade" id="project-1" tabindex="-1" role="dialog" aria-labelledby="project-1-label" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="project-1-label">{{photopost.photoCaption}}</h4>
</div>
<div class="modal-body">
<img ng-src="{{photopost.photoSrc}}"/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal end -->
</div>
</div>
This is the script written using Angular:
(function(){
var postGeneratorApp = angular.module('PhotoPostGenerator', []);
postGeneratorApp.controller('PhotoPostController', function() {
this.posts = photoposts;
this.numOfPosts = 20;
});
var photoposts =
[
{
photoCaption: "This is an image",
photoSubTitle: "Tree",
photoTimestamp: 'Aug 23, 2015',
photoCategory: "landscape",
photoSrc: "../images/gallery/img1.jpg",
photoThumbnailSrc: "../images/gallery/img1_th.jpg",
},
{
photoCaption: "This is also an image",
photoSubTitle: "Bird",
photoTimestamp: 'Sep 23, 2015',
photoCategory: "bird",
photoSrc: "../images/gallery/img2.jpg",
photoThumbnailSrc: "../images/gallery/img2_th.jpg",
},
{...} //more entries such as these
];
})();
This is what the gallery thumbnails look like (please ignore the captions on the thumbnails, they are from an earlier snapshot):
And this is what the modal dialogue looks like, when clicked:
As you can see the caption and the expanded image are missing (photoCaption and photoSrc). When looking for answers I came across AngularUI for Bootstrap but wasn't sure how to use this or if there was a simpler fix available for my current code.
EDIT 1
Look this: http://jsfiddle.net/mamor/4KBWj/embedded/
In the controller main, when opener the modal use the parameter 'resolve' for pass values between conttroler main and modal. In the modal use the name parameter passed in modalInstance when open.
myApp.controller('MainController', ['$scope', '$modal', function ($scope, $modal) {
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
controller: 'ModalInstanceCtrl',
resolve: {
params: function () {
return {
key: 'value',
key2: 'value2'
};
}
}
});
modalInstance.result.then(
function (result) {
console.log('called $modalInstance.close()');
alert(result);
},
function (result) {
console.log('called $modalInstance.dismiss()');
alert(result);
}
);
};
});
Now uses de params in modal controller for obtains values of main controller.
myApp.controller('ModalController', ['$scope', '$modalInstance', 'params', function ($scope, $modalInstance, params) {
console.log(params); /* This params of MainController */
$scope.ok = function () {
$modalInstance.close('this is result for close');
};
$scope.cancel = function () {
$modalInstance.dismiss('this is result for dismiss');
};
}]);
EDIT 2
Add in the element call this: "ng-click='clickedElement = photopost'"
<div class="image-box">
<div class="overlay-container">
<img ng-src="{{photopost.photoThumbnailSrc}}" class="img-responsive" alt="">
<a class="overlay" ng-click="clickedElement = photopost" data-toggle="modal" data-target="#project-1">
<i class="fa fa-search-plus"></i>
<span>{{photopost.photoCategory}}</span>
</a>
</div>
<a class="btn btn-default btn-block" ng-click="clickedElement = photopost" data-toggle="modal" data-target="#project-1">{{photopost.photoSubTitle}}</a>
</div>
After, in the modal acess metada of target use 'clickedElement' for acess the data.
<div class="modal-header">
{{clickedElement}}
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="project-1-label">{{clickedElement.photoCaption}}</h4>
</div>
I have a controller (called "catalogueController") that manages my search box and my search page. I have the controller initially set the page to automatically call the search function defined in "catalogueController" when the app loads to pre-load my array of items (called Inventory) to be repeated via ng-repeat in the page.
The process runs like this:
1. I submit the search form.
2. "catalogueController" will send the search term to my factory (called "Search").
3. "Search" will have a function which will make a server call to query my database for that particular search.
4. The database will send the results of the search to the "Search" factory.
5. The "Search" factory will send the results to the "catalogueController" controller.
6. "catalogueController" will update the $scope.Inventory to be equal to the new result that I was received.
My problem is that ng-repeat does not refresh itself to display my new and updated $scope.Inventory array. $scope.Inventory definitely is updated (I have made sure of this through various console logs).
I have also tried to use $scope.$apply(). It did not work for me.
Thank you in advance for your help!
Here is my code:
HTML Template
<form role="search" class="navbar-form navbar-left" ng-controller="catalogueController" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<main ng-view></main>
catalogue.html partial
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
"app.js" Angular App
var myApp = angular.module('qcbApp', ['ngRoute', 'ngCookies', 'appControllers']);
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'html/partials/login.html',
controller: 'registrationController'
}).
when('/sign-up', {
templateUrl: 'html/partials/sign-up.html',
controller: 'registrationController'
}).
when('/catalogue', {
templateUrl: 'html/partials/catalogue.html',
controller: 'catalogueController'
}).
when('/', {
templateUrl: 'html/partials/qcbhome.html'
}).
otherwise({
redirectTo: '/'
});
}]);
"catalogueController" Controller
myApp.controller('catalogueController', ['$scope', 'Search', function($scope, Search) {
var time = 0;
var searchCatalogue = function(search) {
$scope.inventory = null;
console.log("Controller -- "+search);
Search.searchCatalogue(search)
.then(function(results) {
console.log(results);
$scope.inventory = results;
});
};
if(time == 0)
{
searchCatalogue('');
time++;
}
$scope.search = function(term) {
searchCatalogue(term);
}
}]);
"Search" Factory
myApp.factory('Search', ['$http', '$q', function($http, $q) {
function searchCatalogue(term) {
var deferred = $q.defer();
console.log("Factory -- "+term);
$http.post('/catalogue_data', {term: term}, {headers: {'Content-Type': 'application/json'}})
.success(function(result) {
console.log(result[0].SKU);
deferred.resolve(result);
console.log("Factory results -- "+result);
});
return deferred.promise;
}
return {
searchCatalogue: searchCatalogue
}; //return
}]);
I think the problem is the ng-repeat can not access the inventory in scope. You have to create a div which contains both the form and the ng-repeat.
The html should be:
<div ng-controller="catalogueController">
<!-- Move the controller from the form to parent div -->
<form role="search" class="navbar-form navbar-left" ng-submit="search(search_term)">
<div class="form-group">
<input type="text" placeholder="Search" class="form-control" ng-model="search_term">
</div>
<button type="submit" class="btn btn-default">Search</button>
</form>
<div id="main" class="margin-top-50 clearfix container">
<div ng-repeat="items in inventory" class="row-fluid">
<div class="col-sm-6 col-md-3">
<div class="thumbnail"><img src="image.jpg" alt="..." class="col-md-12">
<div class="caption">
<h3>{{ items.itemName }}</h3>
<p>{{ items.description }}</p>
<p>Buy <a href="#" role="button" class="btn btn-default">More Info</a></p>
</div>
</div>
</div>
</div>
</div>
I've seen the situation a few times where when you are updating a property directly on the $scope object there are interesting problems around databinding to that value (such as inventory). However if you databind to an object property of an object then the databinding works as expected. So for example use a property on $scope. I believe this is a copy by value vs copy by reference issue.
Update all your inventory references as follows
$scope.data.inventory = result;
Also don't forget to update your inventory reference in the html template:
<div ng-repeat="items in data.inventory" class="row-fluid">
Update: I made this plunk to figure it out - http://plnkr.co/edit/0ZLagR?p=preview
I think the primary problem is you have the controller specified twice. I removed it from the form and it started working.