fix(common): NgSwitch - don’t create the default case if another case matches (#12726)

This also simplifies the implementation of `NgSwitch`.

Closes #11297
Closes #9420
This commit is contained in:
Tobias Bosch
2016-11-07 12:22:36 -08:00
committed by vikerman
parent 32fcec9fcb
commit d8f23f4b7f
3 changed files with 196 additions and 167 deletions

View File

@ -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();
}
}
}
/**
@ -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);
}
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);
}
// 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;
set ngSwitch(newValue: any) {
this._ngSwitch = newValue;
if (this._caseCount === 0) {
this._updateDefaultCases(true);
}
}
/** @internal */
_registerView(value: any, view: SwitchView): void {
let views = this._valueViews.get(value);
if (!views) {
views = [];
this._valueViews.set(value, views);
_addCase(): number { return this._caseCount++; }
/** @internal */
_addDefault(view: SwitchView) {
if (!this._defaultViews) {
this._defaultViews = [];
}
views.push(view);
this._defaultViews.push(view);
}
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);
/** @internal */
_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;
}
return matched;
}
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);
}
}
}
}
@ -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)); }
}
/**
@ -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));
}
}