I am using ui-router to manage various states of my site. I have used resolve to pass data to header and home controller as displayed in following code. So now I need to update the value of resolved data from HomeController and this change should reflect across to HeaderController too.
var myapp = angular.module('myapp', ["ui.router"]);
myapp.service("DataService", [function() {
var data = { title: 'Some Title' };
this.get = function() {
return data;
};
}]);
myapp.controller("HeaderController", ["data", function(data) {
var vm = this;
vm.title = data.title;
}]);
myapp.controller("HomeController", ["data", function(data) {
var vm = this;
vm.title = data.title;
vm.updateTitle = function() {
// update the resolved data here so the header and home view
// updates with new data.title
data = { title: "Another title" };
// i know i can do vm.title = data.title; to update home view.
// But would be nice to globally update and reflect that change
// on all controllers sharing the same resolved data
};
}]);
myapp.config(function($stateProvider){
$stateProvider
.state({
name: "root",
abstract: true,
views: {
"header": {
templateUrl: "header.html",
controller: 'HeaderController as vm'
}
},
resolve: {
data: ['DataService', function(DataService) {
return DataService.get();
}]
}
})
.state({
name: "root.home",
url: "",
views: {
"content#": {
templateUrl: "home.html",
controller: "HomeController as vm"
}
}
})
});
PS:
Before looking into resolve, I was injecting service directly into the controller so please do not suggest on doing that.
EDIT: Plunkr updated and now works as expected.
Here is link to plunkr
Lesson Learnt:
Angular only watches the object that is assigned to the scope, and keeps separate reference of the objects. I mean:
data = { title: 'some title' };
vm.data = data;
vm.title = data.title;
data.title = 'another title';
{{vm.title}} // some title
/////////////////
data = { title: 'some title' };
vm.data = data;
data.title = 'another title';
{{vm.data.title}} // another title
You should take an advantage of the variable reference, where you should bind your HeaderController data to vm.data = data
Another incorrect thing is data = { title: "Another title" }; which would create an data object with new reference, and the reference of service object will lost. Instead of that you should do data.title = 'Another title';
header.html
{{vm.data.title}}
HeaderController
myapp.controller("HeaderController", ["data", function(data) {
var vm = this;
vm.data = data;
}]);
Update updateTitle method code to below.
vm.updateTitle = function() {
// update the resolved data here so the header updates with new data.title
vm.data.title = "Another title";
};
Demo here
I'd say that rather than playing with actual object reference, you should have setTitle function inside your factory, from updateTitle you will call that setter method which will update title. But in that case you need to again add the service reference on both controller. If its static data then there is no need to pass them by having resolve function. I'd loved to inject the service inside my controllers and then will play with data by its getter & setter.
Preferred Approach Plunkr
Related
I have below code:
vm.data = [{name: 'test-1'},{name: 'test-2'}];
function addRecords(data) {
vm.data.push(data);
}
function openPopup() {
$uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
}).result.then(addRecords);
}
Trying to mock this, Below are the declarations:
let allData = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
beforeEach(inject(function (_$q_, _$rootScope_, _$componentController_, _$uibModal_) {
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
controller = _$componentController_;
$uibModal = _$uibModal_;
spyOn($uibModal, 'open').and.returnValue({
result: function() {
return $q.when(data);
}
});
vm = controller('bvcListings', {
$q,
data: allData,
$uibModal
});
$scope.$apply();
}));
describe('openPopup', function () {
it('should add records on modal results', function () {
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
});
});
Expectation is, it should add: {name: 'test-3'} as result to existing array.
Spy on modal open is working fine, but after results fetched, its not entering addRecords function. What am i doing wrong?
What changes need to be done here to get inside callback function after results retrieved.
.result.then callback method will get call only when you call modalInstance.close method, also don't forgot to pass data from close method something like modalInstance.close(data).
Before proceeding to test you need to do one change inside openPopup function. It should return $uibModal.open which basically returns newly created modal's instance. Thereafter you can easily have a control over modal to call dismiss/close method whenever needed.
function openPopup() {
vm.modalInstance = $uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
});
vm.modalInstance.result.then(addRecords);
}
Spec
$uibModal = _$uibModal_;
var data = {name: 'test-3'};
//creating fake modal which will help you to mock
var fakeModal = {
result: {
then: function(confirmCallback) {
//Store the callbacks
this.confirmCallBack = confirmCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog
this.result.confirmCallBack( item );
}
};
spyOn($uibModal, 'open').and.returnValue(fakeModal);
describe('It should data to vm.data when popup closed', function () {
it('should add records on modal results', function () {
vm.data = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
vm.modalInstance.close(data);
expect(vm.data.length).toBe(4);
expect(vm.data[3]).toBe(data);
});
});
Note: fakeModal has been referred from this post
Continuing with #Pankajs answer.
Here is a tweak which i made and got that worked.
function openPopup() {
vm.modalInstance = $uibModal.open({
templateUrl: 'modal-popup/modal-popup.html',
controller: 'ModalPopupController',
controllerAs: 'vm',
resolve: {
id: _.constant('123')
}
}).result.then(addRecords);
}
Spec
describe('modalpopup', function () {
it('should add records on modal results', function () {
vm.data = [{name: 'test-1'},{name: 'test-2'}];
let data = {name: 'test-3'};
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
vm.modalInstance.close(data);
expect(vm.data.length).toBe(4);
expect(vm.data[3]).toBe(data);
});
});
Worked like charm for me. And i consier Pankajs answer as well which was almost 90% gave solution to my problem.
add $rootScope.$digest(); to resolve promises (like $q.when())
vm.openPopup();
expect($uibModal.open).toHaveBeenCalled();
$rootScope.$digest(); >> triggers your callback
I'm dividing my functions/objects into service and factory methods, and injecting them into my controller patentTab. I had a code for a tab panel which I originally placed within the controller patentTab that worked.
Now I have placed this code in a factory method and for some reason the content isn't loading. Console log shows no errors, and when I click the relative tab the correct URL is loaded, but the content doesn't change. Is there an issue with my array in the factory? If not, what is the reason?
Orginal code
app.controller('patentTab', function($scope, $http){
$scope.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
$http.get('../json/patent-info.json').then(function(response){
$scope.patentData = response.data.patentInfo;
})
$scope.currentTab = 'patent-info.htm';
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
}
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
});
New code (with issue)
app.controller('patentCtrl', ['$scope', '$http', 'patentTabFactory', function($scope, $http, patentTabFactory) {
$http.get('http://localhost:8080/Sprint002b/restpatent/').then(function(response) {
$scope.patents = response.data;
});
$scope.loadPatentItem = function(url) {
$scope.patentItem = url;
}
$scope.tabs = patentTabFactory.tabs;
$scope.currentTab = patentTabFactory.currentTab;
$scope.onClickTab = patentTabFactory.onClickTab;
$scope.isActiveTab = patentTabFactory.isActiveTab;
}]);
app.factory('patentTabFactory', function() {
var factory = {};
factory.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
factory.currentTab = 'patent-info.htm';
factory.onClickTab = function (tab) {
factory.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
console.log(tab.url);
}
factory.isActiveTab = function(tabUrl) {
return tabUrl == factory.currentTab; //for styling purposes
}
return factory;
});
You not calling factory.onClickTab() method from your controller.
It should be like :
$scope.onClickTab = function(currentTab) {
patentTabFactory.onClickTab(currentTab);
$scope.currentTab = patentTabFactory.currentTab;
};
and, for isActiveTab, Like :
$scope.isActiveTab = patentTabFactory.isActiveTab(currentTab);
Here is a plunker where I am using a factory. The only changes I have done are:
1. Place the factory file before the app script file.
2. Use a separate declaration for factories and then inject it in the app.
var factories = angular.module('plunker.factory', []);
factories.factory('patentTabFactory', function() {
// Factory bits
};
I have injected the factories in the app.
var app = angular.module('plunker', ['plunker.factory']);
Here is a working plunker for that. PlunkR
I'm creating a shared service called Popup Service so that I can share data between two Controllers i.e BankController and CreateBankController.
I'm able to set the object that I need to share in the PopupService. The popupService is called within BankController (while clicking the Edit link on a data row) to set the object to be shared.
The CreateBankController is then invoked by the popup form, but in the CreateBankcontroller I'm not able to access the shared data from the PopupService, I get an error that objectToEdit variable is not defined.
Please can you advise how I can make the PopupService share data between the two controllers
Can the data in the Popup shared service end up over being overridden by competing user actions and show stale data
WORKING PLUNKER
https://plnkr.co/edit/y8dZNU?p=preview
Retrieving data in CreateBankController
CreateBankController.$inject = ['PopupService'];
function CreateBankController(PopupService) {
var vm = this;
var data = {
bankName: "",
bankAddress: "",
};
debugger;
if (PopupService.getObjectToEdit() !== null) {
data = PopupService.getObjectToEdit();
}
SETTING THE SHARED DATA IN THE BankController
$scope.bankGrid = {
dataSource: queryResult,
columns: [{
dataField: 'orderID',
caption: 'Order ID'
}, {
width: 50,
alignment: 'center',
caption: 'Edit',
cellTemplate: function(container, options) {
$('<a/>').addClass('dx-link')
.text('Edit')
.on('dxclick', function() {
PopupService.addObjecToEdit(options.data);
$scope.showPopup = true;
})
.appendTo(container);
}
shared data service - POPUP SERVICE
(function () {
'use strict';
angular
.module('myApp')
.factory('PopupService', PopupService);
function PopupService() {
var popupInstance = {};
var objectToEdit = {};
var service = {
addObjecToEdit : addObjecToEdit,
getObjectToEdit: getObjectToEdit,
showPopup: showPopup,
hidePopup: hidePopup
};
return service;
//Functions
function addObjecToEdit(pObjectToEdit){
objectToEdit = pObjectToEdit;
}
function getObjectToEdit() {
return objecTtoEdit;
}
function showPopup(){
popupInstance.showPopup();
}
function hidePopup(){
popupInstance.hidePopup();
}
}
}());
You have a typo in the service:
function getObjectToEdit() {
return objecTtoEdit;
}
change to:
function getObjectToEdit() {
return objectToEdit;
}
So I'm trying to get use to angular and having some troubles trying to call a directive (google maps https://github.com/davidepedone/angular-google-places-map) and performing reverse geocoding. I think this would be a more general directives questions though.
I am trying to call a function within the directive to update the google maps place information as well as map. The way I'm thinking in my head is that I would need to pass a variable through the controller, scope that variable to the directive and then the directive will run the function?
UPDATED:
<div class="row">
<places-map selectedid="selectid(place.id)"></places-map>
</div>
<button ng-click="selectid(place.id)">{{place.id}}</button> </div>
With this click I suppose to go to the controller,
$scope.selectid= function (pickplaceid){
$scope.selectedid(pickplaceid);
}
Then the selectplaceid should be in the scope variables of the directive.
scope: {
customCallback: '&?',
picked: '=?',
address: '=?',
fallback: '=?',
mapType: '#?',
readonly: '#?',
responsive: '#?',
draggable: '#?',
toggleMapDraggable: '=?',
placeNotFound: '=?',
updateMarkerLabel: '=?',
selectedid:'='
},
and can call my method as so:
link: function ($scope, element, attrs, controller) {
//everything else from angular-google-places
$scope.selectedid= function (selectedplace)
{
///Whatever I want to do to geocode with the placeid
}
I think I may just be doing this completely wrong having really no luck with the directive call at all. I'm trying to update my map based on the location that I click and pull out the information of that specific place from the placeId. Any help would be great.
I have almost same thing working, and I solved it with a Service that receives a placeId (in my code it's called addressId, but it's the placeId Google Maps expects). In my service, I use the placeId to retrieve address details:
app.service('AddressDetailsService', ['$q', function ($q) {
this.placeService = new google.maps.places.PlacesService(document.getElementById('map'));
this.getDetails = function (addressId, address) {
var deferred = $q.defer();
var request = {
placeId: addressId
};
this.placeService.getDetails(request, function (place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
address.placeId = addressId;
address.street = getAddressComponent(place, 'route', 'long');
address.countryCode = getAddressComponent(place, 'country', 'short');
address.countryName = getAddressComponent(place, 'country', 'long');
address.cityCode = getAddressComponent(place, 'locality', 'short');
address.cityName = getAddressComponent(place, 'locality', 'long');
address.postalCode = getAddressComponent(place, 'postal_code', 'short');
address.streetNumber = getAddressComponent(place, 'street_number', 'short');
address.latitude = place.geometry.location.lat();
address.longitude = place.geometry.location.lng();
if (address.streetNumber) {
address.streetNumber = parseInt(address.streetNumber);
}
deferred.resolve(address);
}
});
return deferred.promise;
};
function getAddressComponent(address, component, type) {
var country = null;
angular.forEach(address.address_components, function (addressComponent) {
if (addressComponent.types[0] === component) {
country = (type === 'short') ? addressComponent.short_name : addressComponent.long_name;
}
});
return country;
}
}]);
Then you inject it and call the service from your directive. This is the one I use, you might need to adapt it, but you see the idea. Instead of a link function, I use a controller for the directive:
.directive('mdAddressDetails', function mdAddressDetails() {
var directive = {
restrict: 'EA',
scope: {
address: '='
},
bindToController: true,
templateUrl: 'modules/address/addressDetails.html',
controller: AddressDetailsController,
controllerAs: 'dir'
};
AddressDetailsController.$inject = ['AddressDetailsService', '$q'];
function AddressDetailsController(AddressDetailsService, $q) {
var dir = this;
dir.selectAddress = selectAddress;
function selectAddress(address) {
if ((address) && (address.place_id)) {
AddressDetailsService.getDetails(address.place_id, dir.address).then(
function (addressDetails) {
dir.address = addressDetails;
}
);
}
}
}
return directive;
});
And then you just call the directive with the wanted parameter:
<md-address-details address="myAddress"></md-address-details>
How does one pass {{row.getProperty(col.field)}} into ng-click? What happens is the id does not get propagated back, but the grid render correctly with the id.
code:
var app = angular.module('testing',['ngGrid']);
app.config(['$locationProvider', function($locationProvider)
{
$locationProvider.html5Mode(true);
}]);
app.controller('TestCtrl',function($scope)
{
$scope.details = []; //whatever dummy data
$scope.loadById = function(id)
{
$window.location.href= 'newPage/?id='+id;
};
$scope.gridOptions =
{
data: 'details',
columnDefs:[{field:'id',DisplayName:'id',
cellTemplate:'<div class="ngCellText" ng-class="col.colIndex()"><a ng-click="loadById({{row.getProperty(col.field)}})">{{row.getProperty(col.field)}}</a></div>'
}]
};
});
You can just pass row in ng-click, instead of only the value outputted by the double brackets expression.
Your cellTemplate becomes this
cellTemplate:'<div class="ngCellText" ng-class="col.colIndex()"><a ng-click="loadById(row)">{{row.getProperty(col.field)}}</a></div>'
Your function becomes this
$scope.loadById = function(row) {
window.console && console.log(row.entity);
$window.location.href= 'newPage/?id='+ row.entity.id;
};