Skip to content

Commit d637333

Browse files
committed
fix(lifecycle): lifecycle events fire reliably from the correct scope
1 parent e31498c commit d637333

16 files changed

+9564
-130
lines changed

js/angular/controller/navViewController.js

+1
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate,
206206
if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
207207
viewScope = viewElement.scope();
208208
viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
209+
viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);
209210
break;
210211
}
211212
}

js/angular/directive/view.js

+30
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
* show. Also contained is transition data, such as the transition type and
4444
* direction that will be or was used.
4545
*
46+
* Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
47+
* desirable for a child/nested view to be notified of the event.
48+
* For this use case, `$ionicParentView` life cycle events are broadcast downwards.
49+
*
4650
* <table class="table">
4751
* <tr>
4852
* <td><code>$ionicView.loaded</code></td>
@@ -83,6 +87,32 @@
8387
* <td>The view's controller has been destroyed and its element has been
8488
* removed from the DOM.</td>
8589
* </tr>
90+
* <tr>
91+
* <td><code>$ionicParentView.enter</code></td>
92+
* <td>The parent view has fully entered and is now the active view.
93+
* This event will fire, whether it was the first load or a cached view.</td>
94+
* </tr>
95+
* <tr>
96+
* <td><code>$ionicParentView.leave</code></td>
97+
* <td>The parent view has finished leaving and is no longer the
98+
* active view. This event will fire, whether it is cached or destroyed.</td>
99+
* </tr>
100+
* <tr>
101+
* <td><code>$ionicParentView.beforeEnter</code></td>
102+
* <td>The parent view is about to enter and become the active view.</td>
103+
* </tr>
104+
* <tr>
105+
* <td><code>$ionicParentView.beforeLeave</code></td>
106+
* <td>The parent view is about to leave and no longer be the active view.</td>
107+
* </tr>
108+
* <tr>
109+
* <td><code>$ionicParentView.afterEnter</code></td>
110+
* <td>The parent view has fully entered and is now the active view.</td>
111+
* </tr>
112+
* <tr>
113+
* <td><code>$ionicParentView.afterLeave</code></td>
114+
* <td>The parent view has finished leaving and is no longer the active view.</td>
115+
* </tr>
86116
* </table>
87117
*
88118
* ## LifeCycle Event Usage

js/angular/service/history.js

-3
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $
295295
// create an element from the viewLocals template
296296
ele = $ionicViewSwitcher.createViewEle(viewLocals);
297297
if (this.isAbstractEle(ele, viewLocals)) {
298-
console.log('VIEW', 'abstractView', DIRECTION_NONE, viewHistory.currentView);
299298
return {
300299
action: 'abstractView',
301300
direction: DIRECTION_NONE,
@@ -417,8 +416,6 @@ function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $
417416
}
418417
}
419418

420-
console.log('VIEW', action, direction, viewHistory.currentView);
421-
422419
hist.cursor = viewHistory.currentView.index;
423420

424421
return {

js/angular/service/viewSwitcher.js

+125-18
Original file line numberDiff line numberDiff line change
@@ -298,32 +298,67 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe
298298

299299
},
300300

301-
emit: function(step, enteringData, leavingData) {
302-
var enteringScope = enteringEle.scope(),
303-
leavingScope = leavingEle && leavingEle.scope();
301+
emit: function(step, enteringData, leavingData) {
302+
var enteringScope = getScopeForElement(enteringEle, enteringData);
303+
var leavingScope = getScopeForElement(leavingEle, leavingData);
304304

305-
if (step == 'after') {
306-
if (enteringScope) {
307-
enteringScope.$emit('$ionicView.enter', enteringData);
308-
}
305+
var prefixesAreEqual;
309306

310-
if (leavingScope) {
311-
leavingScope.$emit('$ionicView.leave', leavingData);
307+
if ( !enteringData.viewId || enteringData.abstractView ) {
308+
// it's an abstract view, so treat it accordingly
312309

313-
} else if (enteringScope && leavingData && leavingData.viewId) {
314-
enteringScope.$emit('$ionicNavView.leave', leavingData);
310+
// we only get access to the leaving scope once in the transition,
311+
// so dispatch all events right away if it exists
312+
if ( leavingScope ) {
313+
leavingScope.$emit('$ionicView.beforeLeave', leavingData);
314+
leavingScope.$emit('$ionicView.leave', leavingData);
315+
leavingScope.$emit('$ionicView.afterLeave', leavingData);
316+
leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);
317+
leavingScope.$broadcast('$ionicParentView.leave', leavingData);
318+
leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);
315319
}
316320
}
321+
else {
322+
// it's a regular view, so do the normal process
323+
if (step == 'after') {
324+
if (enteringScope) {
325+
enteringScope.$emit('$ionicView.enter', enteringData);
326+
enteringScope.$broadcast('$ionicParentView.enter', enteringData);
327+
}
317328

318-
if (enteringScope) {
319-
enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
320-
}
329+
if (leavingScope) {
330+
leavingScope.$emit('$ionicView.leave', leavingData);
331+
leavingScope.$broadcast('$ionicParentView.leave', leavingData);
332+
}
333+
else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
334+
// we only want to dispatch this when we are doing a single-tier
335+
// state change such as changing a tab, so compare the state
336+
// for the same state-prefix but different suffix
337+
prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
338+
if ( prefixesAreEqual ) {
339+
enteringScope.$emit('$ionicNavView.leave', leavingData);
340+
}
341+
}
342+
}
321343

322-
if (leavingScope) {
323-
leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
344+
if (enteringScope) {
345+
enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
346+
enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);
347+
}
324348

325-
} else if (enteringScope && leavingData && leavingData.viewId) {
326-
enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
349+
if (leavingScope) {
350+
leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
351+
leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);
352+
353+
} else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
354+
// we only want to dispatch this when we are doing a single-tier
355+
// state change such as changing a tab, so compare the state
356+
// for the same state-prefix but different suffix
357+
prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
358+
if ( prefixesAreEqual ) {
359+
enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
360+
}
361+
}
327362
}
328363
},
329364

@@ -407,6 +442,15 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe
407442
containerEle.innerHTML = viewLocals.$template;
408443
if (containerEle.children.length === 1) {
409444
containerEle.children[0].classList.add('pane');
445+
if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {
446+
angular.element(containerEle.children[0]).attr("abstract", "true");
447+
}
448+
else {
449+
if ( viewLocals.$$state && viewLocals.$$state.self ) {
450+
angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);
451+
}
452+
453+
}
410454
return jqLite(containerEle.children[0]);
411455
}
412456
}
@@ -491,4 +535,67 @@ function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDe
491535
}
492536
}
493537

538+
function compareStatePrefixes(enteringStateName, exitingStateName) {
539+
var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');
540+
var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');
541+
542+
// if either of the prefixes are empty, just return false
543+
if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {
544+
return false;
545+
}
546+
547+
var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);
548+
var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);
549+
550+
return enteringPrefix === exitingPrefix;
551+
}
552+
553+
function getScopeForElement(element, stateData) {
554+
if ( !element ) {
555+
return null;
556+
}
557+
// check if it's abstract
558+
var attributeValue = angular.element(element).attr("abstract");
559+
var stateValue = angular.element(element).attr("state");
560+
561+
if ( attributeValue !== "true" ) {
562+
// it's not an abstract view, so make sure the element
563+
// matches the state. Due to abstract view weirdness,
564+
// sometimes it doesn't. If it doesn't, don't dispatch events
565+
// so leave the scope undefined
566+
if ( stateValue === stateData.stateName ) {
567+
return angular.element(element).scope();
568+
}
569+
return null;
570+
}
571+
else {
572+
// it is an abstract element, so look for element with the "state" attributeValue
573+
// set to the name of the stateData state
574+
var elements = aggregateNavViewChildren(element);
575+
for ( var i = 0; i < elements.length; i++ ) {
576+
var state = angular.element(elements[i]).attr("state");
577+
if ( state === stateData.stateName ) {
578+
stateData.abstractView = true;
579+
return angular.element(elements[i]).scope();
580+
}
581+
}
582+
// we didn't find a match, so return null
583+
return null;
584+
}
585+
}
586+
587+
function aggregateNavViewChildren(element) {
588+
var aggregate = [];
589+
var navViews = angular.element(element).find("ion-nav-view");
590+
for ( var i = 0; i < navViews.length; i++ ) {
591+
var children = angular.element(navViews[i]).children();
592+
var childrenAggregated = [];
593+
for ( var j = 0; j < children.length; j++ ) {
594+
childrenAggregated = childrenAggregated.concat(children[j]);
595+
}
596+
aggregate = aggregate.concat(childrenAggregated);
597+
}
598+
return aggregate;
599+
}
600+
494601
}]);

0 commit comments

Comments
 (0)