When I return from my service call I seem unable to update my view. Why does 'not broken' never get out putted to the console?
the services returns [{test: 'service workies'}]
app.controller('foo-controller', ['fooService','$scope', function (fooService,$scope) {
var ctrl = this;
ctrl.Results = [{ test: 'no workies' }];
ctrl.Search = function () {
fooService.GetFoos().then(function (result) {
console.log('test');
console.log(ctrl.Results);
ctrl.Results = result;
console.log(ctrl.Results);
$scope.$apply(function () {
console.log('not broken');//never fires!!
ctrl.Results = [{test : 'workies' }]
});
});
};
return ctrl;
}]);
app.directive('fooLogo', function () {
return {
restrict: 'E',
templateUrl: './App/Templates/foo.html',
controller: 'foo-controller',
controllerAs: 'f'
};
});
edit foo service
.service('fooService', ['$http', function ($http) {
return $http.get("https://www.googleapis.com/books/v1/volumes?q=harry+potter").then(
function(result){ return [{ test: 'service workies'}]},
function(error) { return [{test: 'service call no workies'}] );
I see a few issues in your code. I don't see anywhere inside fooService where GetFoos() is declared, so that's one issue. Try the following:
app.controller('MainCtrl', ['$scope', 'BookQueryService',
function($scope, BookQueryService) {
$scope.search = function() {
BookQueryService.getBooks().then(function(data) {
$scope.books = data.data.items;
});
};
// call immediately for the sake of this example
$scope.search();
}
]);
app.service('BookQueryService', ['$http',
function($http) {
var service = {};
service.getBooks = function() {
return $http.get("https://www.googleapis.com/books/v1/volumes?q=harry+potter");
};
return service;
}
]);
app.directive('myBookList', function() {
return {
restrict: 'E',
templateUrl: 'BookList.html',
controller: 'MainCtrl'
}
});
With the following html:
<body>
<my-book-list></my-book-list>
</body>
And the following directive template:
<div>
<ul>
<li data-ng-repeat="book in books">
{{book.volumeInfo.title}}
</li>
</ul>
</div>
Here's a plunker with a working example:
http://plnkr.co/edit/KJPUWj0ghDi1tyojHNzI?p=preview
Is anything inside the fooService.GetFoos().then(function(result){...}) being run? If the code you posted is all there is for fooService, then it looks like there is no .GetFoos method & therefore nothing inside the following .then would get run.
Try adding a .error after the original .then that is chained onto fooService.GetFoos:
fooService.GetFoos().then(function (result) {
// your code
}).error(function (data, status){
console.log("Error!\t", status);
};
This will help you figure out what exactly is going on. Whenever your using any sort of promise, make sure you have a .catch or .error — they can save you a lot of trouble when debugging. Check out angular's $http documentation for more details.
Additionally, it looks like the original call to $scope.$apply() is unnecessary. You would only use that if you want to run a function outside of angular, or if you manually want to trigger the digest cycle (if that were the case calling $scope.$digest() explicitly would be much more appropriate than $scope.$apply.
Check out this blog post about when to use $scope.$apply and the $scope.$apply documentation page for more info
Related
I am using ui bootstrap and use tmhDynamicLocaleProvider to get the correct translations for days,month etc in my datepicker.
It works all fine as long as I load the locale documents from online. If I load documents locally (I need to do it locally), it doesn't load the correct translations after minification.
My code looks as follows
app.config(['tmhDynamicLocaleProvider', function(tmhDynamicLocaleProvider) {
tmhDynamicLocaleProvider.localeLocationPattern('app/datepickerLocale/locale.{{locale}}.js');
}])
app.config(....{
$provide.decorator('uibDatepickerDirective', ['$delegate', function($delegate) {
angular.forEach($delegate, function (directive) {
var originalCompile = directive.compile;
var originalLink = directive.link;
if (originalCompile) {
directive.compile = function () {
return function (scope) {
scope.$on('$localeChangeSuccess', function () {
scope.move(0);
});
originalLink.apply(this, arguments);
};
}
}
});
return $delegate;
}]);
})
I resolve it in a state
.state('main', {
url: '/{language:[a-z]{2}}',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl',
controllerAs: 'mainCtrl',
resolve: {
localeLanguage: ['resolveService', '$stateParams', function(resolveService, $stateParams){
resolveService.resolveLocale($stateParams)
}]
}
})
the service looks as follows
resolveLocale: function(stateParams){
var deferred = $q.defer();
if(stateParams.language){
tmhDynamicLocale.set(stateParams.language);
deferred.resolve(1);
} else {
deferred.resolve(2);
}
return deferred.promise;
}
it all works fine until minification. After that I obtain the error, that
the script was not able to be obtained. (GET error).
I assume that the while invoking the function the script locale.en.js has not been loaded yet. Or am I wrong here?
How can I solve this?
How to bind a directive to a controller via a service update ?
I want to create the possibility to update a cart(the service) via a directive(add to cart button) and then the controller (that display the cart) will update its view.
Despite the fact that I added a watch on the service itself my controller is not been updated.
Of course it will be good if the controller and the directive doesn't share the same scope (transclude: true in the directive)
The service:
angular.module('stamModule', [])
.factory('valueService', function () {
var factory = {
data: {value: 1000},
inc: inc,
getData: getData
};
function inc() {
this.data.value++;
}
function getData() {
return this.data;
}
return factory;
})
the directive:
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button>Inc</button>',
link: linkFnc
};
function linkFnc(scope, el) {
el.on('click', function () {
valueService.inc();
});
}
return directive;
})
The controller:
.controller('FirstController', function ($scope, valueService) {
var vm = this;
vm.serv = valueService;
$scope.$watch('vm.serv.getData()', function (newValue) {
console.log("watch");
console.log(newValue);
});
})
The html:
<body ng-app="stamModule">
<hr>
<div ng-controller="FirstController as vm">
<p>{{vm.serv.data}}</p>
<button-directive ></button-directive>
</div>
here's a demo:
https://jsfiddle.net/07tp4d03/1/
Thanks
All your code needed was a little push. No need for event broadcasting or anything like that.
The problem was, that the click event listener was working outside Angular's digest loop, and thus Angular watch wasn't working for you.
If you change your directive's code to the following, it will work.
.directive('buttonDirective', function (valueService) {
var directive = {
restrict: 'E',
template: '<button ng-click="inc()">Inc</button>',
link: linkFnc
};
function linkFnc(scope) {
scope.inc = function() {
valueService.inc();
};
}
return directive;
})
Here is a fork of your fiddle that works
I am having some trouble getting to the controller for my state param. I am using the correct state to link to the next view.
<td><a ui-sref="orders({customerId: cust.id})">View Orders</a></td>
In my config file I am referencing the state that name and the route params. I commented out the resolve object for now. My goal is to get into the controller then pass the correct data. Notice that I am using controllerAs
My initial thought was ({customerId: ctrl.cust.id }) However that did not change the url route.
The url is changing to match the url name but is not connecting to the controller and is not giving me the view.
(function() {
'use strict';
angular
.module('app.orders')
.config(config);
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
customerFactory: 'customerFactory',
customerInfo: function( customerFactory, $stateParams) {
return customerFactory.getCustomers($stateParams.id);
}
}
************** my main problem is the resolve. This is blocking me from getting into the next controller. *****************
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
})
};
})();
For now my controller is very small. I just want to connect to it. I have checked my networks tab and see GET for the files.
(function() {
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders";
vm.customer = null;
}
}());
I have referenced my module in the main javascript file.
(function () {
'use strict';
angular.module('app', ['app.services',
'app.customers',
'app.orders','ui.router']);
})();
When I comment out the resolve I am able to access the controller. So I know the problem is in the resolve. Here is my service. I am making a request to a Json file with $http request and using .then
Updates Here is my refactored service call I am getting back the correct customer in the console each time.
(function() {
angular
.module('app.services',[])
.constant('_', window._)
.factory('customersFactory', customersFactory);
function customersFactory($http, $log) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers(){
return $http.get('./Services/customers.json',{catch: true})
.then(getCustomerListComplete)
.catch(getCustomerListFailed);
function getCustomerListComplete(response) {
console.log('response.data',response.data);
return response.data;
}
function getCustomerListFailed(error) {
console.log('error', error);
}
}
function getCustomer(id) {
var url = './Services/customers.json';
return $http.get(url, {
catch: true
})
.then(function(response) {
console.log('promise id',id);
var data = response.data;
for(var i =0, len=data.length;i<len;i++) {
console.log('data[i].id',data[i].id);
if(data[i].id === parseInt(id)) {
console.log('data[i]', data[i]);
return data[i];
}
}
})
}
}
}());
There is a working example with your code
It is very hard to guess what is wrong. Based on suggestion I gave you here Have a expression error in ui-sref ... your code seems to be completely valid.
I placed your stuff into this app.orders.js file (the ONLY change is templateUrl path, just for plunker purposes):
angular
.module('app.orders', ['ui.router'])
'use strict';
angular
.module('app.orders')
.config(['$stateProvider', config]);
//config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
//templateUrl: './components/orders/orders.html',
templateUrl: 'components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl'
// resolve: {
// customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
// return customerFactory.getCustomers($stateParams.id);
// }]
// }
})
};
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
OrdersController.$inject = ['$stateParams'];
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = null;
}
And this is the working template components/orders/orders.html:
<div >
<h3>current state name: <var>{{$state.current.name}}</var></h3>
<h5>title</h5>
<pre>{{ctrl.title}}</pre>
...
When I call it like this:
<li ng-repeat="cust in [{id:1}, {id:2}]"
><a ui-sref="orders({customerId: cust.id})">View Orders - cust ID == {{cust.id}}</a>
</li>
Check it in action here
So, whil my previous answer was about make the state working without resolve, now we will observe few adjustments (and one fix) to make even resolve working.
There is a working plunker, extending the previous one.
FIX
The only fix, the most important change come from this definition:
angular
.module('app.services',[])
.factory('customersFactory', customersFactory);
see the plural in the factory name, the 'customersFactory'. While here:
...my main problem is the resolve. This is blocking me from getting into the next controller....
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
we ask for 'customerFactory' (singular, no s in the middle)
Few improvements:
So, this would be our adjusted state def:
$stateProvider
.state('orders',{
// INTEGER is here used to later easily use LO_DASH
url:'/customers{customerId:int}', // int is the type
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
// wrong name with 's'
//customerId:[ '$stateParams','customerFactory',
// we use customer, because we also changed the factory
// implementation - to return customer related to
// $statePrams.customerId
customer:[ '$stateParams','customersFactory',
function( $stateParams, customersFactory) {
return customersFactory
//.getCustomers($stateParams.id)
.getCustomer($stateParams.customerId)
;
}]
}
})
Now, this is our adjusted factory, and its new method getCustomer
angular
.module('app.services', [])
.factory('customersFactory', customersFactory);
customersFactory.$inject = ['$http', '$log', '$q', '$stateParams'];
function customersFactory($http, $log, $q, $stateParams) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers() {
// see plunker for this, or above in question
}
// new function
function getCustomer(id) {
var url = "customer.data.json";
return $http
.get(url, {
catch: true
})
.then(function(response){
var data = response.data;
var customer = _.find(data, {"id" : id});
return customer;
})
;
}
}
this is our data.json:
[
{
"id" : 1, "name": "Abc", "Code" : "N1"
},
{
"id" : 2, "name": "Def", "Code" : "N22"
},
{
"id" : 3, "name": "Yyz", "Code" : "N333"
}
]
And here we have controller:
OrdersController.$inject = ['$stateParams', 'customer'];
function OrdersController($stateParams, customer) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = customer;
}
a view to show customer
<h3>customer</h3>
<pre>{{ctrl.customer | json}}</pre>
Check it here in action
I am trying to create angular directive whom can run alone without a Controller, so I can set it anywhere I want, without adding the model to the controller.
The code is fairly simple:
App.directive('ngdPriceWithCurrencySelect',['CurrenciesService' ,function (CurrenciesService) {
return {
restrict: 'E',
replace: true,
scope: true,
link: function ($scope, $element, $attrs) {
$scope.currencies = CurrenciesService.getData();
$scope.$watch('currencies', function(newValue, oldValue){
console.log($scope.currencies);
});
},
template: '<select>\n\
<option ng-repeat="currency in currencies">{{currency.cur_name_he}}</option>\n\
</select>'
}
}]);
The Service actually return the data and the console.log($scope.currencies) shows object with the currencies.
but the repeater is not runing and I am not getting the result I want.
I thought this might be a scope problem, but I can't find a way to see the scope itsel. (angularjs batarang is not working in version 1.3+)
the problem can be in the Service as well so I am giving the service code:
App.service('CurrenciesService', ["$http", "$q", function ($http, $q) {
var service = {
returnedData: [],
dataLoaded: {},
getData: function (forceRefresh) {
var deferred = $q.defer();
if (!service.dataLoaded.genericData || forceRefresh){
$http.get("data/currencies").success(function (data) {
angular.copy(data, service.returnedData)
service.dataLoaded.genericData = true;
deferred.resolve(service.returnedData);
});
}
else{
deferred.resolve(service.returnedData);
}
return deferred.promise;
},
};
service.getData();
return service;
}]);
here is a JS fiddle for testing it: http://jsfiddle.net/60c0305v/2/
Your service returns a promise, not the data itself. You need to set a function for when the service resolves the promise:
link: function ($scope, $element, $attrs) {
// waiting for the service to resolve the promise by using the done method
CurrenciesService.getData().then(function(data) {
$scope.currencies = data;
});
$scope.$watch('currencies', function(newValue, oldValue){
console.log($scope.currencies);
});
}
Check this fiddle
I have a service that grabs JSON data for me and hands it off to a controller:
Service snippet:
...
getP2PKeywordData: function(global_m, global_y) {
// Return the promise
return $http({
url: base_url + 'get/P2P/kwords/',
method: "GET",
// Set the proper parameters
params: {
year: global_y,
month: global_m
}
})
.then(function(result) {
// Resolve the promise as the data
return result.data;
},
function(data) {
// Error handling
});
}
...
The controller successfully grabs the data, which I have tested with a console.log underneath the $scope.d3Data = data; line.
Controller snippet:
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore) {
$rootScope.$on('updateDashboard', function(event, month, year) {
updateDashboard(month, year);
});
var updateDashboard = function(month, year) {
requestService.getP2PKeywordData(month, year).then(function(data) {
$scope.d3Data = data;
});
};
updateDashboard($cookieStore.get('month'), $cookieStore.get('year'));
}]);
The controller is hooked up to a d3-cloud directive (d3 word cloud) that actually appends the proper svg elements and draws the word cloud with the data. However, for some reason the controller above isn't passing the $scope.d3Data to the directive.
This is confusing because when I hardcode in an array of data into the controller, something like this...
$scope.d3Data = [
{
'kword': 'a',
'count': 20,
},{
'kword': 'b',
'count': 10,
...
... it connects to the directive perfectly!
Directive snippet:
myApp.directive('d3Cloud', ['$window',
'd3Service',
'd3Cloud',
function($window,
d3Service,
d3Cloud) {
return {
restrict: 'EA',
scope: {
data: '=',
label: '#'
},
link: function(scope, element, attrs) {
d3Service.d3().then(function(d3) {
window.onresize = function() {
scope.$apply();
};
scope.$watch(function() {
return angular.element($window)[0].innerWidth;
}, function() {
scope.render(scope.data);
});
scope.render = function(data) {
HTML snippet:
<div class="col-md-6">
<div class="module">
<div class="inner-module" ng-controller="DownloadsCloudCtrl">
<div class="module-graph">
<d3-cloud data="d3Data"></d3-cloud>
</div>
</div>
</div>
</div>
What have I tried:
I tried to add a manual $scope.$apply() after the $scope.d3Data = data; line in the controller. This, oddly, worked the first time I did it, but on every page refresh after that I got a "$digest already in progress" error (which was to be expected...).
In order to fix the $digest error, I tried encapsulating my $apply function in a $timeout code chunk, and even the dreaded $$phase conditional. Both of these solutions fixed the console error, but failed to solve the original problem of passing the data from the controller to the directive.
TL;DR: I'm fairly lost. Ideas on where to troubleshoot next?
It seems you are treating the response as a promise twice. So once in the service:
.then(function(result) {
// Resolve the promise as the data
return result.data;
},
And in the controller you resolve the promise again:
requestService.getP2PKeywordData(month, year).then(function(data) {
$scope.d3Data = data;
});
This can work because (from my understanding) Angular sometimes resolves promises automatically when binding to the scope.
It would be better to just handle the promise in the controller only. So the service becomes:
getP2PKeywordData: function(global_m, global_y) {
// Return the promise
return $http({
url: base_url + 'get/P2P/kwords/',
method: "GET",
// Set the proper parameters
params: {
year: global_y,
month: global_m
}
});
}
UPDATE:
Try to initialize the d3Data scope property to an empty collection, and then push the response data into it. For example:
myApp.controller('DownloadsCloudCtrl', ['$scope',
'$rootScope',
'requestService',
'$cookieStore',
function($scope, $rootScope, requestService, $cookieStore) {
//added
$scope.d3Data = [];
$rootScope.$on('updateDashboard', function(event, month, year) {
updateDashboard(month, year);
});
var updateDashboard = function(month, year) {
requestService.getP2PKeywordData(month, year).then(function(data) {
//then
angular.forEach(data, function(thing) {
$scope.d3Data.push(thing);
)};
});
};
updateDashboard($cookieStore.get('month'), $cookieStore.get('year'));
}]);