How to manipulate DOM after ng-show in Angularjs? - javascript

I'm new to angularjs. I'm using angularjs version 1.2.13.
I've come upon a scenario where after ng-show displays my DIV, I need to manipulate the DOM in order to realign some DIV columns. I cannot realign my DIVs if they are hidden.
HTML looks like this:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column">1</div>
<div class="column">2</div>
<div class="column">3</div>
</div>
I think the way to go would be to create a "realign" directive such as:
<div class="column" realign>1</div>
<div class="column" realign>2</div>
<div class="column" realign>3</div>
But I'm not sure how I would make the directive trigger only when the parent DIV is shown (through the ng-show directive).
Any ideas ?
Thanks appreciated!
Here's a little more code:
module.controller("MyController", function($scope, FormState){
$scope.$watch(function(){ return FormState.showGrid; }, function(newVal, oldVal){
//At this point in time, right here, the DOM has not been updated...
$scope.IsThisShown = newVal;
//At this point in time, right here the DOM has still not been updated...
//the DOM gets updated when the function exists
});
});

You have a few different options.
One option you can explore is using an isolate scope with your realign directive.
See this example here:
http://plnkr.co/edit/lO2U4GZcEm4K1qGLpsFV
You don't have to use the isolate scope as Angular scope is prototypical by nature (though with a slight gotcha with regards to primitives), but I figured I'd throw this into the example so you can see isolate scope in action. In this example I'm using an isolate scope with an execute expression.
Just a random example that builds on your description by randomly repositioning some div's after the parent is shown.
Hopefully that helps.

There are a lot of ways, but the way that immediately comes to mind is:
<div ng-controller="MyController" ng-show="IsThisShown">
<div class="column" realign="IsThisShown">1</div>
<div class="column" realign="IsThisShown">2</div>
<div class="column" realign="IsThisShown">3</div>
</div>
And have your new directive observe its argument and do the realignment when it goes true.

The other answers are good (and standard), but tie the realign directive to being shown/hidden by that variable. If, for example, there are two nested ng-show's, this would break down. In my opinion, a better solution is to create an on-show directive, with the following scope:
scope: { 'onShow' : '&' }
And within the link function, set up a $scope.$watch as follows:
$scope.$watch(
function() { return $element.hasClass('ng-hide'); },
function(newVal, oldVal) { /* execute onShow here if newVal === false */ }
);
You could also (and I don't recommend this) change ng-show to ng-if. ng-if removes and adds the elements from the DOM, instead of just hiding them. In that case, the link function fires every time the element reappears.

Related

How to properly execute javascript against a DOM element upon ng-show?

There's a TLDR at the bottom. Otherwise here's the long-winded explanation.
I have a sort of a form, a series of various inputs that is divided into "pages" by using ng-show.
What I want is when ng-show activates and shows a new "page" and hides the old, then to execute javascript to add a class, focus, then find the input and focus on that. Essentially highlighting the next thing the user needs to do on this new page and focusing for quick input.
I've been trying to get a $watch to work but I feel like this might be over-complicating something that might have an easier alternative nor can I get it working properly.
The pages each have several divs that are questions, directions, or input elements. But when a page becomes visible, there would be one div in particular that I would highlight (see myFocusDirective placement), because some of the divs aren't actionable by the user. Example of pages:
<div id="page1" ng-show="isPage(1)">
<div>
text
</div>
<div myFocusDirective>
<input>
</div>
</div>
<div id="page2" ng-show="isPage(2)">
<div myFocusDirective>
<button>
</div>
</div>
I've been trying variations of a $watch on the attributes or using $timeout but I can't seem to accurately only match when ng-show is activated. My understanding is that it should just be applying "ng-hide" to and from the class of the div but I can't seem to match against that...
scope.$watch(function() { return element.attr('class'); }, function(newValue) {
if (newValue.match(/ng-hide/g) === null && newValue.match(/highlight/g) === null && newValue.match(/complete/g) === null) {
highlightAndFocus(element[0]);
}
},true);
also tried using $timeout on the attrs but that's unreliable due to multiple matches because of classes being applied across divs.
scope.$watch(attr.initial, function(newValue) {
$timeout(function() {
highlightAndFocus(element[0]);
});
},true);
Any help would be appreciated, I must be missing something here.
TLDR; After ng-show I want to modify the classes on a div and then focus on an input within the div
Why not make your life easier for yourself and pass the contents of your ng-show to your div as well:
<div id="page2" ng-show="isPage(2)">
<div myFocusDirective focuson="isPage(2)">
<button>
</div>
</div>
And watch the focuson in your directive scope? This way, you don’t have to worry about parent classes, etc.
But in any case, if you are watching an attr, you should be using attr.$observe

Hiding containing element for angular directive from within directive

Given the following situation with one parent directive and two child directives:
<div pane-list>
<div calc-pane></div>
<div user-pane></div>
</div>
I can use replace: true on the child directives, and then in the top level of their template <div ng-show="showPane">. This allows me to control whether or not the pane is showing from within the directives I use inside this list of panes - without replying on the parent pane-list directive having anything to do with it or needing to share scope. With replace: true I can use these panes inside any kind of parent element with no worries.
Apparently replace is being removed, so what can I do instead? I don't see any ways that aren't considerably more complicated. If I can't replace the pane element, I don't see a simple way to be able to show/hide it from within the directive.
Anybody have any ideas?

Angular.js $scope and dom element

How does angular keep track of which $scope is related to which element? I have a few theories:
angular element keeps scope object as property
angular saves each scope in cache with the relationship with element
angular searches each ng-scope, and magically find element
For example, I have an element with $scope, and I want to change dom hierarchy of this element (moving to outside of the outer controller). How is scope affected by this action?
If you have an angular app, and your HTML looks something like this:
<div ng-controller="OuterController">
...
<div ng-controller="InnerController">
<div id="wrapper">
<span>An Element</span>
</div>
</div>
</div>
And you move the <span> out of the #wrapper div, it's still within the context of both OuterController and InnerController. However, if you move it up to where the ... is, only the OuterController's context applies.
Each controller has its own context, and that context extends down into all child-elements recursively, even if a new controller is present, at which point, both controllers are in-scope.
This is true whether the element is moved dynamically with Javascript or otherwise. Angular keeps track of most context internally.

AngularJS use directive-name in scope as string

I have nested angular.js templates. Each template has it's own directive. Based of the data passed to the ParentTemplate, angular.js should call the matching directive.
<div class="parent-template">
<div class="{{childTemplate}}"></div>
<div class="template1"></div>
</div>
Lets say that $scope.childTemplate = template1. Both divs evaluate to the same content <div class="template1"></div> but only the second div calls the directive to show the template as expected.
I need a dynamic way to call the corresponding template directive
Here is the directive for template1
angular.module('myApp').directive('template1', function(){
return {
restrict: 'C',
templateUrl: 'templates/template1.html'
}
});
Directives for other templates directives work similar.
You try to set the class of the DOM element.
You can do this with ngClass.
Otherwise if you would like to set your directive as an attribute of the DOM element. Like this: <div template1> </div>
You can try this way:
var myEl = angular.element( document.querySelector( '#divID' ) );
myEl.attr('myattr',"attr val");
Try:
<div ng-class="childTemplate"><div>
Did you ever solve this? I have a similar challenge, which I currently solve by using ng-include, like so:
<div class="parent-template">
<div ng-include="'templates/template1'"></div>
<div class="template1"></div>
</div>
This works for your example, but gets more tricky when a directive does more than just wrapping a template, e.g. working with the DOM or adding a controller.
What you can do in that case is using the directive inside the template.
However, this feels all kinda hacky, i'd like a more direct approach
This was, for me, all sparked by an article on react plugins, in which you basically enumerate over a set of react component to get a feature set: https://nylas.com/blog/react-plugins

Call a bound function within a Directive from an ng-click

I was wondering I am able to call a bound function within a directive when part of a div is clicked. I currently have a div with another div inside that serves as a button to expand the div. The large div has a directive that makes it expandable. I would like to click on the arrow in the corner of the div and have the arrow's ng-click call a bound function inside the expandable directive. Is this possible? If so, how can I implement it?
Here's a picture of the div:
<div expandable class="boxWithBorderStyle">
<div class="arrowStyle" ng-click="someFuncInsideExpandableDirective()"> </div>
</div>
The contents - i.e. the child elements - of the element hosting the directive have a different scope than the directive if the directive uses isolate scope (scope: {}), which I suspect is what you have.
In order to link the contents against the same scope, you'd need to manually transclude them:
.directive("expandable", function(){
return {
transclude: true,
scope: {
// whatever you have now
},
link: function(scope, element, attrs, ctrls, transcludeFn){
transcludeFn(scope, function(contentClone){
element.append(contentClone);
});
// here you can expose whatever functions you want on the isolate scope:
scope.someFuncInsideExpandableDirective = function(){
// do something
}
}
};
});
The first parameter of the transclusion function transcludeFn is the scope against which the transcluded content is linked, which, in this case, is the directive's scope.
Keep in mind, though, that it is a bit awkward from the point of view of the user of your directive, since now they have HTML that refers to some "magic" variables/function (which is of-course defined by your directive) that is not apparent to someone observing the HTML. Imagine you'd encounter some directive foo which did something similar:
<div foo="abc">
{{foobar}}
<button ng-click="doSomethingFoo()">do foo</button>
</div>
and foobar and doSomethingFoo were defined inside the scope of the foo directive - it would be more difficult to decipher without knowing the specifics of foo what was going on.

Categories