/** * @ngdoc directive * @name ui.router.state.directive:ui-view * * @requires ui.router.state.$state * @requires $compile * @requires $controller * @requires $injector * @requires ui.router.state.$uiViewScroll * @requires $document * * @restrict ECA * * @description * The ui-view directive tells $state where to place your templates. * * @param {string=} name A view name. The name should be unique amongst the other views in the * same state. You can have views of the same name that live in different states. * * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you * scroll ui-view elements into view when they are populated during a state activation. * * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* * * @param {string=} onload Expression to evaluate whenever the view updates. * * @example * A view can be unnamed or named. *
 * 
 * 
* * *
*
* * You can only have one unnamed view within any template (or root html). If you are only using a * single view and it is unnamed then you can populate it like so: *
 * 
* $stateProvider.state("home", { * template: "

HELLO!

" * }) *
* * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#methods_state `views`} * config property, by name, in this case an empty name: *
 * $stateProvider.state("home", {
 *   views: {
 *     "": {
 *       template: "

HELLO!

" * } * } * }) *
* * But typically you'll only use the views property if you name your view or have more than one view * in the same template. There's not really a compelling reason to name a view if its the only one, * but you could if you wanted, like so: *
 * 
*
*
 * $stateProvider.state("home", {
 *   views: {
 *     "main": {
 *       template: "

HELLO!

" * } * } * }) *
* * Really though, you'll use views to set up multiple views: *
 * 
*
*
*
* *
 * $stateProvider.state("home", {
 *   views: {
 *     "": {
 *       template: "

HELLO!

" * }, * "chart": { * template: "" * }, * "data": { * template: "" * } * } * }) *
* * Examples for `autoscroll`: * *
 * 
 * 
 *
 * 
 * 
 * 
 * 
 * 
* * Resolve data: * * The resolved data from the state's `resolve` block is placed on the scope as `$resolve` (this * can be customized using [[ViewDeclaration.resolveAs]]). This can be then accessed from the template. * * Note that when `controllerAs` is being used, `$resolve` is set on the controller instance *after* the * controller is instantiated. The `$onInit()` hook can be used to perform initialization code which * depends on `$resolve` data. * * Example usage of $resolve in a view template *
 * $stateProvider.state('home', {
 *   template: '',
 *   resolve: {
 *     user: function(UserService) { return UserService.fetchUser(); }
 *   }
 * });
 * 
*/ $ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate', '$q']; function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate, $q) { function getService() { return ($injector.has) ? function(service) { return $injector.has(service) ? $injector.get(service) : null; } : function(service) { try { return $injector.get(service); } catch (e) { return null; } }; } var service = getService(), $animator = service('$animator'), $animate = service('$animate'); // Returns a set of DOM manipulation functions based on which Angular version // it should use function getRenderer(attrs, scope) { var statics = function() { return { enter: function (element, target, cb) { target.after(element); cb(); }, leave: function (element, cb) { element.remove(); cb(); } }; }; if ($animate) { return { enter: function(element, target, cb) { if (angular.version.minor > 2) { $animate.enter(element, null, target).then(cb); } else { $animate.enter(element, null, target, cb); } }, leave: function(element, cb) { if (angular.version.minor > 2) { $animate.leave(element).then(cb); } else { $animate.leave(element, cb); } } }; } if ($animator) { var animate = $animator && $animator(scope, attrs); return { enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, leave: function(element, cb) { animate.leave(element); cb(); } }; } return statics(); } var directive = { restrict: 'ECA', terminal: true, priority: 400, transclude: 'element', compile: function (tElement, tAttrs, $transclude) { return function (scope, $element, attrs) { var previousEl, currentEl, currentScope, latestLocals, onloadExp = attrs.onload || '', autoScrollExp = attrs.autoscroll, renderer = getRenderer(attrs, scope), inherited = $element.inheritedData('$uiView'); scope.$on('$stateChangeSuccess', function() { updateView(false); }); updateView(true); function cleanupLastView() { if (previousEl) { previousEl.remove(); previousEl = null; } if (currentScope) { currentScope.$destroy(); currentScope = null; } if (currentEl) { var $uiViewData = currentEl.data('$uiView'); renderer.leave(currentEl, function() { $uiViewData.$$animLeave.resolve(); previousEl = null; }); previousEl = currentEl; currentEl = null; } } function updateView(firstTime) { var newScope, name = getUiViewName(scope, attrs, inherited, $interpolate), previousLocals = name && $state.$current && $state.$current.locals[name]; if (!firstTime && previousLocals === latestLocals) return; // nothing to do newScope = scope.$new(); latestLocals = $state.$current.locals[name]; /** * @ngdoc event * @name ui.router.state.directive:ui-view#$viewContentLoading * @eventOf ui.router.state.directive:ui-view * @eventType emits on ui-view directive scope * @description * * Fired once the view **begins loading**, *before* the DOM is rendered. * * @param {Object} event Event object. * @param {string} viewName Name of the view. */ newScope.$emit('$viewContentLoading', name); var clone = $transclude(newScope, function(clone) { var animEnter = $q.defer(), animLeave = $q.defer(); var viewData = { name: name, $animEnter: animEnter.promise, $animLeave: animLeave.promise, $$animLeave: animLeave }; renderer.enter(clone.data('$uiView', viewData), $element, function onUiViewEnter() { animEnter.resolve(); if(currentScope) { currentScope.$emit('$viewContentAnimationEnded'); } if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { $uiViewScroll(clone); } }); cleanupLastView(); }); currentEl = clone; currentScope = newScope; /** * @ngdoc event * @name ui.router.state.directive:ui-view#$viewContentLoaded * @eventOf ui.router.state.directive:ui-view * @eventType emits on ui-view directive scope * @description * Fired once the view is **loaded**, *after* the DOM is rendered. * * @param {Object} event Event object. * @param {string} viewName Name of the view. */ currentScope.$emit('$viewContentLoaded', name); currentScope.$eval(onloadExp); } }; } }; return directive; } $ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { return { restrict: 'ECA', priority: -400, compile: function (tElement) { var initial = tElement.html(); return function (scope, $element, attrs) { var current = $state.$current, $uiViewData = $element.data('$uiView'), locals = current && current.locals[$uiViewData.name]; if (! locals) { return; } extend($uiViewData, { state: locals.$$state }); $element.html(locals.$template ? locals.$template : initial); var resolveData = angular.extend({}, locals); scope[locals.$$resolveAs] = resolveData; var link = $compile($element.contents()); if (locals.$$controller) { locals.$scope = scope; locals.$element = $element; var controller = $controller(locals.$$controller, locals); if (locals.$$controllerAs) { scope[locals.$$controllerAs] = controller; scope[locals.$$controllerAs][locals.$$resolveAs] = resolveData; } if (isFunction(controller.$onInit)) controller.$onInit(); $element.data('$ngControllerController', controller); $element.children().data('$ngControllerController', controller); } link(scope); }; } }; } /** * Shared ui-view code for both directives: * Given scope, element, and its attributes, return the view's name */ function getUiViewName(scope, attrs, inherited, $interpolate) { var name = $interpolate(attrs.uiView || attrs.name || '')(scope); return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : '')); } angular.module('ui.router.state').directive('uiView', $ViewDirective); angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill);