I am trying to apply animations to ng-view (routing) depending of the views involved.
For example, from View1 to View2 I need the View1 leaving through the left side and View1 entering from the right side. Otherwise, from View2 to View1 I need View2 leaving through the right side and View1 entering from the left side.
But I have also situations where I need apply different animations to both views, for example, View1 leaving fading out and View2 entering scaling up.
What I am doing is using a scope associated variable as class in the ng-view:
<div ng-view class="{{transition}}"></div>
This variable is set in each route change with something like this in each controller:
$scope.transition=Global.transition;
$rootScope.$on("$routeChangeStart",function (event, current, previous) {
// Here I get the leaving view and the entering view and the kind of transition is selected
...
$scope.transition=selectedLeavingTransition; // Set the transition for the leaving view
Global.transition=selectedEnteringTransition; // Set the transition for the entering view
});
Global is a service to set the transition variable for the entering scope from the leaving scope.
This way, when a route change is detected, the current ng-view is set with the class associated to selectedLeavingTransition, and the entering ng-view is set with the class associated to selectedEnteringTransition.
For example, if the route change was from View1 to View2 the ng-views during the animation could be:
<div ng-view class="fadeOut ng-animate ng-leave ng-leave-active"></div>
<div ng-view class="scaleUp ng-animate ng-enter ng-enter-active"></div>
The CSS in this case could be:
fadeOut.ng-leave {animation:1s fadeOut;}
scaleUp.ng-enter {animation:1s scaleUp;}
Though it works, I am wondering if there is a simpler way to do it as it seems a little mess.
An alternative solution that doesn't require much code is to define your animations on your routes:
$routeProvider.when('/view1', {
templateUrl: 'view1.html',
controller: 'View1Controller',
animations: {
enter: 'enter-left',
leave: 'leave-left'
}
});
Then use a directive to retrieve the current route's animations and add them to the element:
app.directive('viewAnimations', function ($route) {
return {
restrict: 'A',
link: function (scope, element) {
var animations = $route.current.animations;
if (!animations) return;
if (animations.enter) element.addClass(animations.enter);
if (animations.leave) element.addClass(animations.leave);
}
};
});
And put the directive on the element that contains the ngView directive:
<body ng-view view-animations></body>
Demo: http://plnkr.co/edit/Y3ExDyiPIJwvVKO4njBT?p=preview
Edit: New solution.
To set animations during run-time I would use a service just like you are doing, but a directive to apply them.
Very basic example of service:
app.factory('viewAnimationsService', function ($rootScope) {
var enterAnimation;
var getEnterAnimation = function () {
return enterAnimation;
};
var setEnterAnimation = function (animation) {
enterAnimation = animation;
};
var setLeaveAnimation = function (animation) {
$rootScope.$emit('event:newLeaveAnimation', animation);
};
return {
getEnterAnimation: getEnterAnimation,
setEnterAnimation: setEnterAnimation,
setLeaveAnimation: setLeaveAnimation
};
});
And the directive:
app.directive('viewAnimations', function (viewAnimationsService, $rootScope) {
return {
restrict: 'A',
link: function (scope, element) {
var previousEnter, previousLeave;
var enterAnimation = viewAnimationsService.getEnterAnimation();
if (enterAnimation) {
if (previousEnter) element.removeClass(previousEnter);
previousEnter = enterAnimation;
element.addClass(enterAnimation);
}
$rootScope.$on('event:newLeaveAnimation', function (event, leaveAnimation) {
if (previousLeave) element.removeClass(previousLeave);
previousLeave = leaveAnimation;
element.addClass(leaveAnimation);
});
}
};
});
Demo: http://plnkr.co/edit/DuQXaN2eYgtZ725Zqzeu?p=preview
I have been working on it and I have a neater solution, what I was doing had some problems. Now I am just using the $routeChangeStart at root scope and selecting there the leaving and enter transitions.
The only problem I have is that on the routeChangeStart event I can't modify the leaving view so I can't establish the leaving transition to the ngView element class attribute. I had to set it directly through the DOM (I know that is bad practice).
I tried to modify the leaving view through a shared service, the root scope and $apply() but none of them worked. Once the routeChangeStart event is launched the view seems static.
Here is a working example: jsfiddle.net/isidrogarcia/Fs5NZ
Related
I'm new to angularJS and i'm trying to have different backgrounds for different pages. But i'm having difficulty since i have my view tied to some elements in my index.html.
index.html
<div class="top-container">
<div class="navigation" >
<nav>....</nav>
</div>
<div ui-view="topContent"></div>
</div>
The ui-view injects new content for each page which is tied to the nav element. I currently have one background for the entire site using
.top-container{
background-image: url('bg.jpeg');
}
Any ideas how i can change the background for different pages? Any way i can pass some variable or something to change the bg image based on the page i'm on.
Thanks
This is how I would do it. Firstly, set the background on every view.
$stateProvider.state("home",
{
templateUrl: "/scripts/app/home/home.html",
url: "/home",
controller: 'HomeCtrl',
data: {
background: 'home'
}
})
.state("login",
{
templateUrl: "/scripts/app/login/login.html",
url: "/login",
controller: 'LoginCtrl',
data: {
background: 'login'
}
});
Now grab that setting on every state change and put it in the root scope.
app.run([
'$rootScope', function($rootScope)
{
$rootScope.$on('$stateChangeStart', function(event, toState)
{
$rootScope.background = toState.data.background;
});
}
]);
Now in the HTML set your background from the root scope on whatever element you want. Mind you, this has to be inside your ng-app declaration.
<body ng-class="background">
And that is the beauty of Angular for you.
Also, this is my go-to solution for anything that changes for every state. Like page title for starters..
You can use ng-class directive
ng-class="{'class1': yourvalue == 'something' || yourvalue == 'somethingelse'}"
you can use ngStyle for your CSS class. here
is the demo plunker for changing color.
Maybe you can add new class name on .top-container div when view changed.
$scope.$on('$routeChangeStart', function(next, current) {
... you could trigger something here ...
});
You can use the $routeChangeStart listener, which will be fired every time you try to switch to a different route/view.
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if(current.loadedTemplateUrl.indexOf("/call") > -1){
// set the required background image
}else if(current.loadedTemplateUrl.indexOf("/login") > -1){
// set the required background image
}
});
You can use javascript to set your background image or ng-class. For example
ng-class="{'one': url == '/login'}"
Note: Don't forget to add background-image property to your class.
It seems my li elements in angularjs directive not responding clicking event.
HTML:
<my-selbg>
<ul>
<li ng-repeat="bgimage in bgimages"><img src={{bgimage}} width="85" height="82" dir={{bgimage}}></li>
</ul>
</my-selbg>
JS:
var mlwcApp = angular.module('mlwcApp', [])
.controller('BgImagesListController', function($scope, $http) {
$http.get("http://localhost:8080/webcontent/bg_images").success(function(response) {
$scope.bgimages = response;
});
})
.directive('myselbg', function(){
return {
restrict: 'E',
scope: true,
link: function(scope, element, attrs){
var elementOne = angular.element(element.children[1]);
var elementTwo = angular.element(element.children[2]);
var elementThree = angular.element(element.children[3]);
setUpBGImg = function(){
console.log('link function');
};
$(elementOne).on('click', setUpBGImg);
$(elementTwo).on('click', setUpBGImg);
$(elementThree).on('click', setUpBGImg);
}
};
});
I have 3 li elements and clicking any of them dose not hit the code in link function. Anyone has idea?
You're new to angular, by the looks of it.
First off, before going any further - your directive will not even bind at all in the state it is in. You've got an element directive (which is fine, though if I were you I'd make it an attribute directive by restricting on A, which allows you to then apply it to the list rather than an element above it) named myselbg in your code. However, your markup is set as my-selbg, which would then look for the angular directive mySelbg, which does not exist.
In addition to this, your directive will evaluate before the list is rendered (thanks to the order of priority in execution). You have two choices to go around this:
You can do something like this: https://jsfiddle.net/a01n3srw/1/ . Really not recommended - I am using $timeout in order to evaluate code after the current refresh cycle is done, at which point the list fully exists
You can use the simple ngClick angular core directive in order to make this easy. Added bonus, when your function that you evaluate starts modifying scope, you won't shoot yourself in the foot using the previous method and having to use $apply
I'm trying to figure out how to show or hide an element in my navbar based on the route, or the current view being displayed. For example, I have an basic/advanced toggle button that I put in the navbar (Bootstrap 3) when the user is on the search form. But when they are anywhere else in the app that toggle button should be hidden.
In terms of the DOM, it's just a list item that builds out the nav. I'm not sure if I should show or hide based on a global value that gets set on each view, or if I can just use the route or view name. If so how that would work?
Thanks!
One solution is to build a function in the controller that is responsible for the navbar that could be queried to determine if the element should be displayed:
$scope.isActive = function(viewLocation) {
return viewLocation === $location.path();
};
(The above code uses Angular's $location service)
Then within the template, you can show/hide based on the result of the call (passing in the route that should toggle displaying the element):
ng-show="isActive('/search-form')"
Here's the approach I took with ui-router:
I only want to hide the navbar for a small number of pages so I went with an opt out property on the state(s) that I want to hide the navbar.
.state('photos.show', {
url: '/{photoId}',
views: {
"#" : {
templateUrl: 'app/photos/show/index.html',
controller: 'PhotoController'
}
},
hideNavbar: true
})
Inject $state in your navbar's controller and expose it to the template:
$scope.state = $state;
Then add ng-hide to your navbar template:
<nav ng-hide="state.$current.hideNavbar" ...
Above works perfectly using ui-router don't forget to pass $scope and $state within your function
Example:
app.controller('LoginCtrl', function($scope, $state){
$scope.state = $state;
console.log($state); // this will return the current state object just in case you need to see whats going on for newbies like me :)
});
I setup a single-page app with AngularJS and used Skrollr on the home page. I have not used Skrollr before, so I wanted to check with others about the proper 'Angular' way to integrate it with AngularJS, before I start to dive into using more features
What I did in Angular was create a service to load the script onto the page and call skrollr.init() and return it as a promise. Then injected the service to a directive which calls refresh as needed. If a page needs skrollr, I can use this directive on the page somewhere and set the data attributes per skrollr documentation.
ie this works:
<div class="main" skrollr-tag>
<div data-0="color:rgb(0,0,255);" data-90="color:rgb(255,0,0);">WOOOT</div>
</div>
It seems elements added to DOM later on, such as by ngRepeat, skrollr doesn't know about, so I need to include this directive on all elements generated dynamically w/ skrollr data attributes for it to work.
<div class="main" skrollr-tag>
<!-- this heading will animate all the time -->
<h1 data-0="opacity: 1" data-50="opacity: 0">WOOT!</h1>
<div data-ng-repeat="item in items" class="had-to-add-skrollr-again" skrollr-tag>
<!-- skrollr animates this only on page refresh, unless skrollr-tag duplicated above -->
<div data-0="color:rgb(0,0,255);" data-90="color:rgb(255,0,0);">{{item.name}}</div>
</div>
</div>
So, to recap, skrollr is 'aware' of these dynamic elements on the 1st load after refresh, but then after navigating to a different route then back again they no longer get animated unless you refresh page again, or add skrollr-tag directive to the dynamic elements themselves.
Is this a bad idea for performance reasons to include this directive on each dynamic element needing skrollr, thus calling refresh() again for each one? Ideally solution would be load skrollr-tag directive once per page, and it's aware of dynamic elements. I am open to any completely different cleaner more simple way to integrate skrollr to angular.
The angular code is here:
service:
.service('skrollrService', ['$document', '$q', '$rootScope', '$window',
function($document, $q, $rootScope, $window){
var defer = $q.defer();
function onScriptLoad() {
// Load client in the browser
$rootScope.$apply(function() {
var s = $window.skrollr.init({
forceHeight: false
});
defer.resolve(s);
});
}
// Create a script tag with skrollr as the source
// and call our onScriptLoad callback when it
// has been loaded
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.async = true;
scriptTag.src = 'lib/skrollr/dist/skrollr.min.js';
scriptTag.onreadystatechange = function () {
if (this.readyState === 'complete') onScriptLoad();
};
scriptTag.onload = onScriptLoad;
var s = $document[0].getElementsByTagName('body')[0];
s.appendChild(scriptTag);
return {
skrollr: function() { return defer.promise; }
};
}
]);
directive:
.directive('skrollrTag', [ 'skrollrService',
function(skrollrService){
return {
link: function(){
skrollrService.skrollr().then(function(skrollr){
skrollr.refresh();
});
}
};
}
])
I'm currently having the same issue trying to integrate Skrollr into AngularJS.
The problem is basically this directive, it works when the page loads for the first time but then nothing is happening even though its being called when new html elements are created - or when you change views.
.directive('skrollrTag', [ 'skrollrService',
function(skrollrService){
return {
link: function(){
skrollrService.skrollr().then(function(skrollr){
skrollr.refresh();
});
}
};
}
])
I think the reason is the way angularJS injects new html content. By the time skrollr does "refresh" its not yet rendered or some sort of conflict.
Maybe the only solution is to modify skrollr script.
This answer should help: AngularJS watch DOM change. Try updating your directive to watch for child node changes. This way, it'll automatically refresh whenever new nodes are added.
.directive('skrollrTag', [ 'skrollrService',
function(skrollrService){
return {
link: function(scope, element, attrs){
skrollrService.skrollr().then(function(skrollr){
skrollr.refresh();
});
//This will watch for any new elements being added as children to whatever element this directive is placed on. If new elements are added, Skrollr will be refreshed (pulling in the new elements
scope.$watch(
function () { return element[0].childNodes.length; },
function (newValue, oldValue) {
if (newValue !== oldValue) {
skrollrService.skrollr().then(function(skrollr){
skrollr.refresh();
});
}
});
}
};
}
]);
EDIT
Updated to account for the promise you're using (that would already be resolved), and added a comment to further explain the solution.
i made a directive for skrollr
(function () {
'use strict';
angular.module('myApp', [])
.directive('skrollr', function () {
var obj = {
link: function () {
/* jshint ignore:start */
skrollr.init().refresh();
/* jshint ignore:end */
}
};
return obj;
});
})();
and use like this
<div skrollr data-0="background-color:rgb(0,0,255);" data-500="background-color:rgb(255,0,0);">WOOOT</div>
I have a project that uses legacy code in .NET and WebForms. The legacy code uses several Update Panels.
My hope is to use AngularJS without impacting the legacy code base.
<div ng-app="myApp">
<NS:TheUserControl ID="TheUserControl1" runat="Server" />
</div>
Here is the javascript:
var app = angular.module("myApp", []);
app.run(function ($rootScope, $compile) {
function insertDirective() {
jQuery(targetElementSelector).attr("my-directive", "");
}
insertDirective();
var mgr = Sys.WebForms.PageRequestManager.getInstance();
mgr.add_endRequest(function (sender, args) {
insertDirective();
$compile(jQuery(targetElementSelector))($rootScope);
});
});
The code above adds an attribute to an HTML element inside NS:TheUserControl, and the attribute specifies a directive (see directive below).
Then, these steps are used when the update panel changes:
Detect the change using Sys.WebForms.PageRequestManager (EndRequest listener)
Re-add the directive using javascript
Run $compile on the newly-inserted directive
Here is the directive:
app.directive("myDirective", function () {
return {
template: "<span ng-repeat='item in items'>{{item}}</span>"
+ "<span ng-transclude></span>"
, transclude: true,
, controller: function ($scope) {
$scope.items = ["a", "b", "c"];
}
};
});
This almost works... except...
Problem 1:
AngularJS creates a scope associated with each instance of a directive. When the update panel inside NS:TheUserControl changes the DOM, the previous scopes are still present. I can see this in Batarang (a Chrome developer tool for AngularJS).
Problem 2:
For a brief moment after the update panels change, I get:
abc <-- Initial page load
abcabc <-- Subsequent update panel changes
abcabcabc
abcabcabcabc
Then, a moment later after each update panel change, the content jumps back to the correct:
abc
Questions:
So, how do I either:
Remove the orphaned scopes?
Or, incorporate AngularJS directives in a way that plays nice with the update panels?