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
Related
I have a field on a form with lots of validation.
At first, I had it structured into multiple directives, each with its own error message.
However, the validation uses a back-end asynchronous call, so suddenly for one field I was making 5 http calls for the same dataservice. I am trying to figure out how to write this more efficiently.
I was wondering if it is possible to have one $async validator that calls the dataservice, and multiple regular $validators inside of the first asynchronous function after .then. I experimented with this but it doesn't seem to reach the nested $validators at all.
I also tried to do the call once in a service, but I don't know how to get it to update when the modelValue on the field changes, and consequently pass the information to the respective validation directives. Could I do this as async validation in a service and attach the response to scope for the directives to look for?
TLDR;
How can I make ONE http call and based off of the returned data, perform multiple validation checks, each with its own error?
FOR EXAMPLE
I have about four directives that all look like this:
angular.module('validationForField').directive('po', ['$q', '$sce', '$timeout', 'myService', function ($q, $sce, $timeout, myService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl, ngModel) {
ctrl.$asyncValidators.validateField = function (modelValue) {
var def = $q.defer();
myService.httpcall(modelValue)
.then(function (response, modelValue) {
if (response.data.status === "Error") {
return def.reject();
}
def.resolve();
}).catch(function(){
def.reject();
});
return def.promise;
}
}
}
}]);
Each one has different analysis of the data to return different error messages. Each one makes a call to myService.httpcall which ends up being redundant because they are all getting the same data.
I am trying to do
angular.module('validationForField').directive('po', ['$q', '$sce', '$timeout', 'myService', function ($q, $sce, $timeout, myService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl, ngModel) {
ctrl.$asyncValidators.validateField = function (modelValue) {
var def = $q.defer();
myService.httpcall(modelValue)
.then(function (response, modelValue) {
if (response.data.status === "Error") {
return def.reject();
}
ctrl.$validators.checkStatus = function (response) {
if (response.data.data.status === "10"){
return false
}
ctrl.$validators.checkPermissions = function (response) {
return response.data.data.permission){
}
def.resolve();
}).catch(function(){
def.reject();
});
return def.promise;
}
}
}
}]);
This way there is the main async validator as to whether the http call is successful or not, and internal $validators that use that data when it returns
I assume the backend service accepts a value (the value of the field to be validated) and returns a single response for all validations, e.g.:
// true would mean valid, string would mean invalid with the given error:
{
businessRuleOne: true,
businessRuleTwo: "The format is incorrect",
...
}
I believe the solution is executing the HTTP call in a service that caches the promise; the async validators call the service and retrieve the same promise, which they return. Some sample code with inline explanation:
// the service:
app.service('myService', function($http, $q) {
// cache the requests, keyed by the model value
var requestMap = {};
this.httpcall = function(modelValue) {
// if cached, return that (and do not make extra call)
if( requestMap[modelValue] ) {
return requestMap[modelValue];
}
// if not cahced, make the call...
var promise = $http.get('....');
// ...cache it...
requestMap[modelValue] = promise;
// ...and remember to remove it from cache when done
promise.finally(function() {
delete requestMap[modelValue];
});
return promise;
};
});
Now the async validators can be implemented exactly as you post. Calling myService.httpcall(modelValue) will invoke the remote service only for the first call, the rest will reuse the cached promise.
Two more points: (1) This technique is called memoization. It is implemented by many libraries, e.g. lodash, you may be able to use those to keep myservice.httpcall() clean. (2) You do not need an extra promise from the async validators, e.g.:
angular.module('validationForField').directive('po', ['$q', '$sce', '$timeout', 'myService', function ($q, $sce, $timeout, myService) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attrs, ctrl, ngModel) {
ctrl.$asyncValidators.validateField = function (modelValue) {
return myService.httpcall(modelValue)
.then(function (response) {
if (response.data.status === "Error") {
return $q.reject();
}
return response;
});
}
}
}
}]);
I have an AngularJS SPA which loads articles into the view. Some articles have code examples and I want to use highlight.js to highlight it.
In my example below I have simulated a get request, 'cause that's how I load my dynamic content in the actual app. The $scope.test is very similar to what my actual app could get returned; some regular HTML to print out which includes code examples.
My problem: it doesn't really seem to work.
Specifically, nothing gets highlighted. It seems to me like I am missing an init or something... Halp?
I've also tried <div hljs/> with the same (lack of) result. There are no console errors.
This answer provides a solution that uses ng-model in the template. However, I don't use ng-model anywhere.
EDIT: I've made some changes to my example code to further explain the problem.
Here's my app (simplified):
var app = angular.module('app', ['ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.title = 'Some Title';
$scope.metaStuff = 'Written by Awesome MacFluffykins';
$scope.articleBody = '<p>Here\'s an example of a simple SQL SELECT:</p><pre><code class="sql" highlight>SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]);
Here's my HTML:
<div ng-app="app" ng-controller="ctrl">
<h2>{{ title }}</h2>
<p><small>{{ metaStuff }}</small></p>
<div ng-bind-html="articleBody"></div>
</div>
And finally a jsFiddle.
In my opinion it's best to use a directive for DOM manipulations like this. Pass your sourcecode through ng-model (you could also use another attribute) and run HLJS in the directive. Since you're using a asynchronous method to supply the value to your scope, you'll need to use $watch to catch the value and then run HLJS:
HTML:
<div highlight ng-model="test"></div>
Directive:
.directive('highlight', [
function () {
return {
replace: false,
scope: {
'ngModel': '='
},
link: function (scope, element, attributes) {
scope.$watch('ngModel', function (newVal, oldVal) {
if (newVal !== oldVal) {
element.html(scope.ngModel);
var items = element[0].querySelectorAll('code,pre');
angular.forEach(items, function (item) {
hljs.highlightBlock(item);
});
}
});
}
};
}
]);
Working JSFiddle: https://jsfiddle.net/1qy0j6qk/
Fiddle
https://jsfiddle.net/vg75ux6v/
var app = angular.module('app', ['hljs', 'ngSanitize']);
app.controller('ctrl', ['$scope', '$http',
function($scope, $http) {
"use strict";
$http.get('/echo/html').then(function successCallback(response) {
$scope.test = '<h2>Here\'s some code:</h2><pre><code hljs class="sql">SELECT * FROM table WHERE user = \'1\'</code></pre>';
}, function errorCallback(response) {
console.log("Error: %d %s", response.code, response.message);
});
}
]).directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}]);
I have tried everything to get ui-router's resolve to pass it's value to the given controller–AppCtrl. I am using dependency injection with $inject, and that seems to cause the issues. What am I missing?
Routing
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: 'AppCtrl',
controllerAs: 'vm',
resolve: {
auser: ['User', function(User) {
return User.getUser().then(function(user) {
return user;
});
}],
}
});
Controller
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope'];
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
console.log(auser); // undefined
...
}
Edit
Here's a plunk http://plnkr.co/edit/PoCiEnh64hR4XM24aH33?p=preview
When you use route resolve argument as dependency injection in the controller bound to the route, you cannot use that controller with ng-controller directive because the service provider with the name aname does not exist. It is a dynamic dependency that is injected by the router when it instantiates the controller to be bound in its respective partial view.
Also remember to return $timeout in your example, because it returns a promise otherwise your argument will get resolved with no value, same is the case if you are using $http or another service that returns a promise.
i.e
resolve: {
auser: ['$timeout', function($timeout) {
return $timeout(function() {
return {name:'me'}
}, 1000);
}],
In the controller inject the resolve dependency.
appControllers.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$rootScope','auser']; //Inject auser here
function AppCtrl($scope, $rootScope, auser) {
var vm = this;
vm.user = auser;
}
in the view instead of ng-controller, use ui-view directive:
<div ui-view></div>
Demo
Here is how I work with resolve. It should receive promise. So I create service accordingly.
app.factory('User', function($http){
var user = {};
return {
resolve: function() {
return $http.get('api/user/1').success(function(data){
user = data;
});
},
get: function() {
return user;
}
}
});
This is main idea. You can also do something like this with $q
app.factory('User', function($q, $http){
var user = {};
var defer = $q.defer();
$http.get('api/user/1').success(function(data){
user = data;
defer.resolve();
}).error(function(){
defer.reject();
});
return {
resolve: function() {
return defer.promise;
},
get: function() {
return user;
}
}
});
These are almost identical in action. The difference is that in first case, service will start fetching date when you call resolve() method of service and in second example it will start fetch when factory object is created.
Now in your state.
$stateProvider.state('app.index', {
url: '/me',
templateUrl: '/includes/app/me.jade',
controller: function ($scope, $rootScope, User) {
$scope.user = User.get();
console.log($scope.user);
},
controllerAs: 'vm',
resolve: {
auser: function(User) {
return User.resolve()
}
}
});
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
I've searched on Google but can't find information on how to do this properly. Seems like all the answers on Google are now outdated (using older versions of AngularJS).
I'm trying to setup two controllers on my AngularJS module. For example, the first controller is handling $http GET requests. And the second controller is displaying either a 'success' or 'error' message. I want to be able to call a method from the second controller with the success/error message that is to be displayed.
Or am I supposed to use a service/factory for this? I've read about services but can't figure out how to make something like this work.
var module = angular.module('app', []);
module.controller('ApiController', ['$scope', '$http', function ($scope, $http) {
$http.get('/api').
success(function(data){
// call AlertController('success')
}).
error(function(data){
// call AlertController('failed')
});
}]);
module.controller('AlertController', ['$scope', function ($scope) {
$scope.message = {
show_message: true,
type: 'info',
message: "Display message!"
};
}]);
Either doing it that way, or perhaps I would like to push the incoming alert onto a global object variable, and then remove it after it has been displayed.
Anyone know the proper way to set this up?
Ok let's try this - you should also check out Injecting $scope into an angular service function()
The Message service:
module.service('MessageService', function ($timeout) {
var messageQueue = [];
var DISPLAY_TIME = 5000; // each message will be displayed for 5 seconds
function startTimer() {
$timeout(function() {
// Remove the first message in the queue
messageQueue.shift();
// Start timer for next message (if there is one)
if (messageQueue.length > 0) startTimer();
}, DISPLAY_TIME);
}
function add(message) {
messageQueue.push(message);
// If this is the only message in the queue you need to start the timer
if (messageQueue.length==0) startTimer();
}
function get() {
if (messageQueue.length==0) return "";
else return messageQueue[0];
}
return { add: add, get: get };
});
You can still use this ApiService as well:
module.service('ApiService', ['$http', function ($http) {
return {
get: function(url) {
return $http.get(url);
}
};
}]);
Your Search controller:
module.controller('SearchController', ['$scope', 'ApiService', 'MessageService', function ($scope, api, messages) {
api.get('/yelp').
success(function(data){
messages.add('success');
}).
error(function(data){
messages.add('failed');
});
}]);
Your Alert controller:
module.controller('AlertController', ['$scope', 'MessageService', function ($scope, messages) {
$scope.getMessage = function() { messages.get(); }
}]);
So in your html you can have:
<div ng-controller="AlertController">
<div>{{ getMessage() }}</div>
</div>
here is how you make factory
module.factory('appService', ['$window', '$http', '$q', function(win, $http, $q) {
return{
backendcall: function(){
var deferred = $q.defer();
$http.get('/yelp').
success(function(data){
deferred.resolve(data);
}).
error(function(data){
deferred.resolve(status);
});
return deferred.promise;
}
}
}]);
and your controller will be like this
module.controller('AlertController', ['$scope', 'appService', function ($scope, appService) {
appService.backendcall().then(function(response){
$scope.message = {
show_message: true,
type: 'info',
message: "Display message!"
};
})
}]);