/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * 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 = new Object(); export class SwitchView { constructor( private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {} create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); } destroy(): void { this._viewContainerRef.clear(); } } /** * @ngModule CommonModule * * @whatItDoes Adds / removes DOM sub-trees when the nest match expressions matches the switch * expression. * * @howToUse * ``` * * ... * ... * ... * * * * * * ...

*
* ``` * @description * * `NgSwitch` stamps out nested views when their match expression value matches the value of the * switch expression. * * In other words: * - you define a container element (where you place the directive with a switch expression on the * `[ngSwitch]="..."` attribute) * - you define inner views inside the `NgSwitch` and place a `*ngSwitchCase` attribute on the view * root elements. * * Elements within `NgSwitch` but outside of a `NgSwitchCase` or `NgSwitchDefault` directives will * be * preserved at the location. * * The `ngSwitchCase` directive informs the parent `NgSwitch` of which view to display when the * expression is evaluated. * When no matching expression is found on a `ngSwitchCase` view, the `ngSwitchDefault` view is * stamped out. * * @stable */ @Directive({selector: '[ngSwitch]'}) export class NgSwitch { private _switchValue: any; private _useDefault: boolean = false; private _valueViews = new Map(); private _activeViews: SwitchView[] = []; @Input() set ngSwitch(value: any) { // Empty the currently active ViewContainers this._emptyAllActiveViews(); // Add the ViewContainers matching the value (with a fallback to default) this._useDefault = false; let views = this._valueViews.get(value); if (!views) { this._useDefault = true; views = this._valueViews.get(_CASE_DEFAULT) || null; } 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)); } } /** @internal */ _emptyAllActiveViews(): void { const activeContainers = this._activeViews; for (var i = 0; i < activeContainers.length; i++) { activeContainers[i].destroy(); } this._activeViews = []; } /** @internal */ _activateViews(views: SwitchView[]): void { // TODO(vicb): assert(this._activeViews.length === 0); if (views) { for (var i = 0; i < views.length; i++) { views[i].create(); } this._activeViews = views; } } /** @internal */ _registerView(value: any, view: SwitchView): void { let views = this._valueViews.get(value); if (!views) { views = []; this._valueViews.set(value, views); } views.push(view); } /** @internal */ _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); } } } /** * @ngModule CommonModule * * @whatItDoes Creates a view that will be added/removed from the parent {@link NgSwitch} when the * given expression evaluate to respectively the same/different value as the switch * expression. * * @howToUse * * ... * * * @description * * Insert the sub-tree when the expression evaluates to the same value as the enclosing switch * expression. * * If multiple match expressions match the switch expression value, all of them are displayed. * * See {@link NgSwitch} for more details and example. * * @stable */ @Directive({selector: '[ngSwitchCase]'}) export class NgSwitchCase { // `_CASE_DEFAULT` is used as a marker for a not yet initialized value /** @internal */ _value: any = _CASE_DEFAULT; /** @internal */ _view: SwitchView; private _switch: NgSwitch; constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, @Host() ngSwitch: NgSwitch) { this._switch = ngSwitch; this._view = new SwitchView(viewContainer, templateRef); } @Input() set ngSwitchCase(value: any) { this._switch._onCaseValueChanged(this._value, value, this._view); this._value = value; } } /** * @ngModule CommonModule * @whatItDoes Creates a view that is added to the parent {@link NgSwitch} when no case expressions * match the * switch expression. * * @howToUse * * ... * ... * * * @description * * Insert the sub-tree when no case expressions evaluate to the same value as the enclosing switch * expression. * * See {@link NgSwitch} for more details and example. * * @stable */ @Directive({selector: '[ngSwitchDefault]'}) export class NgSwitchDefault { constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, @Host() sswitch: NgSwitch) { sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef)); } }