Angularjs ng-click inside ng-repeat - angularjs

I'm trying to load a list of clickable news feed URLs in a dropdown-menu. When I use fixed addresses in view, it works fine but when I populate the addresses using controller, dropdown menu is fine but ng-click doesn't work as expected.
Here is jsfiddle of working version:http://jsfiddle.net/mahbub/b8Wcz/
This code works:
<ul class="dropdown-menu">
<li>ABC News
</li>
<li>CNN
</li>
</ul>
controller code:
var App = angular.module('RSSFeedApp', []);
App.controller("FeedCtrl", ['$scope', 'FeedService', function ($scope, Feed) {
$scope.loadButonText = "Select news channel";
$scope.loadFeed = function (e) {
Feed.parseFeed($scope.feedSrc).then(function (res) {
$scope.loadButonText = angular.element(e.target).text();
$scope.feeds = res.data.responseData.feed.entries;
});
}
App.factory('FeedService', ['$http', function ($http) {
return {
parseFeed: function (url) {
return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q=' + encodeURIComponent(url));
}
}
}]);
This code does not:
<ul class="dropdown-menu">
<li ng-repeat =" newsChannel in channels">
{{newsChannel.title}}
</li>
</ul>
controller code:
App.controller("FeedCtrl", ['$scope', 'FeedService', function ($scope, Feed) {
$scope.loadButonText = "Select news channel";
$scope.loadFeed = function (e) {
Feed.parseFeed($scope.feedSrc).then(function (res) {
$scope.loadButonText = angular.element(e.target).text();
$scope.feeds = res.data.responseData.feed.entries;
});
}
$scope.channels = [
{
'src': 'http://www.abc.net.au/news/feed/45910/rss.xml',
'title': 'ABC News'
},
{
'src': 'http://rss.cnn.com/rss/cnn_topstories.rss',
'title': 'CNN'
}
];
}]);
App.factory('FeedService', ['$http', function ($http) {
return {
parseFeed: function (url) {
return $http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=50&callback=JSON_CALLBACK&q=' + encodeURIComponent(url));
}
}
}]);
TypeError: Cannot read property 'feed' of null
Any idea why the $scope.feedSrc doesn't get the feedSrc url?
Thanks

So I recommend using $parent for this. ng-repeat has it's own scope so when you say "feedSrc" it is looking on the ng-repeat scope for that data. "newsChannel" is available because that gets added by ng repeat but you will notice that is never added to your parent controller's $scope.
<ul class="dropdown-menu">
<li ng-repeat =" newsChannel in channels">
{{newsChannel.title}}
</li>
</ul>
As an aside, you might consider not doing assignments in the view like that, it is generally preferable to only assign variables in the controller and make sure that your HTML is read only

The problem is how you are passing arguments to your ng-click:
ng-click="$parent.feedSrc='{{newsChannel.src}}';..."
You don't need to wrap the prperty in handlebars...
ng-click="$parent.feedSrc='newsChannel.src';..."
should fix your problem.

I found a workaround. Added another parameter to loadFeed method.
<ul class="dropdown-menu">
<li ng-repeat =" newsChannel in channels">
{{newsChannel.title}}
</li>
</ul>
and here is the controller's new version:
App.controller("FeedCtrl", ['$scope', 'FeedService', function ($scope, Feed) {
$scope.loadButonText = "Select news channel";
$scope.loadFeed = function (e, feedUrl) {
Feed.parseFeed(feedUrl).then(function (res) {
$scope.loadButonText = angular.element(e.target).text();
$scope.feeds = res.data.responseData.feed.entries;
});
}
$scope.channels = [
{
'src': 'http://www.abc.net.au/news/feed/45910/rss.xml',
'title': 'ABC News'
},
{
'src': 'http://rss.cnn.com/rss/cnn_topstories.rss',
'title': 'CNN'
}
];
}]);
thanks to this post: ng-click inside ng-repeat

Related

Testing angular components templateUrl

I'm having an issue testing this template html. I'm using karma and sinon. I'm getting this Unknown provider: _Provider <- _ <- service error.
Here is my component:
angular.
.module('app.component.thing-foo')
.component('thingFoo', {
templateUrl: 'components/thing-foo/thing-foo.html',
controller: ThingFooController,
bindings: {
thing: '<',
onSelect: '&'
}
});
function ThingFooController($log, service) {
var ctrl = this;
ctrl.$onInit = $onInit;
ctrl.$onChanges = $onChanges;
ctrl.thingList = [];
function $onInit(){
getThingList();
}
function $onChanges(changesObj){
if (changesObj.thing) {
ctrl.thing = angular.copy(changesObj.thing.currentValue);
}
function getThingList(){
service.getThings()
.then(function (result) {
ctrl.thingList = result.things;
}, function (error) {
$log.error('Did not work: $0', error);
})
}
function selectionChanged(selected) {
ctrl.onSelect({thing: selected});
}
}
thing-foo.html:
<span ui-dropdown class="dropdown-toggle">
<a href id="query-menu-label" ui-dropdown-toggle>
Thing Picker <b class="caret"></b>
</a>
<ul calss="dropdown-menu" aria-labelledby="query-menu-label" role="menu">
<li role="presentation" ng-repeat="thing in $ctrl.thingList">
<a role="menuitem" ng-click="$ctrl.selectionChanged(thing)">{{ thing }}</a>
</li>
</ul>
</span>
I'm including the parts of the spec file that are failing:
beforeEach(inject(function(_$compile_, _$componentController_, _$rootScope_){
$compile = _$compile_;
$componentController = _$componentController_;
$rootScope = _$rootScope_;
}));
beforeEach(function(){
scope = $rootScope.$new();
element = angular.element('<thing-foo></thing-foo>');
service = {
getThings: sinon.stub();
};
locals = {
service: service
};
});
it('loads html', function(){
var tag = $compile(element)(scope);
scope.$apply();
expect(tag.html()).to.exist();
});
There's a lot of code here but I wanted to make sure I included everything. I think the problem is the $ctrl.getThingList in the ng-repeat. Am I supposed to inject the service some how? If so how do I do that? I don't have a lot of practice with writing unit tests for angular. Any help is appreciated.
Not entirely sure, but maybe try to configure $provider to inject your service?
Something like this:
// Put this before your first beforeEach
beforeEach(module(function($provide) {
$provide.value('service', service);
}

Factory value not updated in model ...what I am doing wrong?

I am new to angular-js. I have two controllers (welcomeContoller,productController) and both handling the same model within the factory.
When the model getting updating by one controller(productController) it should reflect the update in another controller. (welcomeContoller)
But its not happening now.
HTML code :
<body ng-app="myApp">
<div ng-controller="welcomeContoller">
{{totalProductCnt}}
</div>
<div ng-controller="productController">
<div class="addRemoveCart">
<span class="pull-left glyphicon glyphicon-minus" ng-click="removeProduct()"></span>
<span class="pull-right glyphicon glyphicon-plus" ng-click="addProduct(1)"></span>
</div>
</div>
JS code
var myApp = angular.module("myApp", ['ui.bootstrap']);
myApp.factory("productCountFactory", function() {
return {
totalProducts:0
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory)
{
$scope.totalProductCnt = productCountFactory.totalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.totalProducts++;
alert(productCountFactory.totalProducts);
};
$scope.removeProduct = function() {
if(productCountFactory.totalProducts >=1)
productCountFactory.totalProducts--;
alert(productCountFactory.totalProducts);
};
});
Even after the addProduct is called the totalProductCnt is displaying as zero. I want to display the value for each increment.
Plunkr Link
Put the factory object reference on scope:
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.productCountFactory = productCountFactory;
});
Watch the property of the object.
{{productCountFactory.totalProducts}}
The DEMO on PLNKR.
By putting a reference on scope, on every digest cycle the watcher looks up the value of the property and updates the DOM if there is a change.
The totalProductCnt from your welcomeController isn't updated because it is assigned only once when the controller is created.
You can use several solutions to refresh the displayed value. Use a getter for your totalProducts in the factory :
myApp.factory("productCountFactory", function() {
var totalProducts = 0;
return {
getTotalProducts: function() {
return totalProducts;
},
addProduct: function() {
totalProducts++;
},
removeProduct: function() {
totalProducts--;
}
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.getTotalProducts = productCountFactory.getTotalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.addProduct();
};
$scope.removeProduct = function() {
if (productCountFactory.getTotalProducts() >= 1)
productCountFactory.removeProduct();
};
});
And update the view accordingly:
<div ng-controller="welcomeContoller">
{{getTotalProducts()}}
</div>
Plunkr Link

How should you access controller functions from other modules

I am having a hard time understanding how Modules should interact with each other in Angularjs. I would like to break the application into nice small modules, but I cannot seem to find the correct way to have these modules interact with each other.
JSFiddle:
http://jsfiddle.net/jwest80/o5o3sr8q/4/
The code shows a breadcrumb I would like to have at the top of the page. The BreadCrumb is in its own module 'bread' and included inside a parent module 'ngFSCH'.
There is a list outside BreadCrumb controller section whose actions should add breadcrumbs. However, I do not understand the correct way to access this addCrumb function. I can only make it work if it is called from inside the breadcrumb controller section in the markup.
Markup:
<div ng-app="ngFSCH">
<section ng-controller="BreadCrumbsCtrl">
<span ng-repeat="crumb in crumbs" class="breadcrumbs">
<span ng-hide="isLast($index)" ng-click="selectCrumb($index)">{{crumb.text}} > </span>
<span ng-show="isLast($index)">{{crumb.text}}</span>
</span>
</section>
<section>
<h4>Add Some Crumbs</h4>
<ul>
<li>Company</li>
<li>Department</li>
<li>User</li>
</ul>
</section>
</div>
Script:
var ngFSCH = angular.module('ngFSCH', ['bread']);
(function () {
var app = angular.module('bread', []);
app.controller('BreadCrumbsCtrl', ['$scope', '$log', function ($scope, $log) {
$scope.crumbs = [{ text: "Crumb 1", url: "url1" }, { text: "Crumb 2", url: "url2" }];
$scope.isLast = function(index) {
return index === $scope.crumbs.length-1;
}
$scope.addCrumb = function (newCrumb) {
$scope.crumbs.push({ text: newCrumb, url: "TestURL" });
}
$scope.selectCrumb = function (index) {
$log.info($scope.crumbs[index].url);
$scope.crumbs = $scope.crumbs.slice(0, index + 1);
}
}]);
})();
I would encapsulate the bread crumb functionality in a service and create a controller for the section with the links (that add the breadcrumbs). The new controller can then use the service to add and remove crumbs from the array. You can also add the crumbs array into a value.. Your controllers can then expose the add and select features to the tiny portions of html they control without polluting other sections of your page.
Here is the result. Hope it helps!
JSFiddle
Here is the code:
var app = angular.module('bread', []);
app.value('crumbs', [
{ text: "Crumb 1", url: "url1" },
{ text: "Crumb 2", url: "url2" }
]);
app.factory("BreadCrumbsService", ['$log', 'crumbs', function ($log, crumbs) {
var service = {
getCrumbs: getCrumbs,
addCrumb: addCrumb,
selectCrumb: selectCrumb
};
return service;
//I did not add a set crumbs because you can set it directly.
function getCrumbs(){
return crumbs;
}
function addCrumb(newCrumb) {
crumbs.push({
text: newCrumb,
url: "TestURL"
});
}
function selectCrumb(index) {
$log.info(crumbs[index].url);
crumbs = crumbs.slice(0, index + 1);
}
}]);
app.controller('BreadCrumbsCtrl', ['$scope', 'BreadCrumbsService', function ($scope, BreadCrumbsService){
$scope.crumbs = BreadCrumbsService.getCrumbs;
$scope.selectCrumb = BreadCrumbsService.selectCrumb;
$scope.isLast = function (index) {
return index === BreadCrumbsService.getCrumbs().length - 1;
}
}]);
app.controller('AddLinksCtrl', ['$scope', 'BreadCrumbsService', function ($scope, BreadCrumbsService) {
$scope.addCrumb = BreadCrumbsService.addCrumb;
}]);
Here is the links section with the new controller:
<section ng-controller="AddLinksCtrl">
<h4>Add Some Crumbs</h4>
<ul>
<li>Company</li>
<li>Department</li>
<li>User</li>
</ul>
</section>
That is intended because you are working within the scope of the controller. How about moving the ng-controller directive to the containing div where ng-app is?
<div ng-app="ngFSCH" ng-controller="BreadCrumbsCtrl">

AngularJS : Setting languages in angular-translate from Controller or Service

I am interested in using angular-translate.
Due to a lot of setup calls that happen initially on startup, I cannot provide the language json during config. Nor is it possible to use the async loader.
I need to be able to specify the languages from a controller or service at a later point.
$translateProvider.translations(.., ...) is the call to use, but $translateProvider isn't available in controllers or services, but seemingly only at config.
$translate doesn't seem to have the ability to load a language JSON.
Is there any workaround?
First inject $translate into your controller.
app.controller('MainCtrl', function($scope, $state, $translate) {});
Then you can get and set current language with $translate.use().
var lang = $translate.use(); // holds current lang
$translate.use(lang); // sets lang to use
If you need to add new translations after config, then you can use partial loaders.
// config example
app.config(function($translateProvider, $translatePartialLoaderProvider){
// "known" translations here, in {lang}.main.json, if any
$translatePartialLoaderProvider.addPart('main');
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: '/path/to/files/{lang}.{part}.json'
});
});
// controller
app.controller('MainCtrl', function($scope, $translate, $translatePartialLoader){
$translatePartialLoader.addPart('translation');
$translate.refresh();
$translate.use('en');
});
// en.translation.json
{ "KEY" : "Value", ... }
If that is not dynamic enough, then you can always do the translation on-the-fly.
// config
app.config(function($translateProvider, $translatePartialLoaderProvider){
$translateProvider.preferredLanguage('en');
$translateProvider.translations('en',{
'TITLE': '{{ title }}',
'SUBTITLE': '{{ subtitle }}',
'STATIC': 'This is static'
});
});
// controller
app.controller('MainCtrl', function($scope, $translate){
$scope.dynamic = {
'title': 'This is my header',
'subtitle': 'My subtitle'
};
});
// template
<pre>{{ 'TITLE' | translate:dynamic }}</pre>
<pre>{{ 'SUBTITLE' | translate:dynamic }}</pre>
<pre>{{ 'STATIC' | translate }}</pre>
This would spit out something like
Got there in the end.
in the .config
$translateProvider.useLoader('customLoader');
the customLoader...
angular.module('myapp').factory('customLoader', function ($q, TranslationService) {
return function (options) {
var deferred = $q.defer();
deferred.resolve(TranslationService.getLanguages().filter(function(lang){
return lang.key == options.key
})[0].values);
return deferred.promise;
};
});
and then a TranslationService to share the data
angular.module('myapp').factory('TranslationService', function () {
var languages = [];
return {
setLanguages: function (data) {
languages = data;
},
getLanguages: function () {
return languages;
}
}
});
Maybe check this:
http://www.ng-newsletter.com/posts/angular-translate.html
Under "Switching the language at runtime"
$translate.use(); // Returns the currently used language key
$translate.use('en'); // Sets the active language to `en`
app.controller('TranslateController', function($translate, $scope) {
$scope.changeLanguage = function (langKey) {
$translate.use(langKey);
};
});
this one works. storageService has local storage and after setting 'NG_TRANSLATE_LANG_KEY' in local storage. You can call it like below.
angular.module('myApp').run(['$rootScope', 'StorageService', function($rootScope, StorageService) {
$rootScope.currentLanguage = StorageService.local.get('NG_TRANSLATE_LANG_KEY') || 'en';
}]);
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1" ng-controller="TranslateController" ng-init="changeLanguage(currentLanguage)">
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" ng-click="changeLanguage('tr')">TR</a></li>
<li role="presentation" class="divider"></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" ng-click="changeLanguage('en')">EN</a></li>
</ul>
I think the best way to manage dynamically loading is
in the resolve config router block like
resolve: {
translatePartialLoader: function loadPartialLoader($translate,$translatePartialLoader) {
$translatePartialLoader.addPart('home');
return $translate.refresh();
}
}

How to refresh an ng-repeat list when the backing model has been updated via ng-click?

I'm rather new to AngularJS so I presume this is going to be something about confusing scope.
I'm trying to get a ng-repeat list watching a service on the scope to update when I push a new item to the service backing model from a ng-click anchor link.
An example failing fiddle is here: http://jsfiddle.net/znFwY/
JS:
'use strict';
angular.module('myApp', [])
.factory('Order', function () {
return {
packages: {
theList: [],
list: function () {
return this.theList;
},
push: function (p) {
if (p === undefined || !!this.theList[p.title]) return;
this.theList[p.title] = p;
console.log('pushed ' + p.title);
},
contains: function (p) {
return !!this.theList[p.title];
},
print: function () {
console.log(this.theList);
}
}
}
})
.controller('AppCtrl', function ($scope, $http, $location, Order) {
$scope.order = Order;
$scope.data = {
packages: [
{title: 'one'},
{title: 'two'},
{title: 'three'},
]
};
})
;
HTML:
<div ng-app="myApp">
<div ng-controller="AppCtrl">
<ul id="package-list">
<li ng-repeat="package in data.packages | orderBy:package.order">
<a ng-click="order.packages.push(package)" ng-class="{selected: order.packages.contains(package)}">{{package.title}}</a>
</li>
</ul>
<ul id="basket-list">
<li ng-repeat="package in order.packages.list() | orderBy:package.order"> {{package.title}}</li>
</ul>
<a ng-click="order.packages.print()">print list</a>
</div>
</div>
Thanks

Resources