Use a custom function from an angular template - angularjs

I'm displaying urls from an angular template, but I'd like to 'slugify' it, that is, replace those ugly chars. Right now I just want to replace spaces with '_'.
This is the code I have on my index.html file
<a ng-href="#/{{url}}">{{name}}</a>
I'd like to define a slugify function like this:
slugify = function(url) {
return url.replace(' ', '_');
};
And then being able to call it from every the template like this
<a ng-href="#/{{slugify(url)}}">{{name}}</a>
How could I achieve it?
-- edit
I finally went with #underscore solution (added it to the $rootScope) and from a normal controller it works ok. But from a template inside the ng-app directive, but with no ng-controller, it works too, but it gives me the following error, lots of times:
Error: error:interr
Interpolation Error
Can't interpolate: #/{{slug(b.link)}}
TypeError: undefined is not a function
This is the offending code:
<a ng-show='b.link' ng-href="#/{{slug(b.link)}}">{{b.name}}</a>
I tried with regular href and ng-href, and also with $root.slug, but it always throws the same error a dozen times, nevertheless it works ok.
Also tried defining a filter, like Peter said, and it gave me the same error:
Can't interpolate: #/{{b.link | slug}}
TypeError: undefined is not a function
Nevertheless, it worked ok too...
--
ok, final edit
It seems when angular is bootstraping the input value might be undefined (as explained here: https://stackoverflow.com/a/18573426/47633)
I could solve the nasty error message with:
presuApp.filter('slug', function() {
return function(input) {
return input ? input.replace(/ /g, '_') : '';
}
});

Create a function in your controller e.g.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.theInput = 'example input';
$scope.slugify = function(input) {
input = input.replace(' ', '_');
return input;
}
});
Here's a plunkr:
http://plnkr.co/edit/RARhGQzQoGPtWhoFColL?p=preview
A more correct way might be to use a filter:
app.filter('slugify', function() {
return function(input) {
input = input.replace(' ', '_');
return input
}
})
And in the template you put:
using a filter:
{{ theInput | slugify }}
Also added that to plunkr

"And then being able to call it from every the template like this"
In this case you should use $rootScope instead of controller scope.
just use it in the $rootScope
$rootScope.slugify = function(url) {
return url.replace(' ', '_');
};
Define it in the app.run(); . So the function will define when angular bootstrap

In your controller you can bind $scope to a function to make it accessible via the html.
$scope.slugify = function(url) {
return url.replace(' ', '_');
};

Related

AngularJS calling service function via view

I'm working with Angular(1.x) and just encountered a strange behaviour (which I do not exclude being a result of my code).
Here is the setup:
var module = angular.module('module_name');
module.service('service_name', function() {
this.function_name = function() { ... }
});
module.controller('controller_name', ['$scope', 'service_name',
function($scope, service_name) {
$scope.function_name = function() { ... }
}])
And in the view :
<div ng-controller='controller_name'>
<button ng-click="function_name()">Test</button>
</div>
The function in service_name is accessible in the controller via service_name.function_name() as expected. But here is the strange behaviour, (once again it occurs in a more complex setting, not saying this portion of code will reproduce the described scenario)
When clicking the button in the view the function called is not the function_name from the controller but the function_name from the service.
Eventhough they have the same name how can the view access a function directly in the service, shouldn't it be limited to its controller scope ?
This simply can not happen unless and until somewhere in your code you write
$scope.function_name = service_name.function_name
Services do not have any local scope.In angular view side on-click event expects
function in controller's (read local) scope.
What I suspect in your case is , As JS is all reference, You must be doing something like this in large file :
var dummy = service_name.function_name
...
$scope.function_name = dummy
If you do not want to redefine the function name in your controller you can do something like this.
module.service('service_name', function() {
this.function_name = function() { ... }
return {
function_name : function_name
}
});
module.controller('controller_name', ['$scope', 'service_name',
function($scope, service_name) {
$scope.utils = service_name;
}]);
and then in your view call the function directly.
<div ng-controller='controller_name'>
<button ng-click="utils.function_name()">Test</button>
</div>
That's it.
It is better to give your controller and service name using for example
abcCtrl for controller and abcService for service and so on.
Why, because it will be easier for you to call it. and its not confusing

Not able to inject filters in Controller for AngularJS

Hi I have just started on Angular. I am using AngularJS 1.2.4.
Whenever I try to inject a filter in my controller I get the following error :
Error: error:unpr Unknown Provider Unknown provider:
senderReceiverDetailInfoProvider <- senderReceiverDetailInfo <-
senderReceiverFilter
Here is my app.js :
var myApp = angular.module("myapp", []);
Here is my myFilter.js:
myApp.filter('senderReceiver',function(senderReceiverDetailInfo,index){
conole.log("i",index);
if(senderReceiverDetailInfo.description != ''){
return true;
}else{
return false;
}
});
Here is my controller.js
myApp.controller("myCtrl",'$scope','$http','PaymentService','senderReceiverFilter',
function($scope,$http,PaymentService,senderReceiverFilter){
$scope.sendReceiveFilter = senderReceiverFilter;
}]);
And Finally here is my index.html in which I am trying to use the filter:
<div ng-repeat="detailInfo in senderReceiverDetail | filter : sendReceiveFilter">
<label>{{$index + 1}}.</label><label>Type : {{detailInfo.lineType}}</label>
<label>Description : {{detailInfo.description}}</label>
</div>
</div>
And finally here are my questions:
1) Why injection is not working?
2) Am i using injected filter correctly(Assuming it is injectede somehow correctly) in my index.html?
3) Also Have I declared filters correctly OR I have to declare them in separate module?
If I declare them in separate module then how to inject them?
E.g. if I do following instead of what I am doing right now for filter declaration:
angular.module('myfilters',[]).filter('senderReceiver',function(senderReceiverDetailInfo,index){
conole.log("i",index);
if(senderReceiverDetailInfo.description != ''){
return true;
}else{
return false;
}
});
and then inject above module in my myapp as dependency as follows:
angular.module('myapp',['myfilters']);
Then how to use/inject the filter defined on module 'myfilters' in controller that I have defined in 'myapp' module?
4) And finally what is the better aproach? declaring filters in entirely spearate module or define it on main module like controller and services?
I know I am not much aware of dependency injection apart from the basic syntax. But I guess thats what I need. Thanks.
This is how I do it. I have stolen the filter from the AngularJS filter documentation.
var MyApp = angular.module('myapp',[]);
MyApp.filter('reverse', function() {
return function(input, uppercase) {
input = input || '';
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
return out;
};
});
Now you will be able to use the filter in your templates like this: {{ myObject.value | reverse }}
The filter that you are using with ng-repeat is this filter https://code.angularjs.org/1.2.15/docs/api/ng/filter/filter
The filter function has to be defined on the scope, simply try:
$scope.sendReceiveFilter = function(senderReceiverDetailInfo,index){
conole.log("i",index);
if(senderReceiverDetailInfo.description != ''){
return true;
}else{
return false;
}
}
Update: To create your own filter function to filter array, you need to define to do something like
myApp.filter('senderReceiver', function () {
function (senderReceivers, index) {
var filteredsenderReceivers = [];
// Fill the above array with filtered values from senderReceivers array
return filteredsenderReceivers
}
});
senderReceivers is an array
Use the filter as
<div ng-repeat="detailInfo in senderReceiverDetail |sendReceive">
The error you're getting is because you are not defining your filter correctly. The filter should return a function. So update your filter to:
myApp.filter('senderReceiver', function(){
return function(senderReceiverDetailInfo,index){
console.log("i",index);
if(senderReceiverDetailInfo.description != ''){
return true;
}else{
return false;
}
};
});
The way you declared the filter made Angular believe you were looking for dependencies named senderReceiverDetailInfo and info. As those are not defined as Angular assets, Angular will throw an error complaining it can't find a provider for the named asset in your case senderReceiverDetailInfoProvider, which is exactly what the logs state.
In case you want to use the filter in a controller, you can inject the $filter service and then simply call:
$filter('senderReceiver')(senderReceiverDetailInfo,index);
To answer your last question, you should put all related assets (be it controller, service or filter) in the same module.

AngularJS interpolation error when parsing Object

Just starting out with AngularJS. Trying to build a single page app to display slides/pages of my comic, one at a time.
The data is getting pulled in from a JSON file, and that's working correctly. But Angular is throwing errors when I try to set up some basic binding. Any thoughts..?
HTML
<span ng-bind-html="showCurrentTitle"></span>
JS
var comicsApp = angular.module('comicsApp', ['ngSanitize']);
comicsApp.controller('comicsCtrl', ['$scope', '$http',
function comicsCtrl($scope, $http) {
$http.get('/luv-slimline/test/comics.json').success(function(data) {
$scope.comics = data.comics;
});
$scope.showCurrentTitle = function() {
return $scope.comics[0].title;
}
} //end ComicsCtrl
]);
Error
TypeError: Object function () { return $scope.comics[0].title; } has no method 'indexOf'
If I strip out the ngSanitize module, then the error message changes.
Alt error
Error: [$sce:unsafe] http://errors.angularjs.org/undefined/$sce/unsafe
And if I swap out the ng-bind-html directive for the older-style mustache braces, then the error message changes again.
Alt error (2)
Error: [$interpolate:interr] http://errors.angularjs.org/undefined/$interpolate/interr?p0=%7B%7BshowCurr…()%7D%7D&p1=TypeError%3A%20Cannot%20read%20property%20'0'%20of%20undefined
Help, please?
Cheers,
Dan
I'm not familiar with the errors you are receiving, or ng-sanitize or the ng-bind-html tag. However, your javascript code looks problematic to me.
var comicsApp = angular.module('comicsApp', ['ngSanitize']);
comicsApp.controller('comicsCtrl', ['$scope', '$http',
function comicsCtrl($scope, $http) {
//so this runs immediately, but . . .
$http.get('/luv-slimline/test/comics.json').success(function(data) {
// . . . this is going to happen async, so this value won't be set
//before the initial binding occurs
$scope.comics = data.comics;
});
$scope.showCurrentTitle = function() {
// . . . as a result, comics will be undefined initially.
return $scope.comics[0].title;
}
} //end ComicsCtrl
]);
I think you need to define an initial value for $scope.comics before your http.get. Something like:
$scope.comics = [];
$scope.comics.push({title: "testTitle"});
Edit
Based on your comment, if you are now just binding to showCurrentTitle (which I would rename to currentTitle, as it makes more sense), you shouldn't even need to initialize $scope.comics--it should work without that code.

How to fix "10 $digest() iterations reached. Aborting!" error in Angular 1.2 filter?

I've created a filter in AngularJS 1.2 that uses the ShowdownJS to parse Markdown content into HTML:
App.filter('markdown', function( $sce ) {
var converter = new Showdown.converter();
return function (value) {
var html = converter.makeHtml(value);
return $sce.trustAsHtml(html);
};
});
The binding in the templates is done with ng-bind-html. This one receives to final HTML content so it's mandatory to show the content:
<div ng-bind-html="post.content | markdown"></div>
The filter works but I get this error in console because it returns the $sce service and it SHOULD return just the parsed HTML string.
10 $digest() iterations reached. Aborting!
How can I avoid this in a filter? I could not find any method that can can extract the escaped html from the $sce service.
EDIT: if I disable the sanitize service in the config I don't get this error even if the code remains exactly the same.
$sceProvider.enabled(false);
Ok after some investigation I found that the problem is instances. Every time your filter fires you return another instance. Then the ng-bind-html watcher fires infinitely.
Demo
I added a cache of all trusted values:
app.filter('markdown', ['$sce', function( $sce ) {
var converter = new Showdown.converter();
var converted = {};
return function (value) {
if(converted.hasOwnProperty(value)) {
return converted[value];
}
var html = converter.makeHtml(value);
var trusted = converted[value] = $sce.trustAsHtml(html);
return trusted;
};
}]);
I think it has something to do with ngBindHtmlDirective watching it and calling $sce.getTrustedHtml on the value. You can get around it by avoiding $sce and using your own directive (plnkr):
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
try
return $sce.trustAsHtml(html).toString();
When I had this error, I was accidentally using a filter on an object instead of a string.

"Computed Properties" in AngularJS

I recently chose AngularJS over ember.js for a project I am working on, and have been very pleased with it so far. One nice thing about ember is its built in support for "computed properties" with automatic data binding. I have been able to accomplish something similar in Angular with the code below, but am not sure if it is the best way to do so.
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// "Computed Property"
$scope.$watch(navigation.getCurrentPageNumber, function(newVal, oldVal, scope) {
scope.currentPageNumber = newVal;
});
$scope.totalPages = navigation.getTotalPages();
}]);
// 'navigation' service
angular.module('mathSkills.services', [])
.factory('navigation', function() {
var currentPage = 0,
pages = [];
return {
getCurrentPageNumber: function() {
return currentPage + 1;
},
getTotalPages: function() {
return pages.length;
}
};
});
// HTML template
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber}} of {{totalPages}}
</div>
I would like for the UI to update whenever the currentPage of the navigation service changes, which the above code accomplishes.
Is this the best way to solve this problem in AngularJS? Are there (significant) performance implications for using $watch() like this? Would something like this be better accomplished using custom events and $emit() or $broadcast()?
While your self-answer works, it doesn't actually implement computed properties. You simply solved the problem by calling a function in your binding to force the binding to be greedy. I'm not 100% sure it'd work in all cases, and the greediness might have unwanted performance characteristics in some situations.
I worked up a solution for a computed properties w/dependencies similar to what EmberJS has:
function ngCreateComputedProperty($scope, computedPropertyName, dependentProperties, f) {
function assignF($scope) {
var computedVal = f($scope);
$scope[computedPropertyName] = computedVal;
};
$scope.$watchCollection(dependentProperties, function(newVal, oldVal, $scope) {
assignF($scope);
});
assignF($scope);
};
// in some controller...
ngCreateComputedProperty($scope, 'aSquared', 'a', function($scope) { return $scope.a * $scope.a } );
ngCreateComputedProperty($scope, 'aPlusB', '[a,b]', function($scope) { return $scope.a + $scope.b } );
See it live: http://jsfiddle.net/apinstein/2kR2c/3/
It's worth noting that $scope.$watchCollection is efficient -- I verified that "assignF()" is called only once even if multiple dependencies are changed simultaneously (same $apply cycle).
"
I think I found the answer. This example can be dramatically simplified to:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// Property is now just a reference to the service's function.
$scope.currentPageNumber = navigation.getCurrentPageNumber;
$scope.totalPages = navigation.getTotalPages();
}]);
// HTML template
// Notice the first binding is to the result of a function call.
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber()}} of {{totalPages}}
</div>
Note that with ECMAScript 5 you can now also do something like this:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', function(navigation, $scope) {
$scope.totalPages = navigation.getTotalPages();
Object.defineProperty($scope, 'currentPageNumber', {
get: function() {
return navigation.getCurrentPageNumber();
}
});
]);
//HTML
<div ng-controller="nav">Problem {{currentPageNumber}} of {{totalPages}}</div>

Resources