Angular.js: ngSwitch to show/hide rather than add/remove DOM elements - javascript

As I've recently found out, ngSwitch acts a bit like ngIf, in that it outright removes elements from the DOM rather than just hide them. Now in my application this strikes me as both needlessly expensive and potentially problematic down the line (if I need to access properties of my hidden DOM elements). At the same time I like the cleanliness of the ngSwitch syntax as opposed to a bunch of different ngShow directives (which would not allow me to include a 'default' behaviour either). Is there any way I can modify the way this directive works to have it merely hide elements, not remove them altogether? Thanks.

OK, I understood better your need and I have the same need before.
I advice you to use ngSwitch with custom directives like this :
<div ng-switch="mode">
<directive-a ng-switch-when="a"></directive-a>
<directive-b ng-switch-when="b">...</directive-b>
<directive-c ng-switch-default>...</directive-c>
</div>
and in the definitions of your directives directiveA, directiveB and directiveC you use the templateUrl wich will use the cache from the second call of the directive.
The second approach is to use ng-show on every directive but I prefer the ng-switch because it let the dom lighter.

I know this is a bit old but I faced the same issue today and I managed to cache segments views by not using ngSwitch but directly the [hidden] attributes with the segment condition this way :
<ion-segment [(ngModel)]="segmentName">
<ion-segment-button value="profile">
Profile
</ion-segment-button>
<ion-segment-button value="friends">
Friends
</ion-segment-button>
</ion-segment>
<page-profile [hidden]="segmentName != 'profile'" ></page-profile>
<page-friends [hidden]="segmentName != 'friends'" ></page-friends>
Just replace ngSwitchCase by the [hidden] conditional

No, there isn't any way to keep nodes hidden as ngShow and ngHide do if you use ngSwitch. Only the ngSwitchWhen node will be created if its condition is true, all others are commented.
The advantage of ngSwitch is that the dom will be lighter.

Related

ngSwitch is "Attribute Directive" OR "Structural Directive" ?

I am a bit confused regarding ngSwitch directive -- whether it is 'attribute directive' or 'structural directive'.
Attribute directives are written with 'square brackets' like [ngStyle], [ngClass], etc. (and we write it as [ngSwitch] which refers it as 'Attribute Directives').
Structural directives are written with 'aestrick' like *ngFor, *ngIf, etc. (and we write the cases as *ngSwitchCase="..." which means it is a structural directive).
<div [ngSwitch]="colorValue">
<p *ngSwitchCase="red">Red</p>
<p *ngSwitchCase="blue">Blue</p>
<p *ngSwitchCase="green">Green</p>
</div>
As per the code above, it is getting very confusing to categorize ngSwtich to either of the Directive Categories! Can someone help me out in understanding the directive-type of ngSwitch ?
[ngSwitch] is an attribute directive used in combination with *ngSwitchCase and *ngSwitchDefault that are both structural directives.
This is clearly explained in Angular's documentation...
NgSwitch — an attribute directive that changes the behavior of its companion directives.
NgSwitchCase — structural directive that adds its element to the DOM when its bound value equals the switch value and removes its bound value when it doesn't equal the switch value.
NgSwitchDefault — structural directive that adds its element to the DOM when there is no selected NgSwitchCase.
https://angular.io/guide/built-in-directives#switching-cases-with-ngswitch
As I understand it , 'structural directive' change the dom's struct. attribute directive change the dom's attribute,such as color,background and so on
ngSwitch change it's children's length , so its a structural directive,
It is a structural directive
Structural directives updates the DOM layout by adding or removing elements.
Structural Directive:
What are structural directives?
Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.
As with other directives, you apply a structural directive to a host element. The directive then does whatever it's supposed to do with that host element and its descendants.
Structural directives are easy to recognize. An asterisk (*) precedes the directive attribute name as in this example.
Refer: https://angular.io/guide/structural-directives
ngSwitch is a built-in structural directive.
[https://angular.io/guide/structural-directives]
#j3ff: The (*) star on *ngSwitchCase is merely sugar syntax,
it does not indicate the type of directive.
It could be written the long way like this...
<div class="course-category" [ngSwitch]="colorValue">
<ng-template [ngSwitchCase]="'RED'">
<div>
<p>Red</p>
</div>
</ng-template>

"destroy" ng-if once expression is true

I am wondering if is possible to unbind/destroy ng-if once its value is true?
I created tree structure with recurrence directive and each of branches has <div ng-if="visible"> which keeps tracking if element needs to be rendered. The problem is that the solution increase number of watchers because every ng-if creates new one. Once the ng-if expression became true it won't change so watch can be removed, is there any way to "destroy" ng-if in that case?
if using angular 1.3+ you can do
<div ng-if="::visible">
Which will remove the expression from angular watchlist and essentially no watches on it. Use this for any single binded expressions in your app. Keeps your watch count low and digest cycles faster.
You could use bindonce library, which happens to have a bo-if = "condition" directive attribute, which is described as:
equivalent to ng-if but doesn't use watchers
So is somewhat similar of what you want to achieve.

What is initialised first - controller or partial/html?

Hi please explain reason for following three scenarios as I am unable to know why is this happening -
1)
<div ng-controller="myctrl">
<p>something for DOM manipulation</p>
</div>
2)in route I write
('someroute',{
templateUrl : "mytemplate",
controller : "myctrl"
});
mytemplate:
<div>
<p>something for dom manipulation</p>
</div>
3)
<div ng-include="mytemplate" ng-controller="myctrl"></div>
with template being same as above
The controllers in all the above scenarios are same, and in all of them I am just trying to select p tag of DOM by writing angular.element('p'). But this seems inconsistent. It works very well in 2nd scenario, it never works in 3rd scenario and I am not sure about 1st sccenario. Can someone explain which method is best for dom selection/manipulation, as I have to add a class to this 'p' tag on hover.
I am not understanding which gets initialized first- controller or partial?
Manipulating DOM inside controllers is discouraged. Quote from Best Practice - Dom Manipulations:
Dom Manipulations should not exist in controllers, services or anywhere else but in directives.
If you only need to style elements on hover, using the p:hover CSS selector would be enough without touching the DOM. ng-class and ng-mouseover can help you if you really want the class.
For more complex scenarios, you may want to write your own directive. You can check the article above for a guide to do that.
Load order from the first case: HTML first. Directives like ngController are loaded after parsing the HTML. Therefore the HTML already exists when the controller is loaded.
Load order for the second case: I'm not sure about it. You may check documentation for ngRoute or uiRouter depending on the router you are using.
Execution order for the third case: Controller first. The directive ngController have higher priority than the ngInclude directive. Therefore, the controller is loaded first.
Quote from ngController documentation :
This directive executes at priority level 500.
Quote from ngInclude documentation :
This directive executes at priority level 400.

Getting an Element in AngularJS

It seems that getting an element in AngularJS is a bad idea, i.e. doing something like:
$('.myElement')
in say, a controller is not an angular way of doing things.
Now my question is, how should I get something in angular?
Right now, what I'm doing (and is an accepted way of doing it) is by watching a variable, and my directive does something based on it.
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.id == varToWatch)
{
//Run my Directive specific code
}
});
However, while this particular design works for most cases, watch is an expensive operation, and having lots of directives watching can really slow down your application.
TL:DR - What is an angular way of getting a directive based on a variable on the directive? (like the one above)?
If you want to get/set values you don't need to fetch the element using jQuery. Angular data binding is the way to do it.
directives is the way to go if you want to do animations or any kind of element attributes and DOM manipulation.
Your code is basically right; the directive should watch something in the $scope and perform it's logic when that thing changes. Yes, watch statements are expensive, and that is a problem once your number of watches start to approach ~2000.
Looking at your code though, I see one problem:
The variable $scope.varToWatch references an id in the template.
When this variable changes, you want something to happen to the element which has this id.
The problem here is in the first point: The controller should know nothing about the DOM, including the id of any element. You should find another way to handle this, for example:
<div my-directive="one"> ... </div>
<div my-directive="two"> ... </div>
<div my-directive="three"> ... </div>
...etc
And in your directive:
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.myDirective == varToWatch)
{
// Run my Directive specific code
}
});
You are very vague as to what you're trying to achieve, but I'll try to answer in context of your last comment.
I have a lot of the same directives (therefore the code will run on all of them), but I need to get only one directive from the lot.
You talk a lot about getting the right element. The directive element is passed to the link function in the directive. If you are not using this element (or children of it) directly, but rather trying to search for the element you want somehow, you are most likely approaching the problem the wrong way.
There are several ways to solve this, I'm sure. If you're thinking about animations, there is already support for that in Angular, so please don't try reinvent the wheel yourself. For other logic, here are two suggestions:
Secondary directive
If the logic you want to apply to this directive is generic, i.e. it could be applied to other directives in your application, you could create a new directive which works together with directives. You can set prioritization in directive in order to control which directive is executed first.
<main-directive ... helper-directive="{{condition_for_applying_logic}}"></main-directive>
jsFiddle example
Expanding main directive
If the logic is tightly coupled to this directive, you can just create a new attribute, either dynamic or static, and bind to it in the directive. Instead of checking 'attrs.id == varToWatch', you check if $scope.apply-logic === 'true' and apply the logic then.
<main-directive ...></main-directive> <!-- Not applied here -->
<main-directive apply-logic="true" ...></main-directive> <!-- Applied here -->
<main-directive apply-logic="{{some.varOnScope}}"...></main-directive> <!-- Conditional -->
Please comment if something is unclear.

AngularJS - dynamically remove directive from element

What is the proper way to dynamically add or remove directive from compiled and linked element?
I have a page that has bunch of inputs there (the list is pretty long, so i want to come up with a general solution). What i want to do is to disable all the inputs if specific flag set. I can do this by using jQuery's element.prop('disabled', true).
The problem of such approach is that if any of inputs have ng-disabled or ng-enabled directives attached, then on any their expression modification they will override previously set 'disabled' property. But I want them to not override my global flag.
I came up with the solution to add another bunch of watchers for ng-disabled or ng-enabled expression, but it seems to be not the best approach.
What I want to do, is to remove most of directives attached to the element and set appropriate attributes myself. But if I recompile and relink the element, and then replace it in the document, then I will get a memory leak, as the old element will be de-attached from the DOM document tree, and will remain in memory. I cannot destroy element's scope either, because those elements basically use whole page's main scope.
You can try something like
<div ng-show="someBoolean" >Some text or nested element</div>
or instead of "someBoolean" you can attach a function that resolves to a boolean. To set your boolean you could attach a ng-click to your input that updates your model/boolean value
<button type="button" ng-click="setBoolean()">Some text or nested element </button>
Because of angulars two way data binding the ng-show will be updated upon completion of the next digest cycle

Categories

Resources