I want to use the following bindOnce directive:
.directive('bindOnce', function() {
return {
scope: true,
link: function( $scope, $element ) {
setTimeout(function() {
$scope.$destroy();
$element.removeClass('ng-binding ng-scope');
}, 0);
}
}
});
If I use this on a simple piece of html like so:
<span class="highlight" data-bind-once> "{{listing.searchTerm}}"</span>
What happens is that there is nothing but the "" being displayed!
I am loading my data using the $http service, I think the bind-once must get removed before I have loaded my data and then it obviously doesn't get bound.
I want to use this in many places in my app, is this a limitation or am I doing this incorrectly?
The newer versions of angular have the ability to bind once within them:
<span class="highlight"> "{{ ::listing.searchTerm }}"</span>
Link: https://docs.angularjs.org/guide/expression#one-time-binding
Related
I used some Syntax highlighting API for highlighting code snippet for my web application.To do that i have used highlightjs .I created popup model and inside model i have put <pre> tag and when model open it should display my highlighted xml string.
HTML Code snippet
<pre id="tepXml" ><code class="xml">{{tepXml}}</code></pre>
In AngularJs controller dynamically bind the value to tepXml from server.
AngularJs controller
...$promise.then(function(data){
$scope.tepXml=data.xml;
}
But the problem was that when i open popup model my xml content is empty.nothing display anything.But when i removed <code class="xml"></code> from <pre> xml content would display with out highlighting.I referred some posts and used $compile in angularJs controller but the problem was still the same.
AngularJs controller with $compile
var target = angular.element($window.document.querySelector('#tepXml'));
var myHTML = data.xml;
target.append( $compile( myHTML )($scope) );
If someone knows where i went wrong please point me out.
Plunker
The quick answer is to do:
$promise.then(function(data){
$scope.tepXml=data.xml;
// Call highlight api
$timeout(function() {
$('pre#tepXml code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
});
The more Angular way of doing things is to call a jQuery function from Angular is to write a Directive. Something like this:
.directive("highlightCode", function($interval) {
return {
restrict: "A",
scope: { highlightCode: "=" },
link: function(scope, elem, attrs) {
$scope.$watch('highlightCode', function() {
$(elem).find('code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
}
}
});
Used like this:
<pre id="tepXml" highlight-code="tepXml"><code class="xml">{{tepXml}}</code></pre>
i'm trying to get angularjs to work with jquery masonry. Without angular i have managed to get masonry to work fine including css. However, i have used angular to script the whole web app and i now load items dynamically via angular. Since masonry needs to be reloaded after dynamically adding data, i have written a directive to listen to last element of the items in angular repeat so that i can call the jquery masonry. When i call masonry, for some reason the items gets all weird up and does not load when it loaded initially without ng-repeat. Please check the code given below and tell me what im doing wrong here. I wish i had plunkr/js fiddler but since i pull data from a backend i cannot replicate the same.
The library i use is called "masonry.pkgd.js"
<div id="container" class="masonry js-masonry" data-masonry-options='{ "columnWidth": ".grid-sizer", "itemSelector": ".item", "isFitWidth": true }'>
<div class="grid-sizer"></div>-
<div ng-repeat="item in items" class="{{item.class}}" newsitemupdated>
<?php $this->load->view('item') ?>
</div>
</div>
app.directive('newsitemupdated', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
masonryUpdate();
});
}
}
}
});
var masonryUpdate = function() {
var $container = $('#container').masonry({
columnWidth: '.grid-sizer',
itemSelector: '.item',
isFitWidth: true
});
setTimeout(function() {
// $('#container').masonry();
$container.masonry('reloadItems');
console.log('called');
}, 500);
}
This is how it looks below i reload items.
This is how it looks after i reloaditems
Please tell me what im doing wrong and how i can call reload items after ng-repeat to load jquery masonry how its supposed to load.
I have fixed by doing the following
First i write a function to initialize my masonry with options
function BuildMasonry() {
var $container = $('#container').masonry();
$container.masonry({
columnWidth: '.grid-sizer',
itemSelector: '.item',
isFitWidth: true
});
}
Afterwards i will call it when ng-repeat finishes loading.
var masonryUpdate = function() {
$('#container').masonry('reloadItems');
BuildMasonry();
}
Hope this helps to someone out there :)
I have a directive that has a template such as:
<div ng-show="isVisible()"> ... </div>
I've changed isVisible so it now simply returns true and does a console.log.
This directive is included a single time on my page, yet the log occurs 50+ times. I want to know what code is calling isVisible so many times. Is there any way to trace what is calling a function? The ACTUAL stack trace is completely useless, because angular appears to hide everything.
Is there some tool to figure out what non-angular code triggered a function call.
In some other cases I have data changing and I can't find where it's changing [it's not actually my code] and because the original code is in angular, I can't just use stack traces, or use watches or do my normal type of debugging.
here is the directive:
require('app').directive('kmFormLanguages', ["$q", function ($q)
{
return {
restrict: 'EAC',
template: '<span ng-show="isVisible()"><span km-select multiple ng-model="binding" binding-type="string" placeholder="Available Languages" items="locales|orderBy:\'name\'" minimum="1"></span></span>',
transclude: true,
scope: {
binding : '=ngModel',
},
controller: ["$scope", "IStorage", "editFormUtility", function ($scope, storage, editFormUtility) {
$scope.locales = [
{identifier:"ar", name:"Arabic" },
{identifier:"en", name:"English" },
{identifier:"es", name:"Spanish" },
{identifier:"fr", name:"French" },
{identifier:"ru", name:"Russian" },
{identifier:"zh", name:"Chinese" }
];
$scope.isVisible = function() {
console.log('binding: ', $scope.binding);
debugger;
return $scope.binding!==undefined && $scope.binding!==null;
}
}]
};
}])
and the html which uses it (no angular controls that are ancestors are shown):
<div ng-controller="editMeasure">
[...]
<div ng-show="!isLoading() && status!='hidden'">
[...]
<span km-form-languages ng-model="document.header.languages"></span>
It is hard to say only from text. If you can post sample code, more accurate answers can be given.
But from what you have described, it seems that your function is being called as part of angular js
$digest or $compile
cycle.
I'm trying to build a pagination directive with angularjs 1.2.15:
This is my view:
<input type="text" ng-model="filter.user">
<input type="number" ng-model="filter.limit" ng-init="filter.limit=5">
<ul>
<li ng-repeat="user in (filteredUsers = (users | orderBy:order:reverse | filter:filter.user ) | limitTo: filter.limit)" ng-click="showDetails(user)">
{{user.id}} / {{user.firstname}} {{user.lastname}}
</li>
</ul>
<div pagination data='filteredUsers' limit='filter.limit'></div>
and here is my pagination directive:
app.directive('pagination', function(){
return {
restrict: 'A',
templateUrl: 'partials/pagination.html',
scope: {
data: '=data',
limit: '=limit'
}
}
})
Everything works perfectly fine without the pagination directive. However with my new directive as soon as I load the page I get a $rootScope:infdig error which I don't understand since the directive is not doing anything to manipulate data that could end up in an infinite loop.
What is the problem here and how can I solve it? Thanks!
Update:
Here are the controller and the resource.
Controller:
usersModule.controller('usersController',
function ($scope, Users) {
function init(){
$scope.users = Users.get();
}
init();
})
Resource (gets users as an array from a REST API):
app.factory('Users', function($resource) {
return $resource('http://myrestapi.tld/users', null,{
'get': { method:'GET', isArray: true, cache: true }
});
});
Update 2
Here is a demo: http://plnkr.co/edit/9GCE3Kzf21a7l10GFPmy?p=preview
Just type in a letter (e.g. "f") into the left input.
The problem is not within the directive, it's within the $watch the directive creates.
When you send filteredUsers to the directive, the directive creates the following line:
$scope.$watch("filteredUsers", function() {
// Directive Code...
});
Notice in the following example how we reproduce it without a directive:
http://plnkr.co/edit/uRj19PyXkvnLNyh5iY0j
The reason it happens is because you are changing filteredUsers every time a digest runs (since you put the assignment in the ng-repeat statement).
To fix this you might consider watching and filtering the array in the controller with the extra parameter 'true' for the $watch:
$scope.$watch("users | orderBy:order:reverse | filter:filter.user", function(newVal) {
$scope.filteredUsers = newVal;
}, true);
You can check the solution here:
http://plnkr.co/edit/stxqBtzLsGEXmsrv3Gp6
the $watch without the extra parameter (true) will do a simple comparison to the object, and since you create a new array in every digest loop, the object will always be different.
When you're passing the true parameter to the $watch function, it means it will actually do deep comparison to the object that returns before running the $watch again, so even if you have different instances of arrays that has the same data it will consider them equal.
A quick fix is to add a "manual" $watchCollection in the directive, instead of a 2-way binding.
app.directive('pagination', function($parse){
return {
restrict: 'A',
template: '',
scope: {
limit: '=limit'
},
link: function(scope, elem, attrs) {
var dataExpr = $parse(attrs.data);
var deregister = scope.$parent.$watchCollection(dataExpr, function(val) {
scope.data = val;
});
scope.$on('$destroy', deregister);
}
}
})
$watchCollection monitors the contents of the array, not the reference to it.
See it running here.
Generally speaking, i don't like expressions like that:
filteredUsers = (users | orderBy:order:reverse | filter:filter.user )
inside views. Views should only render $scope properties, not create new ones.
This error may remove to clear the browser history from setting. I got same issue and apply many solution to resolve this, But cannot resolve.
But when I remove browser history and cache this issue is resolve. May this help for you.
I have a pretty simple case in AngularJS where:
<select ng-repeat="el in elms" disabled="disabled" remove-disable>
<option>make a selection</option>
</select>
Initially my select is empty and so I added the disable attr to avoid having people click on it.
When the ajax call is completed and the select renders the list of options I want to remove the disable attribute.
It looks straight forward, right? but all I have seen is approaches using $watch and not for exactly this case.
I'm approaching it from a jQuery point of view where an looking at the DOM after the ajax call, finding the element and removing the attr. like this:
$('select').removeAttr('disabled');
Unfortunately I don't want to do jQuery, I want to do it with a directive, since that is what is for. the angular folks say that all DOM manipulations should be done via directives so I will like to know just how.
enrollmentModule.directive('removeDisable', function () {
return {
restrict: 'A',
scope: {
ngModel : '='
},
link: function (scope, element, attrs) {
console.log('no people yet');
if (element[0].complete) {
console.log('element finish rendering');
};
scope.$watch(attrs.ngModel, function () {
console.log('agents arrived');
});
}
};
});
AngularJS has a ngDisabled directive that you can use to make the link between the state of the list and an expression :
<select ng-repeat="el in elms" ng-disabled="elms.length == 0">
<option>make a selection</option>
</select>