What's the angular way to handle redraws? - javascript

I'm making a charting application that allows you to create graphs using a drag and drop interface.
I have highcharts and I'm using the highcharts-ng directive.
This directive watches the title, options, and series. And when a person makes a change, I process them and make changes to the options object. Then highcharts-ng redraws the chart.
The problem I'm finding is that I change a few properties in a row such as options.xAxis and options.yAxis, and whenever I do this the application is lagging a bit because it's launching a redraw for every change.
So what would be the angular way to approach this, while still being efficient?
A potential solution I thought of was to add a flag to the highcharts-ng directive, and have it trigger whenever it's changed. And then just change it after I'm done processing the data. Another potential solution is to listen for a certain event inside the highchart-ng directive, and then trigger the redraw whenever that event is received. But these solutions seem/feel a bit hacky to me.

Angular does its own dirty checking and (ideally always, not but really) rewrites the Angular-controlled sections of the DOM whenever their corresponding view models change. I think that this behaviour is so fundamental to Angular that if you don't like it, you'd either better work around it, or use a different databinding framework.
The workaround I'd recommend is basically what you described in your first option: a view model inside the view model. Have a private variable inside the directive's scope which tracks the changes you're interested in, the ones which happen more frequently than you want to redraw. Then when you're ready to redraw (you'll need your own logic for what determines "ready"...time? a particular kind of change? a particular threshold of change?), update the real view model by setting your private variable back to its original field on the real view model.
Code sketch:
// (inside the directive)
var _options = $scope.options;
// ...
// rapidfire updates happen; save them to _options rather than $scope.options
// ...
// now you're ready to redraw:
$scope.options = _options; // angular now knows $scope is dirty and triggers the redraw

Related

Cytoscape event cxttap called multiple times

I am trying to create a network workflow with Cytoscape and AngularJS 1.6
After creating two nodes, user is able to create an edge between them. For this I am using cy.on('cxttap') cytoscape function to detect right clicks on both the nodes and then insert a new edge.
This works fine. Until I added a new view, where the user can see saved workflows from the database. The problem is, if I open the Viewing tab and then go back to creation tab, the cy.on('cxttap') function is called twice, and two edges are inserted on the cytoscape canvas, but only one entry is made in my scope variable. I used to have the same factory for both the views but now I am using different angular factories for each.
If I open switch between these tabs multiple times, the number of times I open the viewing tab, is the number of times the function is called, hence more the number of lines.
Here (https://jsfiddle.net/y47kwpg7/4/) is the snippet with the code for both my views with their controllers. "Creation View" --> "MlalTextWorkflowGeneratorCtrl" and "Viewing View" --> "MlalTextWorkflowViewerCtrl".
(Please forgive me, I don't know how to make it work in a single file)
The factory I have here is for the cytoscape canvas, and have a similar one for another view, but with a different id selection.
Thank you for your help!
You either need to
(1) remove old listeners if you're re-using an instance, or
(2) create a new instance each time you create a new view.
#maxkfranz 's answer guided me to do a little more debugging and finally I found out the problem.
The problem was I had some listener functions that I called from my controller in order to detect events on the cytoscape nodes. And the listeners to fire were stored in a a variable workflowGraph.listeners. you can see the old code []1. The problem was this variable was not emptied anytime my controller was reloaded, because I was calling the listeners from my controller, they were being added to this variable once again. So, one listener had multiple functions to call.
The method I used to solve this was to create a new "listener" type function I had earlier for cytoscape events, which I would call everytime my controller is loaded, to empty the listeners variable for my cytoscape factory.
In cytoscape factory
workflowGraph.reinitialize = function () {
workflowGraph.listeners = {};
};
In controller
workflowCreation is the name of the factory
workflowCreation.reinitialize();
This would simply empty the listeners variable from the factory.
Remember to call this in your controller after cytoscape initialisation and before calling any other listener functions
Hope this helps someone.

AngularJS - Idiom for watching a "single use" variable?

I have a select input with a controller that- when a value is selected- wants to pass that value to a directive that will display it. (In this case, draw a shape on a leaflet map).
The issue is I want the value to be "fire and forget"- so they select the shape, the select box resets to empty again immediately, and the directive in charge of drawing receive the shape and draw it.
My initial thought is to have a service that links the two: so when the shape is selected, the select controller updates the service and sets the drawThisShape variable, which is referenced on the directives $scope and being $watched: when it changes, the directive draws the shape. Happy days.
This seems like a lot of effort to go to just to send a variable from one place to another though. And the service gets left in a state of constantly having the "previous" shape on it's model despite it not being needed anymore.
Perhaps I'm being too fussy but I feel like there must be a better way to achieve this?
E: For clarification, the controller is the controller for the select input, the directive I want to pass it to is a directive for a leaflet map instance, they are not linked in any way
Well, I have used the technique you've described in the past. Another way of achieving what you need is to broadcast an event on the $rootScope (or any other parent scope of drawShape directive) from the select input that the drawShape directive is listening for.
See scope event propogation and broadcasting

Undo Redo In Angular JS

I have a big object lined in $rootScope (say > 100 objects and each having hierarchy of objects/array again), I want to $watch entire $rootScope with deepWatching(i.e. turning 3rd parameter of $watch to TRUE).
But the problem here is, $watch returns 2 objects (i.e. one Old RootScope and Modified RootScope). Then I have to do a process of checking what attribute of object changed in $rootScope and its hierarchy to PUSH it into stack.
Do we have an easy way out to get the exact attribute changed while watching a $scope?
$scope.$watch($rootScope, function(oldObj, newObj){
//all I want here is exactly what attribute changed, NOT entire objects!
}, true);
Alternatively, I could add watch on each attribute of the Object but it appears to be extremely expensive.
What is the best way to achieve undo/redo in angular js?
Note :-
angular-history doesnt suit my need because I want to watch all attributes of object, which may also contain other objects and array.
I'm certain watching entire $rootScope is not a good idea either, but I have aimed to build an UI which has several grids, drag n drop, might contain form, elements can be deleted. So I want to build an overall solution to stack the changes and undo it on CTRL + Z. Imagine replicating desktop version of Photoshop.
Undo/redo is based on the command pattern. And yes, there will be a lot of watches; such a rich functionality does not come cheap.
Just for the fun of it, here is a simple (but quite usable and extensible) implementation: http://jsfiddle.net/sYc4e/1/
A command is an interface for objects that know how to apply and rollback a change:
function XxxCommand() {
// implementation specific
}
Command.prototype.execute = function() {
// implementation specific
};
Command.prototype.rollback = function() {
// implementation specific
};
Usage: You decorate the <input>s with a directive:
<input name="name" undoable ng-model="data.name" />
And wrap them with an element (e.g. the <form>) with the undo-support directive:
<form undo-support>
The link function of undoable listens for a change in the <input> and registers a command with the undoableSupport. The controller of undoableSupport keeps track of the command list.
There's also an undo/redo library called Angular-Chronicle. For myself at least, it was a better option than angular-history.
The library angular-history provides undo/redo.
Currently not maintained. Most recent fork seems to be https://github.com/domoritz/angular-history.
instead of watching the whole $rootScope or for that matter any one object that contains a lot of items which in turn contain a lot of items requiring you to iterate through that nested array on every digest, I would use an array(using it as a queue) of changes that are applied to the original data model and a variable that points to a current location in that queue.
I would put that array and that variable in a service and then change that index whenever undo/redo happens. You can watch that variable from everywhere that you need to respond to these changes, like directives
I think the LazyJsonUndoRedo is what you're looking for.
https://github.com/azazdeaz/LazyJsonUndoRedo
From the readme:
A 'drop in' history handler with automatic undo/redo functionality for nested javascript objects, using ES6 Object.observe() or Polymer shim.
http://dailyjs.com/2014/07/18/lazy-json-undo/

Angular 1.2.0-rc.3 Directive Priority Change Issue

So I have this code example that uses Angular 1.2 RC2 and everything works fine, you click on the handle to toggle the display of the content and the controller and directive have seperate scopes as intended:
http://plnkr.co/edit/e3XAZuhSMAxmkWzKKM39?p=preview
Now I upgraded to Angular RC3 yesterday and now the functionality does not work as it stands in the plunker, I get the error the specific requires generic which is can't find. Going through the change log, I though this might have to do with this breaking change:
$compile: due to 31f190d4, the order of postLink fn is now mirror opposite of the order in which corresponding preLinking and compile functions execute
To fix this they either suggest converting the post linking to a pre linking (which I can do since my post linking needs access to the scope which is not available in the pre linking) or to decrease the priority of the directive. So this plunker does that and functionality does work:
http://plnkr.co/edit/arP3aruH8HEdiwFg6mWp?p=preview
However there is a major issue and that is now because specific has a higher priority, the isolate scope that generic needs is no longer being created so now contentVisible is on the controller scope which is not good.
Now I could just move the scope: {} from the generic directive to the specific directive however it should be possible to use the generic directive by itself and if I did it would attached to whatever scope it is part of and not its own (which would make it impossible to have multiple instance of this directive, which is way it needs its own scope).
The only thing I can think of is to add a directive, called something like isoScope, make sure it has a very high priority, and have it define scope: {}. Then if I need to use generic by itself, I just have to make sure to also add the isoScope directive to make sure it has an isolate scope. Like this:
http://plnkr.co/edit/1NYHpUcPFWEbAzvkCeRH?p=preview
Now I am hoping there is a better way to accomplish what I am looking for without the isolateScope directive. Am I missing a way of that this without that?
UPDATED EXAMPLE
So here is another plunker that includes hopefully better examples of what I am trying to convey (still has virtually no styles but should not be needed to get the point across):
http://plnkr.co/edit/KtRMa1c9giDrhi1Rqyho?p=preview
I have 3 directives:
expander
notification
isolateScope
The expander directive only adds functionality to be able expander and collapse content, nothing more. This functionality is something that should be able to be used alone or with another directive (which is why it has a controller).
The notification directive is used to display notification however since we don't want to display the notifications all the time, we use it with the expander directive so that the user can toggle the display of the actually notifications (similar to how stackoverflow.com does it in the top left).
While I imagine the expander would most likely be used with another directive it should be possible to use alone and that is where the isolateScope directive comes into play. Since the expander directive adds data to the scope and you may want to have multiple expanders on the same page, it needs to have an isolate scope in order to work. Now on a users profile page you have have data like developer key and address that you don't really need to display all the time so lets have the user control that. I have the isolate scope to be able to control both of those independently because without the isolate scope, both of them would be on the same scope and be controlled by the same instance on contentVisible.
I just don't see anyway with how directives now run in 1.2.0 RC3 to be able to accomplish this without that isolateScope directive (though I would be happy to be proven wrong).
I have updated your code so that it does what I think you want (at a minimum this works the way your old code does, but under rc3 as you wanted): http://plnkr.co/edit/nsq4BGAih3lfNmS2mLP7?p=preview
But I made quite a few changes and a significant architectural change so let me know if this moves away from the spirit of what you're trying to achieve.
I think the gist of the issue was that your two directives (generic and specific) were tightly coupled around contentVisible which created a complex dependency that resulted in you having to very carefully manage invocation timing. My approach was to decouple the two directives- encapsulating contentVisible within generic. This allows generic and specific to instantiate fully independently. So you're not dependent on any invocation timing. And thus the directive priority change no longer has any impact on this code. So, one big win with the solution I propose is it should be robust against further changes by the Angular team.
Specifically, I moved the template in to the same directive (generic) as the controller which manages contentVisible . This way the controller that changes contentVisible lives on the same scope as the template which uses it.
Now specific just calls over to the required: generic controller to toggle visibility (effectively as a setter function).
I also moved the ng-class assignment into the template in order to encapsulate that change within one place (the template) and so you don't need jquery or a link:/compile: on generic.
This is a regression. A fix is in the works: https://github.com/angular/angular.js/issues/4431
I have problem, looks very close to your. So if anything will change want to be notified.
My task: I have contact, that could be shown in defferent ways (very common task), but difference between views is in templates, whereas help functions and preparations are same, so I need generic directive for all views.
What I found:
1. in rc2 it works fine in rc3 unstable
2. in rc3 it could work same only when template is inline, but not when it is templateUrl (even if cached)
So I created two planks rc2 version and rc3 version.
Hope this will help.

Ember.js controller destroyed function

In Ember.js on an ObjectController there is a function init() which fires when the controller is instantiated. Is there a way I can fire some additional code, when the controller leaves context?
Here is what I am doing in the init():
init: function() {
jQuery("body").css("background-color", "#f2f1ec");
}
On the controller exit, or lose context I want to simply change the background color again:
jQuery("body").css("background-color", "#333333");
Thanks.
I would create a custom view and write make the css changes on didInsertElement instead of handling it in the controller. In this particular case, since you're operating on the body element, you might even be able to get away with doing it in the activate and deactivate functions of the route you're looking to target. (It might be a problem if you're doing it on ApplicationRoute though. Not sure if that is entered before are after document.ready()
Here's a fiddle using the Router's activate and deactivate functions.
And here's an example of the custom view.

Categories