Bind once in angular... and then again on each event - javascript

I've got a 400kb picture in a data url in my scope. I'm using <img ng-src='{{dataUrl}}'/>, which is working, but extremely slowly: I assume angular is checking this 400kb value against itself every single frame. The angularjs batarang confirms that 95% of my total run time is spent $watching {{dataUrl}}.
The dataUrl CAN change dynamically, but certainly not every frame - only when the user selects a new image.
How can I blast a new dataUrl into the DOM on events without using the full two-way binding features that are becoming so expensive?

The $watch function returns a callback just for that. You just have to execute it back to destruct the watcher.
var watcher = $scope.$watch('data', function(newValue, oldValue) {
if (newValue) {//you could do a isset here
watcher();
}
});
To re-watch. Might be expensive depending on the number of items on the current $scope
if(!$scope.$$phase) {
$scope.$digest()
}

You can use bindonce - a high performance one time binding for AngularJS that can render your results in 0 watches.
Example:
<ul>
<li bindonce ng-repeat="person in Persons">
<a bo-href="'#/people/' + person.id"><img bo-src="person.imageUrl"></a>
<a bo-href="'#/people/' + person.id" bo-text="person.name"></a>
<p bo-class="{'cycled':person.generated}" bo-html="person.description"></p>
</li></ul>
or:
<div bindonce="Person" bo-title="Person.title">
<span bo-text="Person.firstname"></span>
<span bo-text="Person.lastname"></span>
<img bo-src="Person.picture" bo-alt="Person.title">
<p bo-class="{'fancy':Person.isNice}" bo-html="Person.story"></p>
Update:
another One time binding for AngularJS as suggested by Ilan Frumer
Example:
<ul>
<li ng-repeat="user in users">
<a once-href="user.profileUrl" once-text="user.name"></a>
<a once-href="user.profileUrl"><img once-src="user.avatarUrl"></a>
<div once-class="{'formatted': user.description}" once-bind="user.description"></div>
</li></ul>

You can use one-way-databiding, like this
<img ng-scr="{{::dataUrl}}"/>
Or if you are using an angular version bigger than 1.3 there is an one bind embedded. That you can use on almost any directive. The $digest list will handled the watches only once, after loaded they will be destroyed.

Related

Angular: one-time binding, recompiling of nested ng-repeat

I am using one-time binding and kcd-recompile, but have a problem with nested ng-repeat. Let's assume the code looks like this:
<div ng-repeat="person in ::people">
<div ng-repeat="friend in ::person.friends" kcd-recompile="person.friends">
{{friend.name}}
</div>
</div>
If I add a friend to one of the people now, I want this person div to be recompiled without recompiling the other people's divs. The problem is: person.friends is only updated, if person is updated.
So it works if I add the kcd-recompile to the first ng-repeat, but then every person's div is recompiled (which I want to prevent).
Is there any possibility to update person without recompiling the whole first ng-repeat?
Found a solution on my own (after hours), if anyone needs it:
Using people[$index].friends instead of person.friends, it works!
<div ng-repeat="person in ::people">
<div ng-repeat="friend in people[$index].friends" kcd-recompile="person.friends">
{{friend.name}}
</div>
</div>

Trigger function on a certain element - ngrepeat - angularjs

Good morning,
I'm trying to change the limitTo filter on a certain list, my issue is:
when I click to the trigger who change the filter limit the filter changes on all ng-repeated categories.
my function inside the main controller
$scope.showMore = function(limit) {
if($scope.limitItems === $scope.itemsPerList) {
$scope.limitItems = limit;
$scope.switchFilterText = 'less';
} else {
$scope.switchFilterText = 'more';
$scope.limitItems = $scope.itemsPerList;
}
}
my scenario (I rewrote it in a simplified version)
<li ng-repeat="item in category.items | limitTo: limitItems ">
{{item.title}}
</li>
<li ng-if="limitItems < (category.items.length)">
<a ng-click="showMore(category.items.length)" >Show {{ switchFilterText }}</a>
</li>
Could you explain me what's wrong with me?
I searched how to select a single element to apply the function but I didn't find anything useful
Update:
I found the way to solve my issue in this way:
No functions inside the controller are involved to make this functionality works properly:
<li ng-repeat="category in maincategories" ng-init="limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: limitItems "> {{item.title}}
</li>
</ul>
<a ng-click="limitItems = category.items.length" href>
<b ng-if="category.items.length > maxItemsPerList && limitItems != category.items.length "> Show more </b>
</a>
I'm not really convinced about Angular (I used it in my past and I was impressed by the performance but now I can see logics senseless):
What I learned:
ng-if and ng-click cannot be used in the same content because ng-if creates new scopes so if you put ng-if on top of the "show more" link it will break the code
ng-init cannot be used in the same element of the ng-repeat otherwise the var initialised will not be available inside the ng-repeat block
I think there is another way to do that, maybe more clean but in this specific case I can't do a lot.
ng-if and ng-click cannot be used in the same content because ng-if
creates new scopes so if you put ng-if on top of the "show more" link
it will break the code
Yes, ng-if creates a new scope, but it is possible to mix ng-if and ng-click (and most other directives). To do that, you'll be safer if you always write to atributes of another object instead of a simple variable. It is plain JavaScript prototypal inheritance in play.
<li ... ng-init="category.limitItems = maxItemsPerList">
ng-init cannot be used in the same element of the ng-repeat otherwise
the var initialised will not be available inside the ng-repeat block
True, in the sense that variables are created in the local scope. But again, refer to an object.
I think there is another way to do that, maybe more clean but in this
specific case I can't do a lot.
You don't need to do a lot, it is quite simple to do it right actually.
Some advices:
Use ng-init with care. I know it will tempt us but always try to put logic inside controllers and services;
Avoid assignments inside templates;
Learn how to use controllerAs syntax. It gives you an object to write your models to (the controller), so solves most problems related to scope inheritance;
Do not inject $scope, put your view models inside controllers.
Full code goes like this:
<li ng-repeat="category in maincategories" ng-init="category.limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: category.limitItems "> {{item.title}}
</li>
</ul>
<a ng-if="category.items.length > maxItemsPerList && category.limitItems != category.items.length" ng-click="category.limitItems = category.items.length" href>
<b> Show more </b>
</a>

Can I use a JQuery plugin from within an angularjs directive?

I'm quite new to javascript in general. I've spent the past couple of weeks building a front end with Angular.js.
I have a number of directives I've defined that sit on my page, Angular has been great for this.
Here's what my main page looks like:
<body class="body" ng-controller="OverviewController as overview" font-size:1em>
<sidebar-menu ng-controller="PanelController as panel"></sidebar-menu>
<div id="content" >
<div>
<div class="list-group">
<div class="list-group-item" ng-repeat="site in overview.sites" ng-click="">
<div class="item-heading">
<h3>{{site.name}}</h3>
<p>Address: {{site.address}}</p>
Click Here
</div>
<installationsite-panels ng-controller="PanelController as panel"></installationsite-panels>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$('.paulund_modal').paulund_modal_box();
});
</script>
</body>
Note the javascript function to call a modal box at the bottom, using this tutorial.
I've spent the past few days trying different tutorials to get modals to work in my webapp, but with no success. I think it's down to my lack of understanding of Angular and Javascript in general.
In any case, I've managed to get this tutorial to work using JQuery, and when I click on the link, the modal opens as expected.
However, I don't want to call this modal from here. I want to call it from a directive that's embedded within the <installationsite-panels> directive in the above code, which looks like this (just a single section shown here):
Device Statuses
<div>
<div class="device-icon-group">
<div class="device-type1-icons" ng-click="panel.showDevices(3)" ng-show="showtype1Red"><img src="img/type1red.png" style="width:50%; height:50%;"/></div>
<div class="device-type2-icons" ng-click="panel.showDevices(3)" ng-show="showType2Red"><img src="img/type2red.png" style="width:50%; height:50%;" /></div>
</div>
<div class="service" ng-click="panel.showDevices(3)" ng-show="showService">
<b>{{panel.getServiceDeviceCount()}} device needs servicing</b>
</div>
<div ng-show="showServiceList">
<device-list-service></device-list-service>
</div>
</div>
The directive <device-list-service> shows a list of items like so:
<div ng-controller="DevicesController as deviceList" font-size:1em >
<div id="device-list-group">
<div id="device-list-group-item" ng-click="" ng-repeat="device in deviceList.devicesService">
<div ng-class="device.status"><img src="{{(device.type == 'type1') ? 'img/type1white.png' : 'img/type2white.png'}}"> </div>
<div class="device-params">
<b>ID: </b> {{device.id}}<br />
<b>Type: </b> {{device.type}}
</div>
<div class="device-params">
<b>Location: </b> {{device.location}}<br />
<b>Action: </b> {{device.action}} <br />
</div>
</div>
</div>
</div>
I want to show the modal when the user clicks on one of the list-group-item 's, and display some data relating to that item.
The modal works fine from the top level in the main app, but I cannot call it from within any of the directives. How can I do this?
Is it possible, or do I need to scrap my JQuery modal and do it the Angular way, which hasn't worked for me for the past few attempts.
Don't use jquery modals. You can, but you shouldn't.
Instead, I recommend using Angular UI, which has a pretty usable modal implementation: https://angular-ui.github.io/
Second alternative: if you don't like Angular UI, then use AngularJS + Bootstrap, and create your own custom directives
Third alternative: Use jQuery.
If you still want to go with the 3rd alternative, despite my advice against it, then here is how you do it:
var app = angular.module('app', []);
app.directive('modal', function($http, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attr) {
$timeout(function() {
element.paulund_modal_box();
}, 0, false);
}
};
});
Usage:
<div modal></div>
Some explanation is needed here.
Why is the $timeout service necessary? jQuery plugins often require the DOM to be fully loaded in order to work properly. That is why most jQuery plugins are wrapped inside of a $(document).ready block. In AngularJS there is no concept of DOM ready, and there is no easy way in AngularJS to hook into the event. However, there is a well-known hack, which is to use the $timeout service. In Angular there are three phases:
1. compile - Angular walks the DOM tree looking for directives
2. Link - Angular calls the link function for each directive to setup watch handlers.
3. Render - Angular updates the views
Using $timeout within the Link function queues the $timeout function to be executed until after the current closure is done executing. It just so happens that the Render phase is within the current closure's scope of execution. Hence, the $timeout function will execute after the render phase, when the DOM has been loaded.
Mixing JQuery and Angular in that way is maybe a little messy, but sometimes you do want to use a well-built component. You could try to find a similar modal in Angular - angular-modal - or you could try and build the component into your Angular directive itself - jQuery Plugins in AngularJS

accessing parent controller from ng-repeat

I have been learning angular and found this when trying to access the parent controller
http://jsfiddle.net/eqb23s8t/
I was expecting to access the same variable from the parent controller from inside the ng-repeat using the $parent so when one of the checkbox is pressed, all should be updated, but this is not true. Why ?.
<div ng-app ng-controller="ParentCtrl">
<ul>
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-checked="$parent.somevar" /></li>
</ul>
</div>
First, your jsFiddle has a ChildCtrl defined but it will have no effect because you never use it. You can delete it.
Second, as described in the ngChecked documentation, there is a difference between ngChecked and ngModel:
https://docs.angularjs.org/api/ng/directive/ngChecked
If what you're expecting to happen is have all the checkboxes check/uncheck together, you probably want ngModel rather than ngChecked.
It's not clear from your question what you're actually trying to do, but here's a fork of your jsFiddle illustrating data sharing through a $parent variable:
http://jsfiddle.net/7jzyk7f6/
It just does the following:
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-model="$parent.somevar" /></li>
to illustrate both concepts.
You are not bind a model for the view, which can reflect the changes. The current code just reads the model (in this case the somevar) state. Use ng-model instead of ng-checked. The angular will handle the rest:
HTML
<div ng-app ng-controller="ParentCtrl">
<ul>
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-model="$parent.somevar" /></li>
</ul>
</div>
JS
function ParentCtrl($scope) {
$scope.cities = ["NY", "Amsterdam", "Barcelona"];
$scope.somevar = true;
}
http://jsfiddle.net/eqb23s8t/4/
In this case, you need to use ngModel (two-way binding) instead of ngChecked (one-way binding):
ng-model="$parent.somevar"
See JSFiddle
I'm also totally newbie in Angular so I can be wrong. But I see few... things in your code. First of all ChildCtrl is not used at all. Second, it looks like only ng-model directive applied two-way binding to checkboxes. Yet ng-checked used just to add|remove checked attribute.
And js-fiddle
Sir please use ng-model to refer scope of the parent.
here is what i have created demo for you [demo][1]
[1]: http://jsfiddle.net/nwg7bwLx/

Angular JS: ng-switch on boolean not working

I am trying to conditionally display a directive based on a boolean value stored in the parent scope. I can't figure out why the below does not work. By, "not work" I mean neither directives are displayed.
<ul class="nav navbar-nav pull-right" ng-switch="userIsAuthenticated">
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when="false"></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when="true"></account-item>
</ul>
Neither directives are shown even thought "userIsAuthenticated" is set to 'false' in my test case. If I add {{userIsAuthenticated}} above the directives 'false' is output as expected.
I've also tried this:
<ul class="nav navbar-nav pull-right" ng-switch={{userIsAuthenticated}}>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when={{false}}></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when={{true}}></account-item>
</ul>
If I remove the conditional ng-switch-when attribute from either of the directives they will display. So I'm know the problem is my ng-switch.
Your usage of ng-switch works in this simplified demo, of course without your account-item directive:
http://plnkr.co/AppN8xmFeIwjaP631lj7
Without seeing the code for account-item, it is hard to guess what might be interfering with it. You might consider using ng-if to handle displaying one item or another.
<ul>
<div ng-if="!userIsAuthenticated">Content when not authenticated</div>
<div ng-if="userIsAuthenticated">Content when authenticated</div>
</ul>
Update
Also make sure you bind to an object property, instead of a primitive boolean. Like: user. authenticated
Since ngSwitchWhen has a priority of 800, you need to set a higher priority to your custom directive (i.e. account-item) in order for it to be compiled before being process by the ngSwitchWhen directive. E.g.:
.directive('accountItem', function () {
return {
...
priority: 900,
...
};
});

Categories

Resources