How to Dynamically add/remove directive AngularJS - javascript

I'm using the Angular Ellipsis directive (here: https://github.com/dibari/angular-ellipsis) to put some ellipsis on text that overflows. Here is the code that does this for the text contained in the scope variable 'fullText'.
<div data-ng-bind="fullText" data-ellipsis></div>
I'd like also, to have the ability to show the full text, un-ellipsised (if that is a word...) when I click on a button, say. This directive doesn't give me a easy way to do that, as far as I can tell.
What is the best AngularJS way to do this? I am pretty new to AngularJS and haven't yet written any directives yet - is there a non directive way to do this elegantly?

You can use ng-if or ng-show/ng-hide :
<div data-ng-bind="fullText" data-ellipsis ng-if="condition"></div>
<div data-ng-bind="fullText" ng-if="!condition"></div>
<button ng-click="toggle()">Toggle</button>
// In controller :
$scope.toggle = function() {
$scope.condition = !$scope.condition;
}
But the best way is to have the directive handle it directly.

Related

After using $sce.trustAsHtml, ng-click not working

I am trying to print to the screen custom html using angular. I am using $sce.trustAsHtml in combination with ng-bind-html to accomplish this. The goal is not only to be able to print this custom html, but that it will retain directives such as ng-click and they will be usuable. Examples I have seen in articles such as follows are promising:
AngularJS render HTML within double curly brace notation
However in my implementation I find that although the html renders correctly including references to ng-click, the directive doesn't seem to work anymore when trying to click on the link I am using it on; here is some sample code:
$scope.htmlExpression = $sce.trustAsHtml("<a ng-click='test();'>Click Me</a>");
$scope.test = function() {
console.log('Hello World!');
}
<div>
<p ng-bind-html="htmlExpression"></p>
</div>
As everything renders fine and nothing appears lost in translation when analyzing the source; I am left feeling as if I have left something out. Any help is appreciated.
Use https://docs.angularjs.org/api/ngSanitize and bind the html. If this does not work, $digest to reboot the digest cycle.

How does ng-cloak work?

I've been looking at the ng-cloak source code
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngCloak.js
It looks like it strips away the ng-cloak attribute during the compile phase of the directive. But when when I try
console.log(element.html())
during the compile function of a directive, the expressions have still not been evaluated, so I get an output like
<my-directive ng-cloak> {{foo}} </my-directive>
Given that ng-cloak will remove the ng-cloak attribute and the corresponding display:none, wouldn't it show {{foo}}? I'm confused here. Whend do Angular expressions get evaluated? It doesn't look like it gets evaluated in the compile function. When is the DOM updated?
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
The directive can be applied to the element, but the preferred usage is to apply multiple ngCloak directives to small portions of the page to permit progressive rendering of the browser view.
ngCloak works in cooperation with the following css rule embedded within angular.js and angular.min.js. For CSP mode please add angular-csp.css to your html file (see ngCsp).
https://docs.angularjs.org/api/ng/directive/ngCloak
Example index.html
<div id="template1" ng-cloak>{{ 'hello' }}</div>
<div id="template2" class="ng-cloak">{{ 'world' }}</div>
things.js
it('should remove the template directive and css class', function() {
expect($('#template1').getAttribute('ng-cloak')).
toBeNull();
expect($('#template2').getAttribute('ng-cloak')).
toBeNull();});
Or you can use in other way
it might not be enough to add the display: none; rule to your CSS. In cases where you are loading angular.js in the body or templates aren't compiled soon enough, use the ng-cloak directive and include the following in your CSS:
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;}
Angularjs - ng-cloak/ng-show elements blink
Angular will interpolate the bindings at the end of the $digest cycle so that all of the other modifications have already completed. If they were processed earlier, a directive might later change the binding or scope and cause the DOM to be out-of-date.
You can decorate the $interpolate service so that you can log when Angular interpolates a binding:
.config(function($provide){
$provide.decorator("$interpolate", function($delegate){
function wrap() {
var x = $delegate.apply(this, arguments);
if (x) {
var binding = arguments[0];
return function() {
var result = x.apply(this, arguments);
console.log('binding:', binding, result);
return result;
}
}
}
angular.extend(wrap, $delegate);
return wrap;
});
})
You can then see that all of the directives have been processed and finally, the $interpolate service is called and the DOM is modified accordingly.
Click here for live demo.
ng-cloak also fixes the two times click bug for ng-click reverse. Once listed this instruction will fix the issue in witch the button with ng-click reverse instruction needs two clicks in order to execute.

How can I collapse and expand multiple sections with AngularJS and ng-click

We've been struggling with this problem for a very long time now and we can't find the answer. We 're using Ionic with AngularJS for dynamic views.
We want to click on a item field, in this case a header and make something toggle between visible and invisible.
We have the following code:
<ion-item class="item-divider positive" ng-init="toShow=true" ng-click="toShow != toShow">
Display Header
</ion-item>
<div ng-show="toShow">
<ion-item class="row">
<!-- items to show -->
</ion-item>
</div>
</ion-list>
We can't get this working. We've tried every example on the internet mostly with examples. We also tried to make functions to the ng-click, but we need it to be dynamic so it can also be used on different fields.
Please help us
You don't especially need to set a function in your controller, the only needed thing is to put a $scope.toShow=false
Plus, you shouldn't use ng-init if you are not in a ng-repeat loop
according to Angular's doc:
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So, in controller:
$scope.toShow=false
and in your template :
<ion-item class="item-divider positive" ng-init="toShow=true" ng-click="toShow = !toShow">
Display Header
</ion-item>
<div ng-show="toShow">
<ion-item class="row">
<!-- items to show -->
</ion-item>
</div>
</ion-list>
Your first problem is that you have a boolean statement in your ng-click. I'm assuming you wanted ng-click="toShow = !toShow">
The second problem is that the <ion-item> directive uses an isolate scope, so the toShow variable you are targeting in your ngClick attribute is probably using that isolate scope instead of the parent controller as you might expect. The best way to fix this is to create a setting function to handle the value of toShow, like:
$scope.setToShow = function(){
$scope.toShow = !$scope.toShow;
}
now your ngClick becomes
ng-click="setToShow()"
Angular should look for the function on the controller you'd expect instead of creating a new property on the child scope.
You could also access the $parent directly by using
ng-click=$parent.toShow = !$parent.toShow">
I wouldn't recommend that, as $parent can end up referencing something else if another scope gets added in a refactor or something.
EDIT: using an array is an easy way to keep track of a list of conditional parameters like you'll need for your example.
$scope.setToShow = function(index){
$scope.toShowArr[index] = $scope.toShow[index] === undefined ? false : !$scope.toShow[index];
}
I am handling the undefined condition with false, but you might feel the need to do otherwise.

Why does my $watch only ever fire once?

I'm factoring out some widget and the $watch expression works perfectly having all in one file but now I moved the relevant controller part into a new controller and the markup into a new html and the $watch fires exactly once after initialization but not when editing typing in the associated input.
JS:
app.controller('getRecipientWidgetController', [ '$scope', function($scope) {
console.log("controller initializing")
var testReceivingAddress = function(input) {
console.log("change detected")
}
$scope.$watch("addressInput", testReceivingAddress)
} ])
HTML of wrapper:
<ng-include
src="'partials/getRecipientWidget.html'"
ng-controller="getRecipientWidgetController"
ng-init="recipient=cert"> <!-- ng-init doesn't influence the bug. -->
</ng-include>
HTML of partials/getRecipientWidget.html:
<md-text-float ng-model="addressInput"></md-text-float>
I suspect there is some scope voodoo going on? I left the ng-init in to make clear what I want to achieve: build an obviously more complex, reusable widget that in this instance would work on $scope.cert as its recipient.
That is probably because ng-include will create a new inherited scope on the included HTML, hence $scope.addressInput in your controller is not the same reference as $scope.addressInput in getRecipientWidget.html
Well it's not easy to explain, but you should either put ng-controller within the HTML of getRecipientWidget.html (and not on the div above that includes it), OR you can use an object such as something.addressInput instead of the raw addressInput which avoids references issues on raw types (number/string).
ng-include creates new scope.
Try this
<md-text-float ng-model="$parent.addressInput"></md-text-float>
Plunker example

Using Angular, how can I show a DOM element only if its ID matches a scope variable?

I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});

Categories

Resources