Function of isolated scope gets called multiple times - angularjs

I wrote an angular code to implement Like and remove Like functionality. For this purpose I used a directive "like-directive" which takes three functions
1. upFn : code to be executed when liked
2. downFn: code to be executed when like removed
3. upvChk: function which checks whether it is liked or not. I started this with false
For some reason I cannot use a variable with ng-if. I have to use the upvChk function. But this function is getting called more than twice. It should be called only twice because I'm using ng-if twice.
Here is the codepen link http://codepen.io/puneet27/pen/jApJww/
angular.module('app',[])
.controller('MainCtrl',['$scope',MainCtrl])
.directive('likeDirective',[likeDirective]);
function MainCtrl($scope){
var mcl = this;
mcl.checkUpvoted = checkUpvoted;
mcl.upvote = upvote;
mcl.downvote = downvote;
function checkUpvoted(){
alert("function to check whether liked or not");
return mcl.upvoted;
}
function upvote(){
alert('like completed');
mcl.upvoted = true;
}
function downvote(){
alert('dislike completed');
mcl.upvoted = false;
}
}
function likeDirective(){
return {
restrict: 'E',
scope: {
upFn:'&upFn',
downFn:'&downFn',
upvChk:'&upvChk'
},
templateUrl: 'tpl.html'
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="MainCtrl as mc">
<like-directive up-fn="mc.upvote()" down-fn="mc.downvote()" upv-chk="mc.checkUpvoted()"></like-directive>
</div>
<script type="text/ng-template" id="tpl.html">
<div><span id="upvote" ng-if="!upvChk()" ng-click="upFn()">Like</span><span id="upvoted" ng-if="upvChk()" ng-click="downFn()">Liked</span></div>
</script>
</div>

Related

Angular JS: Directive content is not available in Java script function

I have a div which has ng-click. When I click on that div, it calls a function which gets script content from a Directive and I append that to another div and access the content of the script. But when I retrieve the content of the directive I am getting directive name not the content. I want to get the content.
The function I call:
$scope.someFunction = function(){
var appendHtml = $compile("<my-custom-directive></my-custom-directive>")($scope);
$("#someId").append(appendHtml)
//But when i append I am seeing as <my-custom-directive></my-custom-directive> in html not the actual content
$(""#someId"").find('script')
}
Directive:
app.directive('myCustomDirective', function ($compile) {
return {
restrict: 'E',
templateUrl: '/somecontent.html',
replace: true,
link: function ($scope, elem, attr, ctrl) {}
};
});
Somecontent.html
<script type="text/template">
<div class="arrow" style="left: 50%;"></div>
some elements here
</div>
</script>
The HTML where I call from:
<div ng-click="someFunction()">
<div id="someId">
<my-custom-directive></my-custom-directive>
//But Here I am seeing this, when calling
$(appendHtml).find('script') in my javascript function, after Javasciprt function call is done, It works fine. But i want to see actual content here when calling $(""#someId"").find('script')
<div>
</div>
it is not a good practice.
you can use ng-if and binding instead , like the follwing:
HTML
<div ng-click="someFunction()">
<div id="someId">
<div ng-if="$scope.isVisible">
<my-custom-directive></my-custom-directive>
</div>
//But Here I am seeing this, when calling
$(appendHtml).find('script') in my javascript function, after Javasciprt function call is done, It works fine. But i want to see actual content here when calling $(""#someId"").find('script')
<div>
</div>
controller:
$scope.isVisible = false;
$scope.someFunction = function(){
$scope.isVisible = true;
}
you can also pass isolate scope param to your directive and check the param in the directive template
It's possible that you're just not using jQuery or jqLite to select elements correctly.
Your someFunction might need to look more like this:
vm.someFunction = function () {
var appendHtml = $compile('<my-custom-directive></my-custom-directive')($scope);
angular.element(document).find('some-id-element').append(appendHtml);
};
I put together this plunk that I think might achieve what you're trying to do.
Does this approximate your goal?

How to bind content to individual div with promise

This plnkr : https://plnkr.co/edit/BjETLN7rvQ1hNRIm51zG?p=preview binds content to three divs within loop : <div ng-repeat="id in ids">
src :
{ "content" : "divContent" , "id" : "r1" }
<!doctype html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<div ng-repeat="id in ids">
<div ng-bind="content" ></div>
</div>
</div>
</body>
</html>
// Example of how to call AngularJS $http service and
// process the returned promise
function FetchCtrl($scope, $http, $q) {
$scope.ids = ["r1", "r2", "r3"];
$scope.ids = $scope.ids.map(function(id){
var getString = 'http-hello1.html?id='+id
return $http.get(getString);
});
$scope.responses = [];
$q.all($scope.ids).then(function (values) {
var counter = 0;
values.map(function(m){
$scope.content = m.data.content;
})
})
}
But how bind the result of each get request to the specific div ?
Could add id : <div id="{{id}}" ng-bind="content" ></div> but this means I need to maintain a map of id,value entries ? Is there an idiomatic angularjs way to achieve this ?
I think a directive which dynamically fetches your content might be the answer for you.
angular.module('whateverYourModuleNameIs')
.directive('dynamicRow', ['$http', '$interval', dynamicRowDirectiveFn]);
function dynamicRowDirectiveFn($http, $interval) {
return {
restrict: "EA", // I guess this is your choice how it renders
scope: {
id: '=' // you could grab the id and use it in your request
},
link: function linkFn(scope, element, attrs) {
// Use $interval to repeatedly fetch content
var repeatFetchWhichReturnsAPromise = $interval(fetchNewContent, 60000 * 15) //Executes function every x milliseconds.
// Function that executes every time your interval occurs
function fetchNewContent() {
$http.get('urlYouNeedToFetch'+id).then(
fetchNewContentSucess, fetchNewContentError
);
}
function fetchNewContentSuccess(responseObject){
//This sets your new HTML based on promise success
element = responseObject.data;
}
function fetchNewContentError(responseObject){
//If its a bad request we probably either want to stop repeating
// You can choose to do something else
$interval.cancel(repeatFetchWhichReturnsAPromise);
}
}
}
}
So Instead of using $q.all(), Id recommend individually fetching the content based on a timer or specific trigger. The downside with $q.all() is that if one of the promises fail, they all fail.
In terms of knowing what specific URL the directive needs to fetch, you'll have to provide that information to the directive to be used.
This is a very rough example of a directive that you could write. The upside is that you don't have to worry about bind-unsafe-html or include ngSanitize, you are instead just resetting the value of element inside your link function.
As I don't have a better picture of what you are trying to accomplish from a feature/product standpoint I can only suggest this based on the info provided.

How can I access a factory from a directive?

I have some navbar items that should only be shown if the user is authenticated. Simplified view below:
NAVBAR
<body ng-controller="mainController as main">...
<li ng-show="main.isSecretAgent">THIS IS A SECRET</li>
</body>
FACTORY
tardis.factory('bgData', [function() {
var persistentData = {
isSecretAgent: false,
};
return {
checkIfSecretAgent: function(){
return persistentData.isSecretAgent
}
}
}]);
MAIN CONTROLLER
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = bgData.checkIfSecretAgent()
}
]);
Assuming the isSecretAgent value set in bgData factory can possibly change in response to user actions, how can I set my ng-show to update based on this?
You can have a function this way ...
NAVBAR
<body ng-controller="mainController as main">...
<li ng-show="main.isSecretAgent()">THIS IS A SECRET</li>
</body>
MAIN CONTROLLER
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = function {
return bgData.checkIfSecretAgent()
}
}
]);
Also it would be better if you use ng-if because in case of ng-show the element would be still present in the DOM.
If you write your ng-show using a function call:
<span ng-show="isSecretAgent()">Show me when I'm a secret agent</span>
Then every time a digest runs the value inside the factory will be checked. That's all you should need to do.
Except you should change your isSecretAgent to call the function as follows:
tardis.controller('mainController',["$scope","bgData",
function($scope,bgData) {
$scope.isSecretAgent = bgData.checkIfSecretAgent
}
]);
Rather than assigning to the function call, assign the function (remove the '()' at the end.

Template manipulation in Angular.js

I am trying to modify an Angular template before any other directive is triggered, in particular interpolation. I am doing this through the compile option in the directive definition.
Here is my test code:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script>
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
</script>
</head>
<body ng-app="soQuestion">
<div ng-controller="controller">
<div example foo="keyword_{{value}}"></div>
</div>
</body>
</html>
However, the final result that I get is <div foo="keyword_something"></div> instead of <div foo="prefix-keyword_something"></div>, even if the compile function was triggered properly. What is going on here?
Its a directive priority issue, and admittedly I still don't completely understand. But don't for get that {{}} is itself a directive. Its getting applied in some order with yours, and overwriting your manipulation. If its terminal and high priority, it clears up.
DEMO
angular.module('soQuestion', [])
.directive('example', function () {
return {
priority: 1000,
terminal : true,
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'), tElement[0]);
}
return function(){};
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
because this breaks the {{}} I would consider compiling the attr manually as well.
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
// Change the tAttrs hash instead of the element itself
tAttrs.foo = 'prefix-' + tAttrs.foo;
// Change the element too, in case no interpolation is present
tElement.attr('foo', tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
Explanation: the interpolate directive does check for changes in the attribute value. However it doesn't look again on the DOM node itself but on the tAttrs hash.
Old pessimistic answer:
I don't think it's possible to achieve the desired result.
Looking at Angular's source, collectDirectives puts together the list of directives that affect a certain node. Their respective compile functions are collected and then sorted by priority. The problem is that the compile function of {{}} is then bound to keyword_{{value}} and is not affected by the example directive.

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