I have a list of companies that have revenue and cost data. In my app, either revenue or cost can be displayed, and the list can be filtered down by a query.
In my controller I have the following two watches:
$scope.$watch('query', function(q) {
$scope.filteredCompanies = $scope.companies
.filter(function(c) { return q ? c.name.toLowerCase().indexOf(q.toLowerCase()) > -1 : true; });
$scope.chartData = {
name: 'companies',
children: $scope.filteredCompanies.map(function(c) {
return {name: c.name, size: c[$scope.selectedItem]};
})
};
});
$scope.$watch('selectedItem', function(i) {
$scope.chartData = {
name: 'companies',
children: $scope.filteredCompanies.map(function(c) {
return {name: c.name, size: c[i]};
})
};
});
My question is, can I tell chartData to watch selectedItem and filteredCompanies, instead of query? I tried the following:
$scope.$watch('[filteredCompanies, selectedItem]', function(arr) {
$scope.chartData = {
name: 'companies',
children: arr[0].map(function(c) {
return {name: c.name, size: c[arr[1]]};
})
};
});
but got the 10 digests iterations reached error.
Not sure what version of angular you are using.. but the 1.2 version of that is..
$scope.$watchCollection('[filteredCompanies, selectedItem]',
function(newValues, oldValues){
$scope.chartData = {
name: 'companies',
children: arr[0].map(function(c) {
return {name: c.name, size: c[arr[1]]};
})
};
});
I also think a third parameter of true will work:
$scope.$watch('[filteredCompanies, selectedItem]',
function () {
},
true
);
Setting up a fiddle now.
Related
I am trying to plot a graph with Hightchart, my data comes from an REST web service.
I am using angularjs (1.5) to get the data.
my Service look like :
app.factory('ServicesImpl', function($http){
var obj = {};
obj.getData = function(){
return $http.get('http://localhost:8080/chartDepense/');
};
return obj;
});
my controller is :
app.controller('myController', function ($scope, $rootScope, ServicesImpl) {
$scope.chart = ServicesImpl.getData().then(function(r){
return r.data;
});
console.log($scope.chart);
$scope.chartOptions = {
title: {
text: $scope.chart.libelle
},
xAxis: {
categories: $scope.chart.categories ,
title: {
text: 'les X'
}
},
yAxis: {
title: {
text: 'les Y'
}
},
series: [{
data: $scope.chart.peroides
}]
};
});
and I got this :
I don't know can I can get my data in my controller to put in chartConfig?
I'm trying to print just the unique values of names but i'm unable to do that.
html code:
<div ng-controller="MyCtrl">
<div><input type="text" ng-model="nameFilter" placeholder="Search..." /></div>
<p ng-repeat="contact in contacts | orderBy: 'customer.name'| unique:'customer.name'">{{ contact.customer.name }}</p>
</div>
JS code:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.nameFilter = '';
$scope.contacts = [
{ id:1, customer: { name: 'foo', id: 10 } },
{ id:2, customer: { name: 'bar', id: 20 } },
{ id:3, customer: { name: 'foo', id: 10 } },
{ id:4, customer: { name: 'bar', id: 20 } },
{ id:5, customer: { name: 'baz', id: 30 } },
{ id:5, customer: { name: 'tar', id: 30 } },
{ id:5, customer: { name: 'got', id: 30 } },
{ id:5, customer: { name: 'man', id: 30 } },
{ id:5, customer: { name: 'baz', id: 30 } },
];
}
the jsfiddle is here: http://jsfiddle.net/nvarun123/0tgL7u6e/73/
This code is working if i remove unique from the ng-repeat.
Here you go, I used the unique filter in angular UI directives, link in the bottom. I have made a small change in the directive for implementing deep finding using string. The details are inside the references.
Here is a demo of the filter.
JSFiddle Demo
Change made inside unique filter.
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return deepFind(item,filterOn);
} else {
return item;
}
};
As seen above I am implementing the deepFind function. The function is also provided below.
function deepFind(obj, path) {
var paths = path.split('.')
, current = obj
, i;
for (i = 0; i < paths.length; ++i) {
if (current[paths[i]] == undefined) {
return undefined;
} else {
current = current[paths[i]];
}
}
return current;
}
References:
Angular-UI unique filter
Javascript get deep value by passing path
Here's a simple custom filter that should meet your needs:
app.filter("unique", function thisFilter() {
return function(input){
var seen = { objectNames: [] };
return input.filter(function(obj){
return !seen.objectNames.includes(obj.customer.name)
&& seen.objectNames.push(obj.customer.name)
})
}
});
I'm attempting to refactor a website into Angular through UI-Router. On the parent component I have defined some data on the controller. How do I pass this data to the child nested routed component through UI-Router? UI-Router's resolve only works when you have a binding on data, but I don't know if it's possible to bind the parent controller data to the parent component.
const main = angular.module('main', ['ui.router']);
main.config(function($stateProvider) {
const states = [
{ name: 'parent', url: '/parent', component: 'parent' },
{ name: 'parent.child', url: '/{childUrl}', component: 'child',
resolve: {
data: function($transition$) {
// I want to pass in { name: 'Name1', content: 'Page-Long Content1' }, { name: 'Name2', content: Page-Long Content2' }
// How do I do this?
}
}
}
];
states.forEach(function(state) {
$stateProvider.state(state);
});
});
angular.module('main')
.component('parent', {
template:
'<div ng-repeat="data in $ctrl.data">' +
'<p>{{data.name}}</p>' +
'<a ui-sref="parent.child({ childUrl: data.name })" ui-sref-active="active">Child link</a>' +
'</div>' +
'<ui-view></ui-view>',
controller: function() {
this.data = [
{name: 'Name1', content: 'Page-Long Content1'},
{name: 'Name2', content: 'Page-Long Content2'}
]
}
});
angular.module('main')
.component('child', {
bindings: { data: '<' },
templateUrl: 'link.html',
});
Basically you have an unique identifier of your record in the URL. So by that you can retrieve your data once again from the dataset. For achieving the same, I'd suggest you to put your data in service and then fetch the same data in resolve of your state. Afterward apply an filter over the data and get desired record from it based on childUrl parameter of state.
angular.module('main')
.service('dataService', function(){
var dataService = this;
dataService.getData = getData;
var data = [
{name: 'Name1', content: 'Page-Long Content1'},
{name: 'Name2', content: 'Page-Long Content2'}
];
function getData(){
return data;
}
});
state
const states = [
{ name: 'parent', url: '/parent', component: 'parent' },
{ name: 'parent.child', url: '/{childUrl}', component: 'child',
resolve: {
data: function($transition$, dataService) {
let childUrl = $transition$.params('childUrl');
//in case of API call below will be changed to promise driven code.
return dataService.getData().filter((item) => item.name === childUrl)[0];
}
}
}
];
And inside a parent controller you can fetch data from service directly.
controller: function(dataService) {
this.data = dataService.getData();
}
I have a ui-select field
{
key: 'data_id',
type: 'ui-select',
templateOptions: {
required: true,
label: 'Select label',
options: [],
valueProp: 'id',
labelProp: 'name'
},
controller: function($scope, DataService) {
DataService.getSelectData().then(function(response) {
$scope.to.options = response.data;
});
}
}
How can I access that inner controller in my unit tests and check that data loading for the select field actually works ?
UPDATE:
An example of a test could be as such:
var initializePageController = function() {
return $controller('PageCtrl', {
'$state': $state,
'$stateParams': $stateParams
});
};
var initializeSelectController = function(selectElement) {
return $controller(selectElement.controller, {
'$scope': $scope
});
};
Then test case looks like:
it('should be able to get list of data....', function() {
$scope.to = {};
var vm = initializePageController();
$httpBackend.expectGET(/\/api\/v1\/data...../).respond([
{id: 1, name: 'Data 1'},
{id: 2, name: 'Data 2'}
]);
initializeSelectController(vm.fields[1]);
$httpBackend.flush();
expect($scope.to.options.length).to.equal(2);
});
You could do it a few ways. One option would be to test the controller that contains this configuration. So, if you have the field configuration set to $scope.fields like so:
$scope.fields = [ { /* your field config you have above */ } ];
Then in your test you could do something like:
$controller($scope.fields[0].controller, { mockScope, mockDataService });
Then do your assertions.
I recently wrote some test for a type that uses ui-select. I actually create a formly-form and then run the tests there. I use the following helpers
function compileFormlyForm(){
var html = '<formly-form model="model" fields="fields"></formly-form>';
var element = compile(html)(scope, function (clonedElement) {
sandboxEl.html(clonedElement);
});
scope.$digest();
timeout.flush();
return element;
}
function getSelectController(fieldElement){
return fieldElement.find('.ui-select-container').controller('uiSelect');
}
function getSelectMultipleController(fieldElement){
return fieldElement.find('.ui-select-container').scope().$selectMultiple;
}
function triggerEntry(selectController, inputStr) {
selectController.search = inputStr;
scope.$digest();
try {
timeout.flush();
} catch(exception){
// there is no way to flush and not throw errors if there is nothing to flush.
}
}
// accepts either an element or a select controller
function triggerShowOptions(select){
var selectController = select;
if(angular.isElement(select)){
selectController = getSelectController(select);
}
selectController.activate();
scope.$digest();
}
An example of one of the tests
it('should call typeaheadMethod when the input value changes', function(){
scope.fields = [
{
key: 'selectOneThing',
type: 'singleSelect'
},
{
key: 'selectManyThings',
type: 'multipleSelect'
}
];
scope.model = {};
var formlyForm = compileFormlyForm();
var selects = formlyForm.find('.formly-field');
var singleSelectCtrl = getSelectController(selects.eq(0));
triggerEntry(singleSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(1);
var multiSelectCtrl = getSelectController(selects.eq(1));
triggerEntry(multiSelectCtrl, 'woo');
expect(selectResourceManagerMock.searchAll.calls.count()).toEqual(2);
});
I'm having issue with modifying objects that are adding through angular modal controller
I have
.controller("viewController", function($scope, $modal) {
$scope.allPosts = [
{
id: 1,
owner: "Owner 2",
profile: "images/profile.png",
title: "Book title 1",
image: null,
price: 25,
reply: 2,
fav: 1,
isFaved: false,
content: "test"
},
{
id: 2,
owner: "Owner",
profile: "images/profile2.png",
title: "Ken Follett",
image: "images/book1.jpg",
price: 20,
reply: 12,
fav: 3,
isFaved: true,
content: "The book is in nice"
}
];
$scope.addFav = function(id) {
_.each($scope.allPosts, function(post) {
if(post.id === id) {
post.isFaved = !post.isFaved;
if(post.isFaved) {
post.fav++;
$scope.myFavs.push(post);
} else {
post.fav--;
$scope.myFavs = _.reject($scope.myFavs, function(post) {
return post.id === id;
});
}
}
});
};
$scope.addPost = function() {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
allPosts: function(){
return $scope.allPosts;
}
}
});
};
)
.controller('ModalInstanceCtrl', function ($scope, $modalInstance, allPosts) {
$scope.postId = 50;
$scope.ok = function () {
var temp = {};
temp.id = $scope.postId;
temp.profile = "images/profile.png";
temp.title = $scope.title;
temp.type = $scope.type;
temp.price = $scope.price;
temp.reply = 0;
temp.fav = 0;
temp.isFaved = false;
temp.content = $scope.description;
$scope.allPosts.push(temp);
$scope.postId++;
$modalInstance.close();
};
});
$scope.addFav(id) function works fine with existing $scope.allPosts. However, when I add new object by using the ModalInstanceCtrl, the $scope.allPosts is updated but when it goes to $scope.addFav(id), I can not modified the new object that is pushed in to $scope.allPosts from ModalInstanceCtrl. for example I try to update the fav property in post by using
post.fav++; // console.log(post) shows the fav property is not updated. it remains at 0.
As you don't show the markup I suspect that the ModalInstanceController must be nested within the scope of the viewController. This would explain how the same allPosts is available in both controllers. However the postId will be different on each scope due to the way that javascript's prototypical inheritance works. To overcome this you could define an object on scope something like this:
$scope.posts = {
postId: 0,
allPosts: []
}
Alternatively, and even better imho, define a Posts service that encapsulates all the post behaviours and inject that into both controllers. You are then insulated from any changes to the markup that could muck up the controller inheritance.