Read AngularJS expressions in a script outside the controller function - angularjs

Because of the following quote in this thread
The best practice is never use JQuery or angular.element() for DOM manipulation inside controllers. Controllers are only used for our business logic.
I have moved a big function that calls CodeMirror and JQuery outside a controller. The big function is used to combine the content of two textareas. JSBin
Html:
<div ng-app="myApp" ng-controller="myCtrl">
<textarea ng-model="area1">area1</textarea>
<textarea ng-model="area2">area2</textarea>
<div>First: {{area1}}, {{area2}}</div>
<div id="output"></div>
<script>
function bigfunction(area1, area2) {
// a big function, the following is for the sake of simplicity
return "Second: " + area1 + ", " + area2
}
var output = bigfunction({{area1}}, {{area2}});
$("#output").html(output)
</script>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.area1 = "initial textarea1";
$scope.area2 = "initial textarea2"
});
Does anyone know why {{area1}} and {{area2}} in the script are not recognised?

The problem is that you're mixing JavaScript with AngularJS templates.
var output = bigfunction({{area1}}, {{area2}});
You cannot expect inline JavaScript to work using AngularJS templates. Here's what happens: the HTML file (template) is loaded and the inline JS is parsed and executed. It fails because it contains erroneous syntax ({{area1}}). This occurs before AngularJS is even loaded. Also, JavaScript code is evaluated but once, so even if the template actually replaced the {{area1}} with a string, the code would not be reevaluated and would not produce a different output. You will need to change your approach entirely.
I'm guessing that you would like to perform the joining of the two scope variables outside the controller and perhaps add extra logic to that. You can either move the code to a service and then use it in your controller, or use a directive (I don't know your exact use case, so it's hard for me to say which approach will make more sense).
Since what you described is simple concatenation of two model values, a directive appears to make sense. Your HTML could look like this:
<div ng-app="myApp" ng-controller="myCtrl">
<textarea ng-model="area1">area1</textarea>
<textarea ng-model="area2">area2</textarea>
<div>First: {{area1}}, {{area2}}</div>
<output area1="area1" area2="area2"></output>
</div>
And in the JS, add this simple directive:
app.directive('output', function() {
return {
restrict: 'E',
template: '<div id="output">Second: {{area1}}, {{area2}}</div>',
scope: {
area1: "=",
area2: "="
}
};
});
If for some reason you wish to add extra logic to your directive, you can always do that in the linking function.
Were you to add a service, the HTML would again require only a minor change:
<div ng-app="myApp" ng-controller="myCtrl">
<textarea ng-model="area1">area1</textarea>
<textarea ng-model="area2">area2</textarea>
<div>First: {{area1}}, {{area2}}</div>
<div>{{output}}</div>
</div>
And the JS could look like this:
app.controller('myCtrl', function($scope, joiner) {
$scope.area1 = "initial textarea1";
$scope.area2 = "initial textarea2";
$scope.$watchGroup(["area1", "area2"], function() {
$scope.output = joiner.join($scope.area1, $scope.area2);
});
});
app.service('joiner', function() {
this.join = function(a1, a2) {
return "Second: " + a1 + ", " + a2;
};
});
The service can contain any logic you need.

the script tag seem to have no access to angular vars. See Pass Angular scope variable to Javascript to check for a solution.

You can try like this:
$scope.area1 = "initial textarea1";
$scope.area2 = "initial textarea2";
function bigfunction(area1, area2) {
// a big function, the following is for the sake of simplicity
return "Second: {{area1}} {{area2}}"
}
var output = bigfunction($scope.area1, $scope.area2);
$compile(angular.element(document.querySelectorAll('#output')[0]).html(output))($scope);
or you can try directive like this:
app.directive('outputDir', function() {
return {
restrict: 'EA',
scope: {
area1: "=",
area2: "="
},
template: '<div>Second: {{area1}}, {{area2}}</div>'
};
});
All the best.

You could try this:
<script>
window.onload=function bigfunction() {
var area1=angular.element(document.querySelector('[ng-controller="myCtrl"]')).scope().area1;
var area2=angular.element(document.querySelector('[ng-controller="myCtrl"]')).scope().area2;
result= "Second: " + area1 + ", " + area2;
$("#output").html(output);
}
</script>
You can retrieve the angular js scope variable in javascript in this way.

Related

AngularJS using a custom filter in the html tag of a custom directive?

I have a directive with a template that looks like:
<span> {{foo.bar}} {{foo.car}} {{foo.dar}}</span>
which is called in multiple locations. The way it is called is:
<foo-dir foo="fooData"></foo-dir>
Is there any way to apply a custom filter in the html directive tag and not in the directive itself?
I'm hoping something exists such as:
ng-innertext-filter="filterName:params"
but it may be wishful thinking.
If that is not possible, is there a way to use the filter in the directive controller without having to apply it everywhere the directive is used?
Thanks.
Edit: filter code:
import angular from 'angular';
function someFilter() {
return (input, filterParam) => {
if (input && filterParam && input.length)
if(filterParam)
return input.filterLogic();
return input;
}
}
The filter works fine when in a templated string {{somedata | someFilter}}. But I am looking for a way to apply the filter to the directive tag itself.
To do exactly what you want:
use the filter on the resulting string from a directive in the html tag
you need to create a template string and recompile your DOM.
This is a quite advanced use of AngularJS. I hope you enjoy it :-)
var app = angular.module("app", []);
app.controller('ctrl', function ($scope) {
$scope.foo = {
bar: "1",
car: "2",
dar: "3"
};
});
app.directive('foodir', function ($compile) {
return {
restrict: 'E',
scope: {
foo: '=', // Bind the variable to the directive's scope
filter: '#' // Just get the string for compilation
},
link: function ($scope, $element, $attrs) {
// This is the string on which you may want to apply a filter
var string_template = '" bar:" + foo.bar + " car:" + foo.car + " dar:" + foo.dar';
// Full string to compile without filter
var html = '<span>{{' + string_template + '}}</span>';
if ($scope.filter !== undefined)
// Full string to compile with filter (optional)
html = '<span>{{' + string_template + ' | '+$scope.filter+'}}</span>';
console.log(html);
var e = $compile(html)($scope); // Convert your string to a DOM element
$element.replaceWith(e); // Make it the content of the directive
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="app" ng-controller="ctrl">
<foodir foo="foo"></foodir>
<hr/>
<foodir foo="foo" filter="uppercase"></foodir>
<hr/>
<foodir foo="foo" filter="limitTo:10"></foodir>
</body>
Now, if you just want to apply a filter to some parts of your template (and not the full string), you can avoid the string_template and use basic interpolation like this:
var html = '<span> {{foo.bar}} {{foo.car}} {{foo.dar |' + $scope.filter + '}}</span>'
I'm not sure what the first part of your question means. If you're wanting to apply this filter to the compiled result of a directive, I'm not sure you can. I can help with using a filter in a controller, though.
If you define a custom AngularJS filter like so:
app.filter('makeItPretty', function() {
return function(value) {
return value.toUpperCase();
};
});
Then you can access your filter from within a controller by injecting the built-in $filter service:
app.controller('MyController', function($filter) {
var makeItPretty = $filter('makeItPretty');
var myPrettyText = makeItPretty('tHiS iS UgLy!');
});

Replace {{test}} with DOM "<div>example</div>"?

Is there any way to replace a controller's $scope.test with actual HTML? I know that you can set $scope.test to numbers, letters, etc, but how would you inject a template into the binding?
Are you talking about having $scope.test = '{{test}}' and then getting that to parse further, or are you talking about something like $scope.test = '<div>example</div>'.
If it's the later, just do what I did and set it to a string that includes the HTML. If $scope.test is coming from somewhere (like a form input) and you can't just set the variable directly, you could also create a custom filter and do something like {{test|wrapInDiv}}.
Here is an example filter:
angular.module('myapp', [])
.filter('wrapInDiv', function() {
return function (input) {
return '<div>' + input + '</div>';
};
});
As ebinmanuval mentioned, you can also use ng-bind-html which outputs the HTML as a child of the element you put the ng-bind-html on.
<p ng-bind-html="test"></div>
Where test is the $scope.test variable.
use ng-bin-html with custom trust filter to bind html to dom
<span ng-bind-html="test |trust"></span>
.filter('trust', [
'$sce',
function($sce) {
return function(value, type) {
return $sce.trustAsHtml(value);
}
}
]);
checkout https://jsfiddle.net/ebinmanuval/0tLso4vg/

data-binding doesn't work after using "appendChild()" to add new element

when i write like this, angular works:
<html ng-app="mptmanager" class="ng-scope">
<body>
<input ng-model = "test" /><br />
{{test}}
</body>
</html>
<script src="js/angular.min.js"></script>
<script src="angular/angularModules.js"></script>
but when i run JS code like this:
var temp = document.createElement('div');
temp.innerHTML = '<input ng-model = "test2"><br />{{test2}}';
document.body.appendChild(temp);
angular has no effect to the new element, so i can only see "{{test2}}" on my page...
how can i make angular work when i try to add or change some element on my page with JS?
You should never do DOM manipulation outside of directives when using Angular. Just to be complete, I'll show you how to make that work anyway. Just inject the $compile service and compile the new markup with the scope.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $compile) {
$scope.foo = 'Default value';
var elem = document.createElement('div');
elem.innerHTML = '<input ng-model="foo">{{foo}}';
document.body.appendChild(elem);
$compile(elem)($scope);
});
Here are proper approaches using directives:
<div my-directive></div>
<div my-directive2></div>
JavaScript:
app.directive('myDirective', function() {
return {
template: '<input ng-model="foo">{{foo}}'
};
});
app.directive('myDirective2', function() {
return {
compile: function(element) {
element.html('<input ng-model="foo">{{foo}}');
}
};
});
In your directive, if you use the link function, which has access to scope, you will have to manually $compile the markup as I demonstrated above, but be careful to avoid an infinite loop. You will usually do this: $compile(element.contents())(scope); If you $compile the element itself, you would create an infinite loop.
Live demos (click).
As dskh briefly mentioned, directives are used to do that kind of stuff in Angular.
The issue here is that Angular doesn't know about the new element, as the routine which adds it lives outside of the Angular scope. You could call $scope.$apply() manually, but i won't consider this best practice.

Angular.js $compile returns array of html but not actual html

I have the following code:
app.directive('mySample', function($compile) {
return {
//template:"<input type='text' ng=model='sampleData'/> {{sampleData}} <br/>"
link: function(scope, element, atts, controller) {
var markup = "<input type='text' ng=model='sampleData'/> {{sampleData}} <br/>";
angular.element(element).html($compile(markup)(scope));
console.log($compile(markup)(scope));
}
};
});
And I would expect it to generate an input, some span that's coupled via the scope and a break. However I get this output:
[[object HTMLInputElement], [object HTMLSpanElement], [object HTMLBRElement]]
I also tried the template, in comment here, separately and then commenting out the link part. That generates the input and break elements but not the span that shows the coupled model input sampleData.
I have a non-working sample at http://jsfiddle.net/KvdM/nwbsT/ that demonstrates it.
Try this:
element.html(markup);
$compile(element.contents())(scope);
Running the function returned by the $compile service gives you DOM elements rather than html.
So you need to insert them into your page using append (or equivalent):
angular.element(element).append($compile(markup)(scope));
Maybe the easiest way is to use a hard-coded template rather than a dynamic generated one
<div ng-app="myApp">
<my-sample sample-data="'test'"></my-sample>
</div>
var app = angular.module('myApp', []);
app.directive('mySample', function ($compile) {
return {
restrict: 'E',
scope: {
sampleData: '=sampleData'
},
template: "<input type='text'/> {{sampleData}} <br/>",
};
});
FIDDLE
Depends on what kind of data should to be compiled, some times Angular returns a comment node type.
The relevant node that we want to use is the next() (its first sibling).
var tpl = '<div class="myWidget" ng-include="'templates/myWidget.html'"></div>;
var _myWidget = $compile(tpl)(scope);
var myWidget = null;
scope.$on('$includeContentLoaded', function () {
myWidget = _myWidget.next();
});
You just needed to add the jquery to use ".html" and fixed the naming of ng-model

Getting MathJax to update after changes to AngularJS model

I am trying to use AngularJS two-way binding text which includes Latex style equations. I would like to call MathJax to format the equations, but I'm not sure of the best way to ensure that MathJax is called after AngularJS finishes changing the model. I think I need a callback. Here is my JavaScript:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Update = function() {
$scope.Expression = 'Evaluate: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
$scope.Expression = 'Evaluate: \\( \\frac{5}{4} \\div \\frac{1}{6} \\)';
}
And here is my HTML:
<div ng-controller="MyCtrl">
<button ng-click="Update()">Update</button>
{{Expression}}
</div>
Fiddle is here: http://jsfiddle.net/LukasHalim/UVjTD/1/. You'll notice that on the fiddle the original expression isn't removed even after you click the update button twice - seems like a bug or conflict.
Having wasted many days (and maybe weeks) fighting MathJax, I'm all too familiar with its various quirks with updating math expressions on the fly. I'm brand new to Angular but this gave me a good chance to dive in and I ended up with a solution which solves my problems -- hopefully it'll solve yours as well.
Live demo: jsfiddle
Instead of using the plain interpolation that Angular provides, I created a new directive based on ng-bind called mathjax-bind.
If expression is a variable containing math code, then instead of \( {{expression}} \) you can write:
<span mathjax-bind="expression"></span>
and everything will be typeset and updated at the appropriate times.
The supporting code for the directive follows:
myApp.directive("mathjaxBind", function() {
return {
restrict: "A",
controller: ["$scope", "$element", "$attrs",
function($scope, $element, $attrs) {
$scope.$watch($attrs.mathjaxBind, function(texExpression) {
var texScript = angular.element("<script type='math/tex'>")
.html(texExpression ? texExpression : "");
$element.html("");
$element.append(texScript);
MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
});
}]
};
});
Simplest, fastest and most stable solution:
$rootScope.$watch(function(){
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
return true;
});
Advantages:
Easy to setup, just copy this code.
Everything on your page is typeset.
It renders much faster than the other solutions. This is because it can render the page in one go. Other answers here wait for one item to finish, until they typeset the next one. That makes rendering veeeery slow if there are for example multiple mathjax-bind directives (as another answer suggests). This point is the reason I was looking for a different answer.
You can still easily exclude elements using the option “ignoreClass” in your mathjax settings.
Benchmarking:
100 mathjax-bind directives took 63 seconds, while with this method it took 1.5 second to render the page. I know that this function will be executed a lot since it's called on every digest cycle, however, it doesn't noticeably slow down the page.
I created a simple fiddle expanding on Ben Alpert's answer. Here's the fiddle and plunk.
Specifically If a text has only a part of it to be converted by Mathjax, you can use this.
For inline mathjax you must surround the text by $, and for block display you must surround the block by $$. (You can use any format you like if you create the corresponding regex)
app.js
MathJax.Hub.Config({
skipStartupTypeset: true,
messageStyle: "none",
"HTML-CSS": {
showMathMenu: false
}
});
MathJax.Hub.Configured();
var myApp = angular.module("myApp", []);
myApp.directive("mathjaxBind", function() {
return {
restrict: "A",
scope:{
text: "#mathjaxBind"
},
controller: ["$scope", "$element", "$attrs", function($scope, $element, $attrs) {
$scope.$watch('text', function(value) {
var $script = angular.element("<script type='math/tex'>")
.html(value == undefined ? "" : value);
$element.html("");
$element.append($script);
MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
});
}]
};
});
myApp.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
html = html.replace(/\$\$([^$]+)\$\$/g, "<span class=\"blue\" mathjax-bind=\"$1\"></span>");
html = html.replace(/\$([^$]+)\$/g, "<span class=\"red\" mathjax-bind=\"$1\"></span>");
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
function MyCtrl($scope, $element) {
$scope.html = "A coin of is $ \\frac{5}{4} $ thrown $$\\frac{1}{6}$$ dfv";
}
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML&delayStartupUntil=configured&dummy=.js"></script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="http://code.angularjs.org/1.2.7/angular.js" data-semver="1.2.7"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<input type="text" ng-model="html"/><br/>
<div dynamic="html"></div>
</div>
</body>
style.css
input[type="text"] {
width: 800px;
}
.red{
color:red;
display:inline-block;
}
.blue{
color:blue;
display:block;
}
Take a look at http://jsfiddle.net/pz5Jc/
In your template:
{{Label}} <span id="mathElement">{{Expression}}</span>
In your controller:
$scope.Update = function() {
$scope.Expression = '\\frac{9}{4} \\div \\frac{1}{6}';
$scope.Label = 'Updated Expression:'
var math = MathJax.Hub.getAllJax("mathElement")[0];
math.Text('\\frac{4}{4} \\div \\frac{2}{6}');
}
Couple of points:
I'm not too familiar with mathjax, but:
Splitting the label out from the expression allows you to work with the expression directly.
You need to manually pick up a DOM element to force a refresh of the expression. This isn't a very 'angular' way to do things unfortunately - but when mathjax parses the expression (and inserts it's own DOM elements), it pushes those elements outside the angular bindings.
Fix here is to specifically select the correct mathjax element and call a text change function to update the expression.
Here's a directive that lets you use double curly markup inside the expression (and doesn't require setting an expression variable on the scope). It's based on this blog post, except I only support MathJax, and I save the compiled DOM, so that it updates on changes to scope variables.
As Alex Osborn said, it's best to separate non-math from math.
Usage:
<p>This is inline math: <latex>x^{ {{power}} }</latex>,
and this is display math: <div latex> y^{ {{power}} } .</div></p>
In a snippet:
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.power = "\\sin(x^2)";
})
.directive('latex', function() {
return {
restrict: 'AE',
link: function(scope, element) {
var newDom = element.clone();
element.replaceWith(newDom);
var pre = "\\(",
post = "\\)";
if (element[0].tagName === 'DIV') {
pre = "\\[";
post = "\\]";
}
scope.$watch(function() {
return element.html();
}, function() {
console.log(element);
newDom.html(pre + element.html() + post);
MathJax.Hub.Typeset(newDom[0]);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<div ng-app="app" ng-controller="ctrl">
<p>Power:
<input ng-model="power" />
</p>
<p>This is the inline latex,
<latex>x^{ {{power}} }</latex>, followed by some display mode latex
<div latex>y^{ {{power}} } = {{power}}.</div>And that's it!
</p>
</div>
A simple solution is to use $timeout to put MathJax.Hub.Queue(["Typeset", MathJax.Hub]) in the browser event queue (see Run a directive after the DOM has finished rendering).
Something like this:
var app = angular.module('myApp', []);
app.controller('myController', function ($scope, $timeout) {
controller = this;
$scope.Update = function () {
$scope.value = " \\( \\frac{5}{4} \\div \\frac{1}{6} \\)";
$timeout(controller.updateMathJax, 0);
}
this.updateMathJax = function () {
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
});
You can try with my modifications http://jsfiddle.net/bmma8/4/
modify input or click on button will update your expression.
js:
MathJax.Hub.Config({
extensions: ["tex2jax.js"],
jax: ["input/TeX","output/HTML-CSS"],
tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}
});
var myApp = angular.module('myApp',[]);
function MyCtrl($scope, $log) {
var QUEUE = MathJax.Hub.queue; // shorthand for the queue
$scope.Update = function() {
QUEUE.Push(["Text",MathJax.Hub.getAllJax("MathOutput")[0],"\\displaystyle{"+ $scope.Expression+"}"]);
//$scope.Expression = 'Updated Expression: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
//MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
$scope.Expression = 'Original Expression: \\( \\frac{5}{4} \\div \\fra
and html:
<div ng-controller="MyCtrl">
<button ng-click="Update()">Update</button>
<input ng-model="Expression" ng-change="Update()">
<div id="MathOutput">
You typed: ${}$
</div>
</div>
Alexandre
I actually thought of another solution. When you render some angular and math you do this:
ANGULAR CONTROLLER
$scope x = 5;
HTML
<h3> {{ '$ Multiplication = '+ x + ' * 2 =' + (x*2) + '$'}} </h3>
Formated Math Jax result
Multiplication = 5 * 2 = 10
The key is to include the dollar signs inside the brackets as text. When Angular renders them, the dollar signs will appear as plain text, but when the Math Jax format comes into action it will recognize the dollar signs and do the magic.
I Build a directive for this....
FIDDLE: http://jsfiddle.net/8YkUS/1/
HTML
p data-math-exp data-value="math">
JAVASCRIPT
appFlipped.directive("mathExp", function () {
return {
scope: {
value: "="
},
link: function (scope, el) {
var domEl = el[0];
scope.$watch("value", function (newValue) {
//nothing to do here
if (newValue == null || window.MathJax == null)return;
//update the dom with the new value and pass the hub for styling the equation
domEl.innerHTML = '`' + newValue + '`';
MathJax.Hub.Queue(["Typeset", MathJax.Hub, domEl]);
});
}
}
});
I fiddled a bit more on Roney's solution. The display math should be displayed in display mode; with
<script type="math/tex; mode=display">
I added an attribute to the generated span to indicate that.
Fiddle is here http://jsfiddle.net/repa/aheujhfq/8/

Resources