I have an AngularJS directive meant to interact with notifications pushed from a server. The problem I am having is that currently the method I am using is to have an ng-repeat which keeps track of all the notifications during the session, and then displaying them as they are added to the array. What I want to happen, is that an alert comes, it's added to the DOM, it stays for a couple seconds, and removes itself from the DOM. At this point the issue is that the element will hide, but it will not remove itself from the DOM. Being that the element is absolutely positioned, after the fadeout is prevents me from accessing item that it is in front of. I assumed that element.remove() and the element.$destroy() would do the trick, but it seems like the element is not being from the ng-repeat or possibly that the $scope of the directive is not being deleted. Any help would be greatly appreciated.
angular.module('bmuApp').directive('messageNoti', function($timeout){
return {
scope: {
alert: '=messageNoti'
},
replace: true,
restrict: 'EA',
templateUrl: 'partials/authenticated/homepage/alerts.html',
link: function(scope, element, attrs) {
$timeout(function(){
element.addClass('fadeOut');
$timeout(function(){
element.remove();
}, 500);
}, 5000);
element.on('$destroy', function () {
scope.$destroy();
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!------------>
<!-- Alerts -->
<!------------>
<div class="alert-container" ng-cloak>
<a class="alert animated fadeInUp" ng-repeat="alert in alerts" ng-href="messages/{{ alert.user.thread_id }}" message-noti="alert">
</a>
</div>
<!-----Template for Alert------>
<div class="row">
<span class="close">
<i class="glyphicon glyphicon-remove"></i>
</span>
<div class="col-xs-12 text-left">
<span class="profile-image" style="background-image:url('https://www.blackmarketu.com/{{ ::alert.user['profile-picture'] }}');"></span>
<h5> {{ ::alert.user["user-firstname"] }} {{ ::alert.user["user-lastname"] }} </h5>
<p>{{ ::alert.message }}</p>
</div>
</div>
You don't need to interact with the DOM directly to remove it. Just splice (remove) that array member from the array.
So your logic is a bit wrong, because you have an array of alerts and you are not removing them, you just want to remove the DOM element.
If you don't want to change your code too much and add a parent directive to handle this, you can pass both "alert" (current array member) and "alerts" (entire array of alerts) to your directive and then in your timeout callback instead of removing the DOM element, you splice that array member from the array.
Remove replace: true. Since the ng-repeat directive creates inherited scopes and your directive creates isolate scope, they won't play well together on the same element.
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636
Related
So I read Why is ng-non-bindable required for <ui-gmap-windows> element in Angular Google Maps?
and I get how ng-non-bindable is used in the directive.
My problem is that I'm trying to use ng-if to conditionally reveal some icons in my marker infowindows. ng-if doesn't work with ng-non-bindable, and of course, the info windows don't work without it.
If someone can tell me how to make ng-if work in this situation or give an alternative solution, i'd greatly appreciate it.
<ui-gmap-windows show="show">
<div ng-non-bindable>{{obj.name}}<br>{{distance}} miles
<span class="ion-man" ng-if="obj.men"></span>
<span class="ion-woman" ng-if="obj.women"></span>
<span class="ion-ios-people" ng-if="obj.people"></span>
</div>
</div>
</ui-gmap-windows>
You need to use the templateUrl and templateParameter attributes on the ui-gmap-windows directive. The templateUrl is a property on the object that you're passing in which is a string that is the path to the .html template you want to use. The templateParameter is a property on the object that you are passing which is an object containing the parameters you want passed in. See code below. Also you can reference the docs here: http://angular-ui.github.io/angular-google-maps/#!/api/windows
<ui-gmap-windows idKey="'name'" show="show" templateUrl="'pathToHtmltemplate.html'" templateParameter="'{name: 'name', distance: 1.3, womens_restroom: true}'">
</ui-gmap-windows>
Then in your html template you can use ng-if no problem.
Note you have to reference the object you pass into templateParameter as parameter in the html template so the
pathToHtmlTemplate.html file would look like this
<div>
{{parameter.name}}<br>{{parameter.distance}} miles
<span ng-if="parameter.womens_restroom" class="ion-woman"></span>
</div>
The reason it does not work is because, ng-if (600) has lower priority than ng-non-bindable (1000) and ng-non-bindable is terminal:true so ng-if never compiles due to the terminal nature of ng-non-bindable. You could try using ng-switch (1200) directly on the element with ng-non-bindable though since it has higher priority but not sure if that is applicable in your case if you may have multiple conditions that can be true.
You can as well create your own directive say my-non-bindable anddefine it with configuration, {priority:599, terminal:true} and use it with ng-if say:
.directive('gmapTemplate', function() {
return {
priority: 599,
terminal: true
};
});
and use it with ng-if.
<div gmap-template ng-if="someCond">{{obj.name}}<br>{{distance}} miles
Otherwise you will have to wrap non bindables within element with ng-if
<div ng-if="someCond">
<span ng-non-bindable>{{obj.name}}<br>{{distance}} miles</span>
</div>
angular.module('app', []).directive('gmapNonBindable', function() {
return {
priority: 599,
terminal: true
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app" ng-init="test:123; show:false">
Show-->
<input type="checkbox" ng-model="show" />
<div gmap-Non-Bindable ng-if="show">{{test}}</div>
</div>
I'm using an angular directive and I am not having any luck with the jQlite .find() method:
DIRECTIVE
function cardsList () {
return {
restrict: 'A',
controller: 'CardsController',
templateUrl: 'app/directives/cards-list/cards-list.html',
link: function ($scope, $element, attr, CardsController) {
var cardLink = $element.find('a');
console.log(cardLink);
});
}
}
}
contextCards.directive('cardsList', cardsList);
An empty [] gets logged on the console.
TEMPLATE
<li data-ng-repeat="card in cards" class="cards--item">
<a class="cards--link" data-ng-href="#/{{ card.slug }}">{{ card.title }}</a>
</li>
VIEW
<ul class="col-xs-12 cards--list" cards-list></ul>
All I want to do is traverse to the <a> elements. According to the docs, .find() only works on tag names which is exactly what I'm trying to do.
EDIT: I want to add a class to the <a></a> if the card the link represents is selected (like .current-card)
From your answer it's not clear how the selected card is specified in the model, so I am assuming that the card object (the object of each iteration of ng-repeat) holds this flag, for example: card.isSelected.
Then, you could use ng-class to specify which CSS class to add based on this value:
<li ng-repeat="card in cards" class="cards--item">
<a class="cards--link"
ng-class="{'current-card': card.isSelected}"
ng-href="#/{{ card.slug }}">{{ card.title }}</a>
</li>
Addendum:
The answer to your original question about why .find("a") returns empty, it is because ngRepeat directive transcludes its content (which means that Angular takes the elements out of DOM during compilation), and places it at a later stage than your link function.
I've got a template with two kind of article-container: Viewer and Editor:
<article ng-if="!editor" ng-controller="article">
<div>Some content</div>
</article>
<article ng-if="editor" ng-controller="article">
<div mySharedScope></div>
</article>
While clicking the button the user can switch between those two container:
<button ng-click="editor = !editor" ng-bind="editor ? 'Abort' : 'Edit'"></button>
Now I want to create a directive on the second container. So this is what I did:
app.directive('mySharedScope', function () {
return {
template: 'New content'
};
});
But something is missing, as this doesn't work.
I want to use a directive to do some DOM mainpulation link: function ($scope, element, attrs) { }
Two things, first is that the directive mySharedScope will transform in it's directive definition from camel case
<div mySharedScope></div>
to a dashes like so
<div my-shared-scope></div>
After you switch that out, you'll need to make sure you're translcuding content nested inside of your first directive (article), and placing the ng-transclude directive inside of its template.
see docs for this on angular website
as a basic implementation of this, i've created a fiddle with the two of your two directives that appropriately switch when a button is triggered. The content is transcluded here, so feel free to cherry pick what you need from it.
https://jsfiddle.net/wvty8rpc/2/
A directive named 'mySharedScope' translates to attribute 'my-shared-scope':
<article ng-if="editor" ng-controller="article">
<div my-shared-scope></div>
</article>
New to Angular and need some assistance.
I have a block of HTML content that will be coming from a database that will contain a group of widgets. These are simple widgets that will essentially render out various elements, but for the purposes of this question we'll assume they're all basic HTML inside.
Those widgets are included in an unpredictable way, so my first thought was to use directives to render the HTML. So, we'd have something like:
<div widget data="This is the content."></div>
So I've got a directive that will place the value of data into the div. Easy enough!
Now, how would I go about nesting those widgets? So, how would I get something like:
<div widget data="Welcome! ">
<div widget data="This is some inside content."></div>
</div>
to render out:
Welcome! This is some inside content.
... because the issue I'm noticing is that if I place anything inside the directive HTML, it essentially gets ignored since it gets replaced with its own result (thus only echoing out Welcome!).
I realize I may be going the wrong direction on this in the first place, so any insight would be greatly appreciated. Thanks so much!
This is where you need to use the transclusion feature of the directive combined with ng-transclude directive.
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
A very basic version of transclusion of content based on your example might look something like this:
.directive('widget', function() {
return {
transclude: true,//Set transclusion
template: '{{text}} <section ng-transclude></section>', <!-- set where you need to present the transcluded content -->
scope: {
text: "#"
}
}
});
Demo
angular.module('app', []).directive('widget', function() {
return {
transclude: true,
template: '{{text}} <section ng-transclude></section>',
scope: {
text: "#"
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<span widget data-text="Welcome! ">
<div widget data-text="This is some inside content.">
<span widget data-text="This is some inside inside content."></span>
</div>
</span>
</div>
I've created a single answer survey, and I want to translate it into a directive. I'ts a single answer because only one answer can be chosen at the same time, and it triggers x or y function (also ng-class) depending on the chosen answer.
I have the working functionality without directive in this jsfiddle. As you'll see, every link triggers an different ng-click.
<div class="col-md-4">
<a class="btn col-md-12" ng-click="continent='1'" ng-class="{'active' : continent == '1'}">North America</a>
</div>
<div class="col-md-4">
<a class="btn col-md-12" ng-click="continent='2'" ng-class="{'active' : continent == '2'}">South America</a>
</div>
My problem is that this method doesn't work (or better, I don't know how to do it) when translated into a directive. You can see the jsfiddle with the directive here.
And the js:
.directive('myContinent', function() {
return{
restrict: 'E',
template: '<div class="col-md-4"> \
<a class="btn col-md-12" ng-click="continent=\'1\'" ng-class="{active : continent == \'1\'}">{{text}} \
</a> \
</div>',
replace: true,
scope: {
text: '#'
}
};
});
I think one of the easiest and clean ways is to pass the controller's scope variable to your directive scope (using =). You can save the selected continent in this variable and use it in multiple directive instances.
Fiddle: http://jsfiddle.net/pascalockert/xRy7H/1/
The problem is that all your my-continent directives has different isolated scopes, so each of them has scope.continent == 1 after you click it.
You can modify your directive in any of the available solutions:
Build in on top of radio input group and standard angular form.
Use $parent.continent and assign different values to it (bad practice actually).
Use a single directive with transclusion (where options are transcluded into the 'parent' directive).
I'll leave the choice for you, as it depends on the overall architecture of your decision.