Angular slugify leaving dash at the end - angularjs

Remove accents and symbols slugify, but leaving dash at the end.
I would also share the same function between controllers , how do?
code:
$scope.slugify = function(slug){
str = slug.replace("-");
str = angular.lowercase(str);
str = str.replace(/[^A-Z0-9]+/ig, "-");
$scope.item.slug = str;
}
Example:
source: Neymar the best player!
return: neymar-the-best-player-
[Resolved]
i found this: https://github.com/paulsmith/angular-slugify

Main problem:
Remove accents and symbols slugify, but leaving dash at the end.
Before we go to the main problem, there are several parts of you code that needs mending.
The first replace() statement, works for dashes that between any other strings, but would definitely be a problem if dashes are placed in both ends of a string.
e.g. '-Some-Text-' results to 'undefinedSome-Textundefined'
To solve this problem you need to add the second argument for replace() with an empty string.
From:
str = slug.replace("-");
To:
str = slug.replace('-', '');
The second replace() statement has a regular expression flag representing, i, representing for a case-insensitive search. While your regular expression statement suggests an A-Z (uppercase expression), this in fact is redundant, since you've already modified the string into a lower case. So the statement should be changed:
From:
str = str.replace(/[^A-Z0-9]+/ig, "-");
To:
str = str.replace(/[^a-z0-9]+/g, "-");
Now for the main problem, you're simply missing a dash trimming function for both ends of the string.
Add this replace statement after the code in #2
str = str.replace(/^-+|-+$/g, '');
From what I see in your code, this seems to be more of a reusable function rather than something that can be attributed to a controller function. You can create a service for this specific function instead and inject it in your controller.
DEMO
.controller('SluggerController', function($scope, slugify) {
$scope.slug = 'Neymar the best player!';
$scope.item = { slug: '' };
$scope.slugify = function(slug) {
$scope.item.slug = slugify(slug);
};
})
.factory('slugify', function() {
return function(text) {
return angular.lowercase(text)
.replace('-', '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
};
});
UPDATE:
Since you don't want to include unicode characters as dashed characters then you can incorporate this with #2.
DEMO
Instead of:
str = str.replace(/[^a-z0-9]+/g, "-");
Change it to:
str = str.replace(/[^\u00BF-\u1FFF\u2C00-\uD7FF\w0-9]/g, '-');
As for how I got the regular expression, you can refer to this SO comment

To make it public there is a necessary step to return something. Then you can wrap it as a constant and inject in any controller.
app.controller('MainCtrl', function($scope, slugify) {
$scope.show = function(){
alert(slugify($scope.someText));
}
});
app.constant('slugify', function(slug){
str = slug.replace("-");
str = angular.lowercase(str);
str = str.replace(/[^A-Z0-9]+/ig, "-");
return str;
});
Also from the nature of the transformation applied by function it fits the pattern of filter.
app.filter('slugifyFilter', function(){
return function(slug){
str = slug.replace("-");
str = angular.lowercase(str);
str = str.replace(/[^A-Z0-9]+/ig, "-");
return str;
}
});
And use like this in template
{{someText | slugifyFilter}}
See it in action in plunker

Related

Is there something that avoids some characters (1, f, x, and b, specifically) to be validated from an input?

I have a field to validate repeated questions. I must convert special characters into normal letters using a method, and then I use a method to skip spaces and other characters to validate if the new text is equal to the existing questions. In my local everything is working fine, but in a Production instance, it skips characters 1, f, x and b. My project in Production is mounted in Linux.
My two methods are:
replaceSpecialCharacters(output){
return output = output.replace(/á|é|í|ó|ú|ñ|ä|ë|ï|ö|ü|à|è|ì|ò|ù/ig,function (str,offset,s) {
var str = str=="á"?"a":str=="é"?"e":str=="í"?"i":str=="ó"?"o":str=="ú"?"u":str=="ñ"?"n":str;
str = str=="Á"?"A":str=="É"?"E":str=="Í"?"I":str=="Ó"?"O":str=="Ú"?"U":str=="Ñ"?"N":str;
str = str=="à"?"a":str=="è"?"e":str=="ì"?"i":str=="ò"?"o":str=="ù"?"u":str;
str = str=="À"?"A":str=="È"?"E":str=="Ì"?"I":str=="Ò"?"O":str=="Ù"?"U":str;
str = str=="ä"?"a":str=="ë"?"e":str=="ï"?"i":str=="ö"?"o":str=="ü"?"u":str;
str = str=="Ä"?"A":str=="Ë"?"E":str=="Ï"?"I":str=="Ö"?"O":str=="Ü"?"U":str;
return (str);
});
}
validateRepeatedQuestion(question): void {
var questionToCheck = this.replaceSpecialCharacters(question).replace(/[(\s)+(\¿)+(\?)+(\¡)+(\!)+(\")+(\')+]/g, "");
this.setState({isRepeated: false});
for (let i = 0; i < this.props.allQuestions.length; i++) {
var questionFromArray = this.replaceSpecialCharacters(this.props.allQuestions[i].text).replace(/[(\s)+(\¿)+(\?)+(\¡)+(\!)+(\")+(\')+]/g, "");
if(this.props.allQuestions[i].position != this.props.question.position){
if(questionFromArray.toLocaleLowerCase() == questionToCheck.toLocaleLowerCase()){
this.setState({isRepeated: true});
}
}
}
}
If I have a question like "How old are you?" and I try to add a question like "How old are you?1111" I expect the validation to take the new question as different, but the actual output says that the question is repeated, even if I'm adding numbers 1. It must be different.

AngularJS $parse/scope.$eval expression

I am trying to use $parse to get my required result.
Here is my Example. Check script.js file
I want to parse an expression but I am getting error. How can I solve this kind of data?
Here is my code
scope.data = {name:{_en:"username", _ar:'مفقود '}}
// set variable in scope like scope.name_en = scope.data._en, scope.name_ar = scope.data._ar
for(var i in scope.data) for(var j in scope.data[i]) scope[i+j] = scope.data[i][j];
scope.messages2 = [{code:200, msg_en:"{{name_en}} is missing.", msg_ar:"مفقود {{name_ar}}"}];
scope.finalMegs = [];
for( var i in scope.messages2) {
var obj = {};
for(var j in scope.messages2[i]){
if(j == 'code') continue;
console.log(scope.$eval(scope.messages2[i][j]) );
obj[j] = $parse(scope.messages2[i][j])
}
/*
required object is {msg_en = "username is missing.", msg_ar:"مفقود مفقود "}
*/
scope.finalMegs.push(obj);
}
console.log(scope.finalMegs);
Thank you.
Syntax of your messages is incorrect, so the messages can't be parsed by $parse or $eval. You should write them in this format:
scope.messages2 = [
{
code:200,
msg_en:"name_en + ' is missing.'",
msg_ar:"'مفقود ' + name_ar"
}
];
Then if you want to get object with already formatted messages you should call $parse function in this way:
obj[j] = $parse(scope.messages2[i][j])(scope);
Because $parse converts angular expression to function, which you should call with your scope to get expected results.
But in your case, I think that you can replace those line to this also:
obj[j] = scope.$eval(scope.messages2[i][j]);
Result will be the same.
Example on plunker. (I've reformatted your example a little bit)
Please, take a look also at the example of usage scope.$eval here and documentation about $parse.

replace string in more than one instance angularjs [duplicate]

Given a string:
s = "Test abc test test abc test test test abc test test abc";
This seems to only remove the first occurrence of abc in the string above:
s = s.replace('abc', '');
How do I replace all occurrences of it?
As of August 2020: Modern browsers have support for the String.replaceAll() method defined by the ECMAScript 2021 language specification.
For older/legacy browsers:
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function replaceAll(str, find, replace) {
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
Here is how this answer evolved:
str = str.replace(/abc/g, '');
In response to comment "what's if 'abc' is passed as a variable?":
var find = 'abc';
var re = new RegExp(find, 'g');
str = str.replace(re, '');
In response to Click Upvote's comment, you could simplify it even more:
function replaceAll(str, find, replace) {
return str.replace(new RegExp(find, 'g'), replace);
}
Note: Regular expressions contain special (meta) characters, and as such it is dangerous to blindly pass an argument in the find function above without pre-processing it to escape those characters. This is covered in the Mozilla Developer Network's JavaScript Guide on Regular Expressions, where they present the following utility function (which has changed at least twice since this answer was originally written, so make sure to check the MDN site for potential updates):
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
So in order to make the replaceAll() function above safer, it could be modified to the following if you also include escapeRegExp:
function replaceAll(str, find, replace) {
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
For the sake of completeness, I got to thinking about which method I should use to do this. There are basically two ways to do this as suggested by the other answers on this page.
Note: In general, extending the built-in prototypes in JavaScript is generally not recommended. I am providing as extensions on the String prototype simply for purposes of illustration, showing different implementations of a hypothetical standard method on the String built-in prototype.
Regular Expression Based Implementation
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
Split and Join (Functional) Implementation
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
Not knowing too much about how regular expressions work behind the scenes in terms of efficiency, I tended to lean toward the split and join implementation in the past without thinking about performance. When I did wonder which was more efficient, and by what margin, I used it as an excuse to find out.
On my Chrome Windows 8 machine, the regular expression based implementation is the fastest, with the split and join implementation being 53% slower. Meaning the regular expressions are twice as fast for the lorem ipsum input I used.
Check out this benchmark running these two implementations against each other.
As noted in the comment below by #ThomasLeduc and others, there could be an issue with the regular expression-based implementation if search contains certain characters which are reserved as special characters in regular expressions. The implementation assumes that the caller will escape the string beforehand or will only pass strings that are without the characters in the table in Regular Expressions (MDN).
MDN also provides an implementation to escape our strings. It would be nice if this was also standardized as RegExp.escape(str), but alas, it does not exist:
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
We could call escapeRegExp within our String.prototype.replaceAll implementation, however, I'm not sure how much this will affect the performance (potentially even for strings for which the escape is not needed, like all alphanumeric strings).
In the latest versions of most popular browsers, you can use replaceAll
as shown here:
let result = "1 abc 2 abc 3".replaceAll("abc", "xyz");
// `result` is "1 xyz 2 xyz 3"
But check Can I use or another compatibility table first to make sure the browsers you're targeting have added support for it first.
For Node.js and compatibility with older/non-current browsers:
Note: Don't use the following solution in performance critical code.
As an alternative to regular expressions for a simple literal string, you could use
str = "Test abc test test abc test...".split("abc").join("");
The general pattern is
str.split(search).join(replacement)
This used to be faster in some cases than using replaceAll and a regular expression, but that doesn't seem to be the case anymore in modern browsers.
Benchmark: https://jsben.ch/TZYzj
Conclusion:
If you have a performance-critical use case (e.g., processing hundreds of strings), use the regular expression method. But for most typical use cases, this is well worth not having to worry about special characters.
Here's a string prototype function based on the accepted answer:
String.prototype.replaceAll = function(find, replace) {
var str = this;
return str.replace(new RegExp(find, 'g'), replace);
};
If your find contains special characters then you need to escape them:
String.prototype.replaceAll = function(find, replace) {
var str = this;
return str.replace(new RegExp(find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), replace);
};
Fiddle: http://jsfiddle.net/cdbzL/
Use word boundaries (\b)
'a cat is not a caterpillar'.replace(/\bcat\b/gi,'dog');
//"a dog is not a caterpillar"
This is a simple regex that avoids replacing parts of words in most cases. However, a dash - is still considered a word boundary. So conditionals can be used in this case to avoid replacing strings like cool-cat:
'a cat is not a cool-cat'.replace(/\bcat\b/gi,'dog');//wrong
//"a dog is not a cool-dog" -- nips
'a cat is not a cool-cat'.replace(/(?:\b([^-]))cat(?:\b([^-]))/gi,'$1dog$2');
//"a dog is not a cool-cat"
Basically, this question is the same as the question here:
Replace " ' " with " '' " in JavaScript
Regexp isn't the only way to replace multiple occurrences of a substring, far from it. Think flexible, think split!
var newText = "the cat looks like a cat".split('cat').join('dog');
Alternatively, to prevent replacing word parts—which the approved answer will do, too! You can get around this issue using regular expressions that are, I admit, somewhat more complex and as an upshot of that, a tad slower, too:
var regText = "the cat looks like a cat".replace(/(?:(^|[^a-z]))(([^a-z]*)(?=cat)cat)(?![a-z])/gi,"$1dog");
The output is the same as the accepted answer, however, using the /cat/g expression on this string:
var oops = 'the cat looks like a cat, not a caterpillar or coolcat'.replace(/cat/g,'dog');
//returns "the dog looks like a dog, not a dogerpillar or cooldog" ??
Oops indeed, this probably isn't what you want. What is, then? IMHO, a regex that only replaces 'cat' conditionally (i.e., not part of a word), like so:
var caterpillar = 'the cat looks like a cat, not a caterpillar or coolcat'.replace(/(?:(^|[^a-z]))(([^a-z]*)(?=cat)cat)(?![a-z])/gi,"$1dog");
//return "the dog looks like a dog, not a caterpillar or coolcat"
My guess is, this meets your needs. It's not foolproof, of course, but it should be enough to get you started. I'd recommend reading some more on these pages. This'll prove useful in perfecting this expression to meet your specific needs.
RegExp (regular expression) object
Regular-Expressions.info
Here is an example of .replace used with a callback function. In this case, it dramatically simplifies the expression and provides even more flexibility, like replacing with correct capitalisation or replacing both cat and cats in one go:
'Two cats are not 1 Cat! They\'re just cool-cats, you caterpillar'
.replace(/(^|.\b)(cat)(s?\b.|$)/gi,function(all,char1,cat,char2)
{
// Check 1st, capitalize if required
var replacement = (cat.charAt(0) === 'C' ? 'D' : 'd') + 'og';
if (char1 === ' ' && char2 === 's')
{ // Replace plurals, too
cat = replacement + 's';
}
else
{ // Do not replace if dashes are matched
cat = char1 === '-' || char2 === '-' ? cat : replacement;
}
return char1 + cat + char2;//return replacement string
});
//returns:
//Two dogs are not 1 Dog! They're just cool-cats, you caterpillar
These are the most common and readable methods.
var str = "Test abc test test abc test test test abc test test abc"
Method 1:
str = str.replace(/abc/g, "replaced text");
Method 2:
str = str.split("abc").join("replaced text");
Method 3:
str = str.replace(new RegExp("abc", "g"), "replaced text");
Method 4:
while(str.includes("abc")){
str = str.replace("abc", "replaced text");
}
Output:
console.log(str);
// Test replaced text test test replaced text test test test replaced text test test replaced text
Match against a global regular expression:
anotherString = someString.replace(/cat/g, 'dog');
For replacing a single time, use:
var res = str.replace('abc', "");
For replacing multiple times, use:
var res = str.replace(/abc/g, "");
str = str.replace(/abc/g, '');
Or try the replaceAll method, as recommended in this answer:
str = str.replaceAll('abc', '');
or:
var search = 'abc';
str = str.replaceAll(search, '');
EDIT: Clarification about replaceAll availability
The replaceAll method is added to String's prototype. This means it will be available for all string objects/literals.
Example:
var output = "test this".replaceAll('this', 'that'); // output is 'test that'.
output = output.replaceAll('that', 'this'); // output is 'test this'
Using RegExp in JavaScript could do the job for you. Just simply do something like below code, and don't forget the /g after which standout for global:
var str ="Test abc test test abc test test test abc test test abc";
str = str.replace(/abc/g, '');
If you think of reuse, create a function to do that for you, but it's not recommended as it's only one line function. But again, if you heavily use this, you can write something like this:
String.prototype.replaceAll = String.prototype.replaceAll || function(string, replaced) {
return this.replace(new RegExp(string, 'g'), replaced);
};
And simply use it in your code over and over like below:
var str ="Test abc test test abc test test test abc test test abc";
str = str.replaceAll('abc', '');
But as I mention earlier, it won't make a huge difference in terms of lines to be written or performance. Only caching the function may affect some faster performance on long strings and is also a good practice of DRY code if you want to reuse.
Say you want to replace all the 'abc' with 'x':
let some_str = 'abc def def lom abc abc def'.split('abc').join('x')
console.log(some_str) //x def def lom x x def
I was trying to think about something more simple than modifying the string prototype.
Use a regular expression:
str.replace(/abc/g, '');
Performance
Today 27.12.2019 I perform tests on macOS v10.13.6 (High Sierra) for the chosen solutions.
Conclusions
The str.replace(/abc/g, ''); (C) is a good cross-browser fast solution for all strings.
Solutions based on split-join (A,B) or replace (C,D) are fast
Solutions based on while (E,F,G,H) are slow - usually ~4 times slower for small strings and about ~3000 times (!) slower for long strings
The recurrence solutions (RA,RB) are slow and do not work for long strings
I also create my own solution. It looks like currently it is the shortest one which does the question job:
str.split`abc`.join``
str = "Test abc test test abc test test test abc test test abc";
str = str.split`abc`.join``
console.log(str);
Details
The tests were performed on Chrome 79.0, Safari 13.0.4 and Firefox 71.0 (64 bit). The tests RA and RB use recursion. Results
Short string - 55 characters
You can run tests on your machine HERE. Results for Chrome:
Long string: 275 000 characters
The recursive solutions RA and RB gives
RangeError: Maximum call stack size exceeded
For 1M characters they even break Chrome
I try to perform tests for 1M characters for other solutions, but E,F,G,H takes so much time that browser ask me to break script so I shrink test string to 275K characters. You can run tests on your machine HERE. Results for Chrome
Code used in tests
var t="Test abc test test abc test test test abc test test abc"; // .repeat(5000)
var log = (version,result) => console.log(`${version}: ${result}`);
function A(str) {
return str.split('abc').join('');
}
function B(str) {
return str.split`abc`.join``; // my proposition
}
function C(str) {
return str.replace(/abc/g, '');
}
function D(str) {
return str.replace(new RegExp("abc", "g"), '');
}
function E(str) {
while (str.indexOf('abc') !== -1) { str = str.replace('abc', ''); }
return str;
}
function F(str) {
while (str.indexOf('abc') !== -1) { str = str.replace(/abc/, ''); }
return str;
}
function G(str) {
while(str.includes("abc")) { str = str.replace('abc', ''); }
return str;
}
// src: https://stackoverflow.com/a/56989553/860099
function H(str)
{
let i = -1
let find = 'abc';
let newToken = '';
if (!str)
{
if ((str == null) && (find == null)) return newToken;
return str;
}
while ((
i = str.indexOf(
find, i >= 0 ? i + newToken.length : 0
)) !== -1
)
{
str = str.substring(0, i) +
newToken +
str.substring(i + find.length);
}
return str;
}
// src: https://stackoverflow.com/a/22870785/860099
function RA(string, prevstring) {
var omit = 'abc';
var place = '';
if (prevstring && string === prevstring)
return string;
prevstring = string.replace(omit, place);
return RA(prevstring, string)
}
// src: https://stackoverflow.com/a/26107132/860099
function RB(str) {
var find = 'abc';
var replace = '';
var i = str.indexOf(find);
if (i > -1){
str = str.replace(find, replace);
i = i + replace.length;
var st2 = str.substring(i);
if(st2.indexOf(find) > -1){
str = str.substring(0,i) + RB(st2, find, replace);
}
}
return str;
}
log('A ', A(t));
log('B ', B(t));
log('C ', C(t));
log('D ', D(t));
log('E ', E(t));
log('F ', F(t));
log('G ', G(t));
log('H ', H(t));
log('RA', RA(t)); // use reccurence
log('RB', RB(t)); // use reccurence
<p style="color:red">This snippet only presents codes used in tests. It not perform test itself!<p>
Replacing single quotes:
function JavaScriptEncode(text){
text = text.replace(/'/g,'&apos;')
// More encode here if required
return text;
}
Using
str = str.replace(new RegExp("abc", 'g'), "");
worked better for me than the previous answers. So new RegExp("abc", 'g') creates a regular expression what matches all occurrences ('g' flag) of the text ("abc"). The second part is what gets replaced to, in your case empty string ("").
str is the string, and we have to override it, as replace(...) just returns result, but not overrides. In some cases you might want to use that.
This is the fastest version that doesn't use regular expressions.
Revised jsperf
replaceAll = function(string, omit, place, prevstring) {
if (prevstring && string === prevstring)
return string;
prevstring = string.replace(omit, place);
return replaceAll(prevstring, omit, place, string)
}
It is almost twice as fast as the split and join method.
As pointed out in a comment here, this will not work if your omit variable contains place, as in: replaceAll("string", "s", "ss"), because it will always be able to replace another occurrence of the word.
There is another jsperf with variants on my recursive replace that go even faster (http://jsperf.com/replace-all-vs-split-join/12)!
Update July 27th 2017: It looks like RegExp now has the fastest performance in the recently released Chrome 59.
Loop it until number occurrences comes to 0, like this:
function replaceAll(find, replace, str) {
while (str.indexOf(find) > -1) {
str = str.replace(find, replace);
}
return str;
}
If what you want to find is already in a string, and you don't have a regex escaper handy, you can use join/split:
function replaceMulti(haystack, needle, replacement)
{
return haystack.split(needle).join(replacement);
}
someString = 'the cat looks like a cat';
console.log(replaceMulti(someString, 'cat', 'dog'));
function replaceAll(str, find, replace) {
var i = str.indexOf(find);
if (i > -1){
str = str.replace(find, replace);
i = i + replace.length;
var st2 = str.substring(i);
if(st2.indexOf(find) > -1){
str = str.substring(0,i) + replaceAll(st2, find, replace);
}
}
return str;
}
I like this method (it looks a little cleaner):
text = text.replace(new RegExp("cat","g"), "dog");
String.prototype.replaceAll - ECMAScript 2021
The new String.prototype.replaceAll() method returns a new string with all matches of a pattern replaced by a replacement. The pattern can be either a string or a RegExp, and the replacement can be either a string or a function to be called for each match.
const message = 'dog barks meow meow';
const messageFormatted = message.replaceAll('meow', 'woof')
console.log(messageFormatted);
Of course in 2021 the right answer is:
String.prototype.replaceAll()
console.log(
'Change this and this for me'.replaceAll('this','that') // Normal case
);
console.log(
'aaaaaa'.replaceAll('aa','a') // Challenged case
);
If you don't want to deal with replace() + RegExp.
But what if the browser is from before 2020?
In this case we need polyfill (forcing older browsers to support new features) (I think for a few years will be necessary).
I could not find a completely right method in answers. So I suggest this function that will be defined as a polyfill.
My suggested options for replaceAll polyfill:
replaceAll polyfill (with global-flag error) (more principled version)
if (!String.prototype.replaceAll) { // Check if the native function not exist
Object.defineProperty(String.prototype, 'replaceAll', { // Define replaceAll as a prototype for (Mother/Any) String
configurable: true, writable: true, enumerable: false, // Editable & non-enumerable property (As it should be)
value: function(search, replace) { // Set the function by closest input names (For good info in consoles)
return this.replace( // Using native String.prototype.replace()
Object.prototype.toString.call(search) === '[object RegExp]' // IsRegExp?
? search.global // Is the RegEx global?
? search // So pass it
: function(){throw new TypeError('replaceAll called with a non-global RegExp argument')}() // If not throw an error
: RegExp(String(search).replace(/[.^$*+?()[{|\\]/g, "\\$&"), "g"), // Replace all reserved characters with '\' then make a global 'g' RegExp
replace); // passing second argument
}
});
}
replaceAll polyfill (With handling global-flag missing by itself) (my first preference) - Why?
if (!String.prototype.replaceAll) { // Check if the native function not exist
Object.defineProperty(String.prototype, 'replaceAll', { // Define replaceAll as a prototype for (Mother/Any) String
configurable: true, writable: true, enumerable: false, // Editable & non-enumerable property (As it should be)
value: function(search, replace) { // Set the function by closest input names (For good info in consoles)
return this.replace( // Using native String.prototype.replace()
Object.prototype.toString.call(search) === '[object RegExp]' // IsRegExp?
? search.global // Is the RegEx global?
? search // So pass it
: RegExp(search.source, /\/([a-z]*)$/.exec(search.toString())[1] + 'g') // If not, make a global clone from the RegEx
: RegExp(String(search).replace(/[.^$*+?()[{|\\]/g, "\\$&"), "g"), // Replace all reserved characters with '\' then make a global 'g' RegExp
replace); // passing second argument
}
});
}
Minified (my first preference):
if(!String.prototype.replaceAll){Object.defineProperty(String.prototype,'replaceAll',{configurable:!0,writable:!0,enumerable:!1,value:function(search,replace){return this.replace(Object.prototype.toString.call(search)==='[object RegExp]'?search.global?search:RegExp(search.source,/\/([a-z]*)$/.exec(search.toString())[1]+'g'):RegExp(String(search).replace(/[.^$*+?()[{|\\]/g,"\\$&"),"g"),replace)}})}
Try it:
if(!String.prototype.replaceAll){Object.defineProperty(String.prototype,'replaceAll',{configurable:!0,writable:!0,enumerable:!1,value:function(search,replace){return this.replace(Object.prototype.toString.call(search)==='[object RegExp]'?search.global?search:RegExp(search.source,/\/([a-z]*)$/.exec(search.toString())[1]+'g'):RegExp(String(search).replace(/[.^$*+?()[{|\\]/g,"\\$&"),"g"),replace)}})}
console.log(
'Change this and this for me'.replaceAll('this','that')
); // Change that and that for me
console.log(
'aaaaaa'.replaceAll('aa','a')
); // aaa
console.log(
'{} (*) (*) (RegEx) (*) (\*) (\\*) [reserved characters]'.replaceAll('(*)','X')
); // {} X X (RegEx) X X (\*) [reserved characters]
console.log(
'How (replace) (XX) with $1?'.replaceAll(/(xx)/gi,'$$1')
); // How (replace) ($1) with $1?
console.log(
'Here is some numbers 1234567890 1000000 123123.'.replaceAll(/\d+/g,'***')
); // Here is some numbers *** *** *** and need to be replaced.
console.log(
'Remove numbers under 233: 236 229 711 200 5'.replaceAll(/\d+/g, function(m) {
return parseFloat(m) < 233 ? '' : m
})
); // Remove numbers under 233: 236 711
console.log(
'null'.replaceAll(null,'x')
); // x
// The difference between My first preference and the original:
// Now in 2022 with browsers > 2020 it should throw an error (But possible it be changed in future)
// console.log(
// 'xyz ABC abc ABC abc xyz'.replaceAll(/abc/i,'')
// );
// Browsers < 2020:
// xyz xyz
// Browsers > 2020
// TypeError: String.prototype.replaceAll called with a non-global RegExp
Browser support:
Internet Explorer 9 and later (rested on Internet Explorer 11).
All other browsers (after 2012).
The result is the same as the native replaceAll in case of the first argument input is:
null, undefined, Object, Function, Date, ... , RegExp, Number, String, ...
Ref: 22.1.3.19 String.prototype.replaceAll ( searchValue, replaceValue)
+ RegExp Syntax
Important note: As some professionals mention it, many of recursive functions that suggested in answers, will return the wrong result. (Try them with the challenged case of the above snippet.)
Maybe some tricky methods like .split('searchValue').join('replaceValue') or some well managed functions give same result, but definitely with much lower performance than native replaceAll() / polyfill replaceAll() / replace() + RegExp
Other methods of polyfill assignment
Naive, but supports even older browsers (be better to avoid)
For example, we can support IE7+ too, by not using Object.defineProperty() and using my old naive assignment method:
if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function(search, replace) { // <-- Naive method for assignment
// ... (Polyfill code Here)
}
}
And it should work well for basic uses on IE7+.
But as here #sebastian-simon explained about, that can make secondary problems in case of more advanced uses. E.g.:
for (var k in 'hi') console.log(k);
// 0
// 1
// replaceAll <-- ?
Fully trustable, but heavy
In fact, my suggested option is a little optimistic. Like we trusted the environment (browser and Node.js), it is definitely for around 2012-2021. Also it is a standard/famous one, so it does not require any special consideration.
But there can be even older browsers or some unexpected problems, and polyfills still can support and solve more possible environment problems. So in case we need the maximum support that is possible, we can use polyfill libraries like:
https://polyfill.io/
Specially for replaceAll:
<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>
The simplest way to do this without using any regular expression is split and join, like the code here:
var str = "Test abc test test abc test test test abc test test abc";
console.log(str.split('abc').join(''));
var str = "ff ff f f a de def";
str = str.replace(/f/g,'');
alert(str);
http://jsfiddle.net/ANHR9/
while (str.indexOf('abc') !== -1)
{
str = str.replace('abc', '');
}
If the string contains a similar pattern like abccc, you can use this:
str.replace(/abc(\s|$)/g, "")
As of August 2020 there is a Stage 4 proposal to ECMAScript that adds the replaceAll method to String.
It's now supported in Chrome 85+, Edge 85+, Firefox 77+, Safari 13.1+.
The usage is the same as the replace method:
String.prototype.replaceAll(searchValue, replaceValue)
Here's an example usage:
'Test abc test test abc test.'.replaceAll('abc', 'foo'); // -> 'Test foo test test foo test.'
It's supported in most modern browsers, but there exist polyfills:
core-js
es-shims
It is supported in the V8 engine behind an experimental flag --harmony-string-replaceall.
Read more on the V8 website.
The previous answers are way too complicated. Just use the replace function like this:
str.replace(/your_regex_pattern/g, replacement_string);
Example:
var str = "Test abc test test abc test test test abc test test abc";
var res = str.replace(/[abc]+/g, "");
console.log(res);
After several trials and a lot of fails, I found that the below function seems to be the best all-rounder when it comes to browser compatibility and ease of use. This is the only working solution for older browsers that I found. (Yes, even though old browser are discouraged and outdated, some legacy applications still make heavy use of OLE browsers (such as old Visual Basic 6 applications or Excel .xlsm macros with forms.)
Anyway, here's the simple function.
function replaceAll(str, match, replacement){
return str.split(match).join(replacement);
}
If you are trying to ensure that the string you are looking for won't exist even after the replacement, you need to use a loop.
For example:
var str = 'test aabcbc';
str = str.replace(/abc/g, '');
When complete, you will still have 'test abc'!
The simplest loop to solve this would be:
var str = 'test aabcbc';
while (str != str.replace(/abc/g, '')){
str.replace(/abc/g, '');
}
But that runs the replacement twice for each cycle. Perhaps (at risk of being voted down) that can be combined for a slightly more efficient but less readable form:
var str = 'test aabcbc';
while (str != (str = str.replace(/abc/g, ''))){}
// alert(str); alerts 'test '!
This can be particularly useful when looking for duplicate strings.
For example, if we have 'a,,,b' and we wish to remove all duplicate commas.
[In that case, one could do .replace(/,+/g,','), but at some point the regex gets complex and slow enough to loop instead.]

Understanding angular js filter syntax according to documentation

According to this the "currency" filter takes amount as the first parameter given the following syntax:
{{ currency_expression | currency : amount : symbol}}
But in the following example it never passed the amount as a parameter:
<span id="currency-default">{{amount | currency}}</span>
I'm assuming that amount in the example refers to the currency_expression in the syntax as written in the documentation. They could have written it in the documentation in this way:
{{ currency_expression | currency : symbol}}
Another example is the filter filter with the following syntax:
{{ filter_expression | filter : array : expression : comparator}}
But in the following example it never specified the "source array" parameter:
<tr ng-repeat="friendObj in friends | filter:search:strict">
I'm assuming that friendObj in friends in the example refers to the filter_expression and search refers to the array if we're going to follow the syntax as written in the documentation. They could have written it in the documentation in this way:
{{ filter_expression_that_returns_array | filter : expression : comparator}}
I'm not so sure if I'm missing something but the documentation doesn't make sense to me given their examples.
My question is, should I simply ignore what the documentation says that the first parameter must be the input?
For what it's worth (since by experience we already know it is like that), the source code indicates that the expression only needs to be before the |.
Using the source for version 1.2.16 and without going into much detail:
ng/parse.js#L103
// In the OPERATORS hash:
var OPERATORS = {
...
'|': function (self, locals, a, b) {
return b(self, locals)(self, locals, a(self, locals));
},
...
ng/parse.js#L579
// `Parser`'s `filter()` method:
Parser.prototype = {
...
filter: function() {
var token = this.expect();
var fn = this.$filter(token.text);
var argsFn = [];
while (true) {
if ((token = this.expect(':'))) {
argsFn.push(this.expression());
} else {
var fnInvoke = function(self, locals, input) {
var args = [input];
for (var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
}
return fn.apply(self, args);
};
return function() {
return fnInvoke;
};
}
}
},
...
So, what did we learn ?
if ((token = this.expect(':'))) {
argsFn.push(this.expression());
The parser will get all tokens after the filter (separated by :) and put them in an array (argsFn).
var args = [input];
for (var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
}
At "runtime" (when the actual filtering is hapenning) an new array will be created (args) which will contain input (but what is input ? more on that later) and each parameter token that was previosuly stored in argsFn.
return fn.apply(self, args);
This args array will be the arguments list to the filtering function.
So, args contains input and the tokens after filter_name (as in expression | filter_name : param1 : param2).
If we can convince ourselves that input is indeed the expression (appearing on the left of the |, then we should be convinced that there is no need to have the expression appear as the first parameter after filter_name.
var fnInvoke = function(self, locals, input) {
...
return function() {
return fnInvoke;
};
filter() returns an anonymous function that when executed returns the function fnInvoke.
input is the third argument assed to fnInvoke when it is executed.
Now lets get back to the | operator:
'|': function (self, locals, a, b) {
...
It will result in calling this anonymous function with a and b being the left-hand side (expression) and the right-hand side (filter_name:param1:param2) respectively.
(In fact a and b are not the left- and right-hand sides, but they are functions that when executed return the result of evaluating the left- and right-hand sides in a given context (i.e. scope).
return b(self, locals)(self, locals, a(self, locals));
This tells us that the function returned by calling the anonymous function returned by filter() (b(self, locals)) will be executed with the following arguments:
`self`, `locals`, `a(self, locals)`
Which means that the mysterious input parameter (remember it was the 3rd argument of fnInvoke ?) is a(self, locals).
And a(self, locals) is basically the result of evaluating the left-hand side argument of the | operator in the context of the current scope, e.g. the result of evaluating a string ('someExpression') to the value of the property in the current scope ($scope.someExpression).
I don't know if you are convinced (I don't think I would have been).
I left much detail out of the explanation, but the interested reader can delve into that source and convince themselves :)
I feel kind of bad for posting such a longish answer with so little practical value. Sigh...
Personally, I would always read the source code when in doubt.
For your <span id="currency-default">{{amount | currency}}</span> example:
https://github.com/angular/angular.js/blob/master/src/ng/filter/filters.js#L50
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(amount, currencySymbol){
if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
replace(/\u00A4/g, currencySymbol);
};
}
Looks to me like amount then currency, so input must be first parameter.
Update.
Source for filter in HTML Template Binding context.
https://github.com/angular/angular.js/blob/master/src/ng/filter/filter.js#L116
function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
First if is a check for array, aka input, as first parameter.

Angular: infinite digest loop in filter

I'm writing a custom Angular filter that randomly capitalizes the input passed to it.
Here's the code:
angular.module('textFilters', []).filter('goBananas', function() {
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(Math.random())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});
I call it in my view like so:
<a class='menu_button_news menu_button' ng-href='#/news'>
{{"News" | goBananas}}
</a>
It works, but in my console I'm seeing a rootScope:infdig (infinite digest) loop.
I'm having some trouble understanding why this is happening and what I can do to resolve it. If I understand correctly, this is due to the fact that there are more than 5 digest actions called by this function. But the input is only called once by the filter, right?
Any help appreciated.
The problem is that the filter will produce a new result every time it is called, and Angular will call it more than once to ensure that the value is done changing, which it never is. For example, if you use the uppercase filter on the word 'stuff' then the result is 'STUFF'. When Angular calls the filter again, the result is 'STUFF' again, so the digest cycle can end. Contrast that with a filter that returns Math.random(), for example.
The technical solution is to apply the transformation in the controller rather than in the view. However, I do prefer to transform data in the view with filters, even if the filter applies an unstable transformation (returns differently each time) like yours.
In most cases, an unstable filter can be fixed by memoizing the filter function. Underscore and lodash have a memoize function included. You would just wrap that around the filter function like this:
.filter('myFilter', function() {
return _memoize(function(input) {
// your filter logic
return result;
});
});
Since digest will continue to run until consistent state of the model will be reached or 10 iterations will run, you need your own algorithm to generate pseudo-random numbers that will return the same numbers for the same strings in order to avoid infinite digest loop. It will be good if algorithm will use character value, character position and some configurable seed to generate numbers. Avoid using date/time parameters in such algorithm. Here is one of possible solutions:
HTML
<h1>{{ 'Hello Plunker!' | goBananas:17 }}</h1>
JavaScript
angular.module('textFilters', []).
filter('goBananas', function() {
return function(input, seed) {
seed = seed || 1;
(input = input.split('')).forEach(function(c, i, arr) {
arr[i] = c[(c.charCodeAt(0) + i + Math.round(seed / 3)) % 2 ? 'toUpperCase' : 'toLowerCase']();
});
return input.join('');
}
});
You can play with seed parameter to change a bit an algorithm. For example it may be $index of ngRepeat
Plunker: http://plnkr.co/edit/oBSGQjVZjhaIMWNrPXRh?p=preview
An alternative, if you want the behaviour to be truly random, is to do deal with the randomness only once during linking by creating a seed, and then use a seeded random number generator in the actual filter:
angular.module('textFilters', []).filter('goBananas', function() {
var seed = Math.random()
var rnd = function () {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
return function(input) {
var str = input;
var strlen = str.length;
while(strlen--) if(Math.round(rnd())) {
str = str.substr(0,strlen) + str.charAt(strlen).toUpperCase() + str.substr(strlen+1);
}
return str;
};
});

Resources