Use ngAnimate on template in directive - angularjs

I have this function,
app.directive('movieDetails', MovieDetailsDirectiveFn)
.controller('MovieRowCtrl', ['$scope', '$rootScope', MovieRowCtrlFn]);
function MovieDetailsDirectiveFn() {
return {
restrict: 'E',
scope: {
movie: '='
},
templateUrl: '/tpl.html',
// template: '<div class="movie" style="background-image:url(https://image.tmdb.org/t/p/w1280{{movie.backdrop}})">{{movie.title}}</div>'
}
}
function MovieRowCtrlFn($scope, $rootScope) {
$scope.selectedMovie;
$scope.rowActive = false;
$scope.$on('movieRowActivated', ($event, dataObject) => {
if ($scope.$id != dataObject.targetScopeId) {
$scope.rowActive = false;
}
});
$scope.updateSelectedMovie = function updateVisibleMovieIndexFn(movie) {
$scope.selectedMovie = movie;
$rootScope.$broadcast('movieRowActivated', {targetScopeId: $scope.$id});
$scope.rowActive = true;
console.log ('hello')
}
}
And this html,
<div class="content" ng-repeat="movie in movieGroup" ng-init='$movieIndex = $index'>
<a href='' ng-click='updateSelectedMovie(movie)'>{{ movie.title }}</a>
</div>
<div ng-if='rowActive'>
<movie-details movie='selectedMovie'></movie-details>
</div>
When a user clicks on a button the updateSelectedMovie function triggers and the directive element movie-details is updated with new information.
Check the plunker here http://plnkr.co/edit/Ea1OtRUc1wvC4UNw1sNi?p=preview
What I want to do is animate a fadeOut/fadeIn transition when the movie-details directive element changes content. I've used ngAnimate on ui-router elements, since they get the ng-enter ng-animate classes etc. but that doesn't work with a directive.

For anyone having the same problemn. I "fixed" this by putting a ui-view inside the template that's being loaded by the directive. And fixed a state with it. So now the directive creates a template and goes to another state. The state then places the template inside the freshly created ui-view (from the directive) and now I can animate that element.

Related

How to update Directive on State Changes

I have a root state that defines the overall structure of the Angular template. In the root state, I have the sidebar included that has dynamic menus via directive that changes based on the state. Like this:
.state(‘root', {
abstract: true,
url: ‘/root',
templateUrl: ‘views/root.html',
})
root.html includes the sidebar.html that has dynamic menu called through Directive like this:
sidebar.html
<ul class="nav" id="side-menu">
<li class="nav-header">
<img alt="avatar" ng-src="{{ avatar }}" />
</li>
<!—Dynamic Menus Directive -->
<li sidebar-menus></li>
</ul>
The directive shows the menu based on $state.includes(). But what happens is, the directive shows fine in the first load but it doesn’t update the directive during state changes. To resolve this, I tried the following methods but nothing worked:
Added the $state to scope in Main controller but it still doesn’t change the directive
once it is compiled first.
Tried adding $stateChangeSuccess watcher to trigger recompiling the directive, but it doesn’t
recompile again after the first time (or) maybe it is recompiling but the changes are not seen in the template (this is the code I have now
which I will give below).
Moving the sidebar inside separate child
states instead of having it in root state works, but it beats the
purpose since I am trying to load the overall structure in the root
state first and only refresh the menu sections in subsequent state
changes.
I am not really sure how to approach this. I have a feeling my approach can be out of whack and hoping someone can guide me here. This is my directive code at the moment:
.directive('sidebarMenus', ['$compile', '$state', '$rootScope',
function($compile, $state, $rootScope) {
return {
restrict: 'A',
replace: true,
link: function(scope, element, attrs) {
var state = scope.$state; // Scope from Main Controller
// HTML Template
function contructHtml(state) {
var htmlText = '';
// First Child State
if (state.includes('root.child1')) {
var htmlText = '<li>Child 1 Menu</li>';
}
// Second Child State
if (state.includes('root.child2')) {
var htmlText = '<li>Child 2 Menu</li>';
}
// Third Child State
if (state.includes('root.child3')) {
var htmlText = '<li>Child 3 Menu</li>';
}
$compile(htmlText)(scope, function( _element, _scope) {
element.replaceWith(_element);
});
}
$rootScope.$on('$stateChangeSuccess', function() {
var state = scope.$state; // scope.$state is added in main controller
contructHtml(state);
});
// Initial Load
contructHtml(state);
}
}
}])
You can get rid of the compile business by using template.
You template could look something like this:
<li ng-if="state.includes('root.child1')">Child 1 Menu</li>
<li ng-if="state.includes('root.child2')">Child 2 Menu</li>
<li ng-if="state.includes('root.child3')">Child 3 Menu</li>
So your directive code should look sth like this
return {
restrict: 'A',
replace: true,
template:'<div> <li ng-if="state.includes('root.child1')">Child 1 Menu</li>
<li ng-if="state.includes('root.child2')">Child 2 Menu</li>
<li ng-if="state.includes('root.child3')">Child 3 Menu</li>
</div>'
link: function(scope, element, attrs) {
$scope.state = scope.$state; // Scope from Main Controller
$rootScope.$on('$stateChangeSuccess', function() {
$scope.state = scope.$state; // scope.$state is added in main controller
});
}
}

Call a function in a angular-controller from outside of the controller?

I have a lightbox-dierective and controller that looks like this:
directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="ng-modal" ng-show="show"><div class="ng-modal-overlay" ng-click="hideModal()"></div><div class="ng-modal-dialog" ng-style="dialogStyle"><div class="ng-modal-dialog-content" ng-transclude><div class="ng-modal-close" ng-click="hideModal()">X</div></div></div></div>'
};
}).controller('Lightbox', function($scope) {
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
});
Here is the desierd html, what I need is to open the secon ligthbox from withing the first one:
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>One lightbox <span ng-mousedown='toggleModal()'>Open lightbox two</span></h2>
</modal-dialog>
</div>
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>another lightbox</h2>
</modal-dialog>
</div>
For most cases it works great! I use it in several occations throughout the site, with diffrent lightboxes and different content.
I have now come across a case, where I need to call one of the lightboxes from outside of the controller. Can this be achieved and in that case how do I reference the right lightbox?
I'd extend that setting to an object
var modalSet = {
shown: false,
toggle: function(){ modalSet.shown = !modalSet.shown }
}
Then put it on your main controller (the one with ngApp attribute) and have your entire scope modaleble.
Also, directives do have a controller option, but since only one modal is gonna show up at any given time, you might not want to re-create a controller for every new instance.
Upon re-reading your question: Where is it exactly -> "outside of the controller"?

angularjs : directive and ng-class initialization inside ng-repeat

I have an ng-class initialization issue when I move the code which was in my controller, but related to the DOM, to a directive.
To be short, here is some code:
view.html
<div ng-repeat="foo in foos" ng-click="rowClick($index)>
<div ng-class="changeOpacity($index)>
<span>{{foo.title}}</span>
<span>{{foo.name}}</span>
<span>{{foo.date}}</span>
...
// some div with hidden content. Show when clicking on the div
</div>
<div>
controller.js
$scope.rowClick = function (idx) {
// unfold the clicked div to show content
};
$scope.changeOpacity = function (idx) {
if (this is the div clicked) {
return {dark: true};
} else {
return {light: true};
}
So when I have a list of div. When I clicked on a div, all the other divs get darker excepts this one.
Everything is working fine when $scope.rowClick and $scope.changeOpacity are in my controller.
When I move rowClick and changeOpcaity to a directive:
myBar.js
myApp.directive('myBar', function () {
...
return {
restrict:'A',
link: function (scope, element) {
element.bind('click', function () {
...
same code from rowClick
...
scope.changeOpacity = function () {
...
same code from changeOpacity
}
scope.$apply();
}
}),
changeOpacity: function () {
...
return {light: true} // for all divs
}
}
});
Now my view.html is something like that:
<div ng-repeat="foo in foos" ng-click="rowClick($index) my-bar>
<div ng-class="changeOpacity($index)>
<span>{{foo.title}}</span>
<span>{{foo.name}}</span>
<span>{{foo.date}}</span>
...
// some div with hidden content. Show when clicking on the div
</div>
<div>
But now the divs are not initialized anymore with the ng-class. I have to click one time on each div to initialize it, with the link function which listen to the click.
How can I initialize the ng-class inside the directive ?
Thanx.
The problem is that you are putting the changeOpacity function in your scope only after a click. Do it in the link function instead like this:
link: function (scope, element) {
scope.changeOpacity = function () {
...
same code from changeOpacity
}
}
making it a field of the "directive object" has no effect.
However, in my opinion it would be more elegant to use a model property to indicate if the element is active or not and change this property in an ng-click attribute. You can then refer to this property in the ng-class directive.
Something like this:
<div ng-repeat="foo in foos" ng-click="foo.active = true>
<div ng-class="{light: active, dark: !active }>
<span>{{foo.title}}</span>
<span>{{foo.name}}</span>
<span>{{foo.date}}</span>
<div ng-show="foo.active">
// some div with hidden content. Show when clicking on the div
</div>
</div>
ng-repeat creates a child scope for each item within the array. That means scope within a directive within ng-repeat will be the child scope. Thus you can set properties directly on that child scope and use ng-class, ng-show etc
Following is very basic example of directive to toggle state based on ng-click handler
HTML
<div ng-repeat="foo in foos" ng-click="rowClick($index)" my-directive parent-array="foos">
<div ng-class="{activeClass: foo.isActive}" class="item">
<span>{{foo.name}}</span>
<div ng-show="foo.isActive">Hidden content {{$index+1}}</div>
</div>
</div>
Directive
app.directive('myDirective',function(){
return function(scope,elem,attrs){
/* ng-repeat adds properties like `$first` , `$last` that can be helpful*/
/* set default first element active*/
if(scope.$first){
scope.foo.isActive=true;
}
var parentArray=scope[attrs.parentArray];
/* update active state across array*/
scope.rowClick=function(idx){
angular.forEach(parentArray,function(item, index){
item.isActive = idx==index
})
}
}
})
DEMO
Here's another approach that stores currSelected item in the parent scope ( controller)
app.directive('myDirective',function(){
return function(scope,elem,attrs){
/* set default first element active*/
if(scope.$first){
scope.foo.isActive=true;
scope.$parent.currSelected=scope.foo
}
/* reset previous activeitem and make item clicked the active one*/
scope.rowClick=function(item){
scope.$parent.currSelected.isActive=false
scope.$parent.currSelected=item;
item.isActive=true
}
}
})
DEMO

Changing src of only hovered ng-include element, where value of src is a $scope variable

I have multiple ng-include elements that have src attribute set to $scope.template_url.
I want to change src of hovered element only to new template but changing it's value will change all of elements. How can i implement it?
Html code:
<section class="parent">
<div data-ng-include data-src="template_url"></div>
</section>
Javascript (in controller):
angular.element(document).on('mouseover', '.parent', function(){
$scope.$apply(function () {
$scope.template_url = "path/to/new/template.html";
});
});
Writing jQuery dom manipulation is dirty and also don't works:
$(this).attr('data-src', "path/to/new/template.html");
I'd suggest making this a directive. Directives have their own scope, so you can still do the "on hover use a different template" idea, but for each individual one that is hovered.
<div>
<div data-some-directive=""></div>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('someDirective', function() {
return {
controller: function ($scope) {
$scope.model = "Hello"
$scope.mouseover = function () {
$scope.model = "Hovered!";
};
},
scope:{},
restrict: 'AE',
replace: true,
template: '<div><input ng-mouseover="mouseover()" ng-model="model"></div>',
};
});
Heres a fiddle to see it in action.
Tweak the template variable in the directive to use a variable on your model for the include url.
By the way, angular already has a mouseover handler, so i've just linked that into the controller with ng-mouseover in the template.

Ng-controller on same element as ng-repeat - no two-way-data-binding

I can't get two-way-data-binding to work in an Angular js ng-repeat.
I have an ng-controller specified on the same element that has the ng-repeat specified -
I just learnt that by doing this, I can get a hold of each item that is being iterated over by ng-repeat. Here is the HTML:
<div ng-controller="OtherController">
<div id="hoverMe" ng-controller="ItemController" ng-mouseenter="addCaption()"
ng-mouseleave="saveCaption()" ng-repeat="image in images">
<div class="imgMarker" style="{{image.css}}">
<div ng-show="image.captionExists" class="carousel-caption">
<p class="lead" contenteditable="true">{{image.caption}}</p>
</div>
</div>
</div>
</div>
And here is the ItemController:
function ItemController($scope){
$scope.addCaption = function(){
if($scope.image.captionExists === false){
$scope.image.captionExists = true;
}
}
$scope.saveCaption = function(){
console.log($scope.image.caption);
}
}
And the OtherController:
function OtherController($scope){
$scope.images = ..
}
When I hover the mouse over the #hoverMe-div - the caption-div is added correctly. But when I input some text in the paragraph and then move the mouse away from the #hoveMe-div, the $scope.image-variables caption value is not updated in the saveCaption-method. I understand I'm missing something. But what is it?
You don't need a ng-controller specified on the same element that has the ng-repeat to be able to get each item.
You can get the item like this:
<div ng-repeat="image in images" ng-mouseenter="addCaption(image)" ng-mouseleave="saveCaption(image)" class="image">
And in your controller code:
$scope.addCaption = function (image) {
if(!image.captionExists){
image.captionExists = true;
}
};
To get contenteditable to work you need to use ng-model and a directive that updates the model correctly.
Here is a simple example based on the documentation:
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, controller) {
element.on('blur', function() {
scope.$apply(function() {
controller.$setViewValue(element.html());
});
});
controller.$render = function(value) {
element.html(value);
};
}
};
});
Note that the directive probably needs more logic to be able to handle for example line breaks.
Here is a working Plunker: http://plnkr.co/edit/0L3NKS?p=preview
I assume you are editing the content in p contenteditable and are expecting that the model image.caption is update. To make it work you need to setup 2 way binding.
2 way binding is available for element that support ng-model or else data needs to be synced manually. Check the ngModelController documentation and the sample available there. It should serve your purpose.

Resources