Ionic: Passing array elements between controllers - angularjs

I have a controller & view which computes and displays a specific element. When I click a button on the view, I want to open another which shows more information about the item.
What is the best way to pass the element to another controller for display?
Many thanks

You could build a service which get data from one controller and passing it to the other, like so:
.factory('shareData', function(){
var finalData;
return {
sendData: function(data) {
finalData = data;
},
getData: function() {
return finalData;
}
}
});
Another option you have is to create a shared varraible in your main controller('app' controller in ionic)
.controller('app', function($scope){
$scope.data;
})
.controller('app.controller1', function($scope){
$scope.data = "Hello World";
})
.controller('app.controller2', function($scope){
console.log($scope.data); //Hello World
});

Yet another option besides those included by Matan Gubkin is including the array in the $rootScope: this way you will be able to access it from everywhere.

#Matan Gubkin idea is close, but you should use the service to get the specific item that you desire.
.factory('shareData', function(){
var finalData = ["Hello World",'more data'];
return {
sendData: function(data) {
finalData = data;
},
getData: function() {
return finalData;
},
getItem:function(_id) {
return finalData[_id];
}
}
});
then in setting up your controllers
.controller('app.controller', function($scope){
})
.controller('app.controller-list', function($scope,shareData){
$scope.data = shareData.getData();
})
.controller('app.controller-detail', function($scope,$stateParams,shareData){
// use stateParams to get index
console.log(shareData.getItem($stateParams.index)); //
});
setting up your routes
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
controller: 'app.controller'
})
.state('app.controller-list', {
url: "/list",
})
.state('app.controller-detail', {
url: "/detail/:index", // <-- pass in index as stateParam
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/app/list');
});

Related

AngularJS How to access local scope from outside function?

I have this code in my service
orderSewaService.vehicleDetail = function (license_plate) {
//var defer = $q.defer();
var config = {
headers: {
'X-Parse-Application-Id': parseAppId
},
params: {
where: {
vehicle_license_plate: license_plate,
vehicle_status: 'available'
},
limit: 1,
include: 'car_id.car_class_id,pool_id.city_id,partner_id.user_id'
}
}
return $http.get('http://128.199.249.233:1337/parse/classes/vehicle', config).then(function (response) {
var detail = {
license_plate: response.data.results[0].vehicle_license_plate,
photo: response.data.results[0].vehicle_photo,
partner_name: response.data.results[0].partner_id.user_id.full_name,
year: response.data.results[0].vehicle_year,
class: response.data.results[0].car_id.car_class_id.name,
pool_address: response.data.results[0].pool_id.pool_address,
city: response.data.results[0].pool_id.city_id.city_name,
zone_id: response.data.results[0].zone_id.objectId,
car_class_id: response.data.results[0].car_id.car_class_id.objectId
};
return detail;
//defer.resolve(detail);
}, function (error) {
//defer.reject(error);
return error;
});
//return defer.promise;
};
in my controller
$scope.vehicle = {};
orderSewaService.vehicleDetail($routeParams.license_plate).then(function(response){
$scope.vehicle = response;//rendered in view
console.log($scope.vehicle); //log object success
}, function (error) {
console.log(error);
});
console.log($scope.vehicle); //doesn't work //empty object
//My goal is I will call other service function like this
orderSewaService.infoTarif($scope.vehicle.zone_id, $scope.vehicle.car_class_id).then(...);
Already read this access scope data from outside function but looks like to complex or not suit for my simple goal.
How I can access $scope.vehicle outside function or how to achieve my goal ?
And I don't think $rootScope is good solution in this case.
You need to declare $scope.vehicle outside the function call,
somewhere in your controller at the begining,
If it's an array
$scope.vehicle =[];
The problem is with the way this controller code flow works.
$scope.vehicle = {}; //vehicle property is declared and defined as empty obj in the $scope
orderSewaService.vehicleDetail($routeParams.license_plate)
This is an ajax call, js calls this method and then goes to the next line , after the end of this method, i.e.
console.log($scope.vehicle); without waiting for the call to return and populate $scope.vehicle with your response.
So, try this:
In Controller:
`
$scope.vehicle = {};
orderSewaService.vehicleDetail($routeParams.license_plate).then(function(response){
$scope.vehicle = response;//rendered in view
getInfoTarif();
}, function (error) {
console.log(error);
});
function getInfoTarif(){
console.log($scope.vehicle);
orderSewaService.infoTarif($scope.vehicle.zone_id,$scope.vehicle.car_class_id).then(...);
}
`
I think there are two matter of concerns in this question.
Firstly - sync & async methods
Since orderSewaService.vehicleDetail is asynchronous, $scope.vehicle would be null.
If you are not sure what that means, compare the two:
var foo = null;
foo = ['a','b'];
console.log(foo); // ['a','b']
versus
var foo = null;
setTimeout(function(){
foo = ['a','b'];
console.log(foo); // array
}, 500); // or any value including zero
console.log(foo); // null
Conclusively, your code should look like this:
$scope.vehicle = {};
orderSewaService
.vehicleDetail($routeParams.license_plate)
.then(function(response){
$scope.vehicle = response;//rendered in view
console.log($scope.vehicle); //log object success
//My goal is I will call other service function like this
orderSewaService.infoTarif($scope.vehicle.zone_id, $scope.vehicle.car_class_id).then(...);
}, function (error) {
console.log(error);
});
There are a ton of articles and docs that describe this, if you are further interested.
Secondly - load contents before reaching controller
Now, from how you described the problem, it seems like you also want to load the contents of orderSewaService.vehicleDetail based on a URL parameter before it reaches the controller. Otherwise, you will have to call orderSewaService.vehicleDetail and orderSewaService.infoTarif in every controller.
A much cleaner and more common approach is to use ui-router's $stateProvider. Tutorials here
If you run a few examples from their docs, you can inject dependencies into your controller like this:
app.route.js
$stateProvider
.state('vehicles', {
url: '/vehicles',
resolve: {
vehicles: ['VehiclesService', function(VehiclesService){
return VehiclesService.getAll();
}]
},
controller: 'VehiclesListCtrl',
templateUrl: 'vehicles.html'
})
.state('vehicles.detail', {
url: '/vehicles/:vehicleId',
resolve: {
info: ['VehiclesService', '$stateParams', function(VehiclesService, $stateParams){
return VehiclesService.get($stateParams.vehicleId)
.then(function(vehicle){
return orderSewaService.infoTarif(vehicle.zone_id, vehicle.car_class_id)
.then(function(tarif){
return {
vehicle: vehicle,
tarif: tarif
};
});
});
}]
},
controller: 'VehicleDetailCtrl',
templateUrl: 'vehicle.detail.html'
});
vehicle.detail.controller.js
.controller('VehicleDetailCtrl', VehicleDetailCtrl);
VehicleDetailCtrl.$inject = [
'$scope',
'info'
];
function VehicleDetailCtrl(
$scope,
info
) {
console.log('vehicle %o tarif %o', info.vehicle, info.tarif);
}
vehicles.controller.js
.controller('VehiclesCtrl', VehiclesCtrl);
VehiclesCtrl.$inject = [
'$scope',
'vehicles'
];
function VehiclesCtrl(
$scope,
vehicles
) {
console.log('vehicles list %o', vehicles);
}
To access this state, you need to do something like
menu.html
<a ui-sref="vehicles.detail({vehicleId: 1234})">
I purposely did not make vehicles route abstract for illustration purposes. You may want to look into that if you want to create nested state/views.
I hope this helps.

Jasmine testing controller from wrong ui-router state

I have a page where everything works fine. However, my test for this page's controller FeeRuleCtrl, after it tests the code of said controller, goes on and starts testing the controller of a different state. Here's my app.js:
$stateProvider
.state('root', {
url: "/",
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/root') %>",
controller: 'RootCtrl',
resolve: {
feeSuites: function(FeeSuiteCrud, FeeSuite){
console.log('here');
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule', {
abstract: true,
controller: 'FeeRuleCtrl',
template: "<ui-view/>",
resolve: {
feeTypes: function(FeeSuiteCrud, FeeType){
var feeCrud = new FeeSuiteCrud(FeeType)
var promise = feeCrud.query();
return promise.then(function(response){
return response;
})
},
feeSuites: function(FeeSuiteCrud, FeeSuite){
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule.new', {
url: '/new',
controller: 'NewCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Add a New Fee Rule' }
})
.state('fee-rule.edit', {
url: "/edit/:id",
controller: 'EditCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Edit Fee Rule' },
resolve: {
feeRule: function(FeeSuiteCrud, FeeRule, $stateParams){
var feeCrud = new FeeSuiteCrud(FeeRule);
var promise = feeCrud.get($stateParams.id)
return promise.then(function(response){
return response;
});
}
}
});
I have an abstract state, fee-rule, because both the new and edit states share most of the same functionality.
When I go to the page's address, <host>/admin/fee_suites/new, I inspect the network tab and there are 4 server calls made:
api/v3/fee_types
api/v3/fee_suites
api/v3/fee_suites/8?association=fee_rules
api/v3/fee_types/9?association=fee_parameters
The first 2 are resolves in the fee-rule state. I take care of this like so in the test:
beforeEach(function(){
module(function($provide){
$provide.factory('feeSuites', function(FeeSuite){
feeSuite = new FeeSuite({
id: 8,
site_id: 9,
active: true
});
return [feeSuite];
});
$provide.factory('feeTypes', function(FeeType){
feeType = new FeeType({
id: 9,
name: 'Carrier Quotes',
value: 'carrier_quotes'
});
return [feeType];
});
});
inject(function($injector){
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
$controller("FeeRuleCtrl", {
'$scope': scope
});
});
});
The last 2 server calls are made inside FeeRuleCtrl. I test them like so:
beforeEach(function(){
var JSONResponse = {"master":[{"id":29,"fee_suite_id":8,"fee_parameter_id":1,"name":"American Express Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":30,"fee_suite_id":8,"fee_parameter_id":2,"name":"Discover Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":31,"fee_suite_id":8,"fee_parameter_id":3,"name":"MasterCard Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":32,"fee_suite_id":8,"fee_parameter_id":4,"name":"Visa Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_suites/8?association=fee_rules').respond(JSONResponse);
JSONResponse = {"master":[{"id":25,"fee_type_id":9,"name":"UPS Published Quote","value":"ups_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":26,"fee_type_id":9,"name":"FedEx Published Quote","value":"fedex_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":27,"fee_type_id":9,"name":"UPS Negotiated Quote","value":"ups_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":28,"fee_type_id":9,"name":"FedEx Negotiated Quote","value":"fedex_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_types/9?association=fee_parameters').respond(JSONResponse);
$httpBackend.flush();
});
it('should set currentFeeRuleNum', function(){
expect(scope.FeeSuite.currentFeeRuleNum).toEqual(4);
});
When I run my test I get the following error:
Error: Unexpected request: GET /api/v3/fee_suites/
I know it is coming from root state's resolve function feeSuites because the test also prints to the console log the word 'here'.
I cannot figure out why it seems like the test doesn't stop and starts testing the RootCtrl in the root state. Could it have anything to do with the fact that state fee-rule is abstract? Also NewCtrl is defined but it is empty.
After some more googling with different keywords, turns out in my test I need to mock the $state variable inside FeeRuleCtrl. That fixed the problem.

AngularJS: How can I share data across controller?

I want to get title dynamically. How do I share the $scope.title in indexCtrl?
var app = angular.module('app', []);
app.controller('firstCtrl', function($scope){
$scope.title = 'OneFC';
})
app.controller('secondCtrl', function($scope){
$scope.title = 'UFC';
})
app.controller('indexCtrl', function(){
// i want to get the title here depends on the active state in ui-router.
}
We have some ways to do this. rootscope's property can handle this which is same as global var in your app. But I love OOP and I think we should encapsulate the processing of data in a service.
Code for service:
app.factory('titleFactory',function(){
var _title = null;
return {
setTitle: function(title) {
this._title = title;
},
getTitle: function() {
return this._title;
}
}
});
Now in any controller you just need inject and call get or set title to handle your data.
Set title value (same for secondCtrl):
app.controller('firstCtrl', function($scope,titleFactory){
titleFactory.setTitle('OneFC');
})
Title now can be accessed by all other controllers like this:
app.controller('indexCtrl', function(){
$scope.title = titleFactory.getTitle();
});
I would recomend passing it as Data inside your routes definitions (you can do that in case you were not aware of) just like this:
$stateProvider.state("page1", {
url: "/page1",
data: {
title: "Your Title Here"
},
views: {
}
}).state("page2", {
url: "/page2",
data: {
title: "Your Other Title Here"
},
views: {
}
})
Afterwards, you can use it in your controller like this:
app.controller('indexCtrl', function($state){ //Note that you inject state
$scope.pageTitle= $state.data.title;
}
Hope this works for you
Regards
try to use $rootScope.title?
app.controller('indexCtrl', ['$rootScope','$scope',function($rootScope, $scope){
$rootScope.$on("toindexCtrl", function (e) {
$scope.$apply(function () {
$scope.title = $rootScope.title;
});
});
}])
app.controller('secondCtrl', ['$rootScope','$scope', function($rootScope, $scope){
$rootScope.title = 'UFC';
$rootScope.$broadcast('toindexCtrl');
}])
This code work for me. I also use $broadcast to listen when the value changed.

How to update angular ng-src with image url returned from factory/controller promise?

In my app I have a controller that takes a user input consisting of a twitter handle from one view and passes it along through my controller into my factory where it is sent through to my backend code where some API calls are made and ultimately I want to get an image url returned.
I am able to get the image url back from my server but I have been having a whale of a time trying to append it another view. I've tried messing around with $scope and other different suggestions I've found on here but I still can't get it to work.
I've tried doing different things to try and get the pic to be interpolated into the html view. I've tried injecting $scope and playing around with that and still no dice. I'm trying not to use $scope because from what I understand it is better to not abuse $scope and use instead this and a controller alias (controllerAs) in the view.
I can verify that the promise returned in my controller is the imageUrl that I want to display in my view but I don't know where I'm going wrong.
My controller code:
app.controller('TwitterInputController', ['twitterServices', function (twitterServices) {
this.twitterHandle = null;
// when user submits twitter handle getCandidateMatcPic calls factory function
this.getCandidateMatchPic = function () {
twitterServices.getMatchWithTwitterHandle(this.twitterHandle)
.then(function (pic) {
// this.pic = pic
// console.log(this.pic)
console.log('AHA heres the picUrl:', pic);
return this.pic;
});
};
}]);
Here is my factory code:
app.factory('twitterServices', function ($http) {
var getMatchWithTwitterHandle = function (twitterHandle) {
return $http({
method: 'GET',
url: '/api/twitter/' + twitterHandle
})
.then(function (response) {
return response.data.imageUrl;
});
};
return {
getMatchWithTwitterHandle: getMatchWithTwitterHandle,
};
});
this is my app.js
var app = angular.module('druthers', ['ui.router']);
app.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('/', {
url: '/',
templateUrl: 'index.html',
controller: 'IndexController',
controllerAs: 'index',
views: {
'twitter-input': {
templateUrl: 'app/views/twitter-input.html',
controller: 'TwitterInputController',
controllerAs: 'twitterCtrl'
},
'candidate-pic': {
templateUrl: 'app/views/candidate-pic.html',
controller: 'TwitterInputController',
controllerAs: 'twitterCtrl'
}
}
});
});
Here is the view where I want to append the returned image url
<div class="col-md-8">
<img class="featurette-image img-circle" ng-src="{{twitterCtrl.pic}}"/>
</div>
You can do as
app.controller('TwitterInputController', ['twitterServices', function (twitterServices) {
this.twitterHandle = null;
// added
this.pic = '';
// when user submits twitter handle getCandidateMatcPic calls factory function
this.getCandidateMatchPic = function () {
twitterServices.getMatchWithTwitterHandle(this.twitterHandle)
.then(function (pic) {
// this.pic = pic
// console.log(this.pic)
console.log('AHA heres the picUrl:', pic);
return this.pic;
});
};
}]);
html
<div class="col-md-8">
<div ng-controller='TwitterInputController as twitterCtrl'>
<img class="featurette-image img-circle" ng-src="{{twitterCtrl.pic}}"/>
</div>
</div>
hope this may help you
Here is working demo http://jsfiddle.net/r904fnb3/2/
angular.module('myapp', []).controller('ctrl', function($scope, $q){
var vm = this;
vm.image = '';
vm.setUrl = function(){
var deffered = $q.defer();
setTimeout(function(){
deffered.resolve('https://angularjs.org/img/AngularJS-large.png');
}, 3000);
//promise
deffered.promise.then(function(res){
console.log(res);
vm.image = res;
});
};
});
hope this may help you

How to create an object with properties in the resolve parameter in the route provider in Angular?

I want to pre-load data in my controller. I am doing this using resolve in the routeprovider:
.when('/customers', {
controller: 'CustomerController', templateUrl: '/Customer/Index', resolve: {
countries: CustomerController.loadCountries,
genders: CustomerController.loadGenders,
}
})
As you can see I have two objects which will be injected into my controller, countries and gender. All this works fine.
What I want to do is, I want those objects to be part of one object: listData. I've tried:
.when('/customers', {
controller: 'CustomerController', templateUrl: '/Customer/Index', resolve: {
listData: {
countries: CustomerController.loadCountries,
genders: CustomerController.loadGenders
}
}
})
but this doesn't work: Argument 'fn' is not a function, got Object.
What is the right syntax / approach to accomplish this?
If you pass an object to a key, you must have a function as the value :
listData: function () {
// you need to inject
var deferred = $q.defer();
var listData = {};
// these should be services, btw !
CustomerController.loadCountries.then(function (countries) {
listData.countries = countries;
// resolve when you have both
if (listData.genders) deferred.resolve(listData);
});
CustomerController.loadGenders.then(function (genders) {
listData.genders = genders;
if (listData.countries) deferred.resolve(listData);
});
return deferred.promise;
}

Resources