Skip to content

Commit

Permalink
fix(common): NgSwitch - don’t create the default case if another ca…
Browse files Browse the repository at this point in the history
…se matches (angular#12726)

This also simplifies the implementation of `NgSwitch`.

Closes angular#11297
Closes angular#9420
  • Loading branch information
tbosch authored and vikerman committed Nov 7, 2016
1 parent 32fcec9 commit d8f23f4
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 180 deletions.
152 changes: 60 additions & 92 deletions modules/@angular/common/src/directives/ng_switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,31 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';

import {ListWrapper} from '../facade/collection';

const _CASE_DEFAULT = {};
import {Directive, DoCheck, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';

export class SwitchView {
private _created = false;

constructor(
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}

create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
create(): void {
this._created = true;
this._viewContainerRef.createEmbeddedView(this._templateRef);
}

destroy(): void { this._viewContainerRef.clear(); }
destroy(): void {
this._created = false;
this._viewContainerRef.clear();
}

enforceState(created: boolean) {
if (created && !this._created) {
this.create();
} else if (!created && this._created) {
this.destroy();
}
}
}

/**
Expand Down Expand Up @@ -64,92 +76,52 @@ export class SwitchView {
*/
@Directive({selector: '[ngSwitch]'})
export class NgSwitch {
private _switchValue: any;
private _useDefault: boolean = false;
private _valueViews = new Map<any, SwitchView[]>();
private _activeViews: SwitchView[] = [];
private _defaultViews: SwitchView[];
private _defaultUsed = false;
private _caseCount = 0;
private _lastCaseCheckIndex = 0;
private _lastCasesMatched = false;
private _ngSwitch: any;

@Input()
set ngSwitch(value: any) {
// Set of views to display for this value
let views = this._valueViews.get(value);

if (views) {
this._useDefault = false;
} else {
// No view to display for the current value -> default case
// Nothing to do if the default case was already active
if (this._useDefault) {
return;
}
this._useDefault = true;
views = this._valueViews.get(_CASE_DEFAULT);
set ngSwitch(newValue: any) {
this._ngSwitch = newValue;
if (this._caseCount === 0) {
this._updateDefaultCases(true);
}

this._emptyAllActiveViews();
this._activateViews(views);
this._switchValue = value;
}

/** @internal */
_onCaseValueChanged(oldCase: any, newCase: any, view: SwitchView): void {
this._deregisterView(oldCase, view);
this._registerView(newCase, view);

if (oldCase === this._switchValue) {
view.destroy();
ListWrapper.remove(this._activeViews, view);
} else if (newCase === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._emptyAllActiveViews();
}
view.create();
this._activeViews.push(view);
}
_addCase(): number { return this._caseCount++; }

// Switch to default when there is no more active ViewContainers
if (this._activeViews.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViews(this._valueViews.get(_CASE_DEFAULT));
}
}

private _emptyAllActiveViews(): void {
const activeContainers = this._activeViews;
for (var i = 0; i < activeContainers.length; i++) {
activeContainers[i].destroy();
}
this._activeViews = [];
}

private _activateViews(views?: SwitchView[]): void {
if (views) {
for (var i = 0; i < views.length; i++) {
views[i].create();
}
this._activeViews = views;
/** @internal */
_addDefault(view: SwitchView) {
if (!this._defaultViews) {
this._defaultViews = [];
}
this._defaultViews.push(view);
}

/** @internal */
_registerView(value: any, view: SwitchView): void {
let views = this._valueViews.get(value);
if (!views) {
views = [];
this._valueViews.set(value, views);
_matchCase(value: any): boolean {
const matched = value == this._ngSwitch;
this._lastCasesMatched = this._lastCasesMatched || matched;
this._lastCaseCheckIndex++;
if (this._lastCaseCheckIndex === this._caseCount) {
this._updateDefaultCases(!this._lastCasesMatched);
this._lastCaseCheckIndex = 0;
this._lastCasesMatched = false;
}
views.push(view);
return matched;
}

private _deregisterView(value: any, view: SwitchView): void {
// `_CASE_DEFAULT` is used a marker for non-registered cases
if (value === _CASE_DEFAULT) return;
const views = this._valueViews.get(value);
if (views.length == 1) {
this._valueViews.delete(value);
} else {
ListWrapper.remove(views, view);
private _updateDefaultCases(useDefault: boolean) {
if (this._defaultViews && useDefault !== this._defaultUsed) {
this._defaultUsed = useDefault;
for (var i = 0; i < this._defaultViews.length; i++) {
const defaultView = this._defaultViews[i];
defaultView.enforceState(useDefault);
}
}
}
}
Expand Down Expand Up @@ -179,24 +151,20 @@ export class NgSwitch {
* @stable
*/
@Directive({selector: '[ngSwitchCase]'})
export class NgSwitchCase {
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
private _value: any = _CASE_DEFAULT;
export class NgSwitchCase implements DoCheck {
private _view: SwitchView;
private _switch: NgSwitch;

@Input()
ngSwitchCase: any;

constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) {
this._switch = ngSwitch;
@Host() private ngSwitch: NgSwitch) {
ngSwitch._addCase();
this._view = new SwitchView(viewContainer, templateRef);
}

@Input()
set ngSwitchCase(value: any) {
this._switch._onCaseValueChanged(this._value, value, this._view);
this._value = value;
}
ngDoCheck() { this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase)); }
}

/**
Expand Down Expand Up @@ -226,7 +194,7 @@ export class NgSwitchCase {
export class NgSwitchDefault {
constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() sswitch: NgSwitch) {
sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef));
@Host() ngSwitch: NgSwitch) {
ngSwitch._addDefault(new SwitchView(viewContainer, templateRef));
}
}
Loading

0 comments on commit d8f23f4

Please sign in to comment.