Skip to content

feature: support multiple case matching in ngSwitch/@switch #14659

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
peterhe2000 opened this issue Feb 23, 2017 · 38 comments
Open

feature: support multiple case matching in ngSwitch/@switch #14659

peterhe2000 opened this issue Feb 23, 2017 · 38 comments
Assignees
Labels
area: common Issues related to APIs in the @angular/common package feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Milestone

Comments

@peterhe2000
Copy link

peterhe2000 commented Feb 23, 2017

Like what anuglar 1 provide
https://docs.angularjs.org/api/ng/directive/ngSwitch

Also mentioned in here:
#12174

Angular version: 2.4.7

@wayearn
Copy link

wayearn commented Feb 23, 2017

@peterhe2000 I support you.

I am building an angular application like the below.

<div [ngSwitch]="data.type">
  <div *ngSwitchCase="'multi-choice'">FORM 1</div>
  <div *ngSwitchCase="'singe-choice'">FORM 1</div>
  <div *ngSwitchCase="'range'">FORM 2</div>
</div>

In angular1, I can do this:

<div class="animate-switch" ng-switch-when="'multi-choice'|'singe-choice'" ng-switch-when-separator="|">Settings Div</div>

But, there's no "ng-switch-when-seprartor" feature like in angular2

<div [ngSwitch]="data.type">
  <div *ngSwitchCase="'multi-choice' || 'singe-choice'">FORM 1</div>
</div>

for now, the solution is:

<div [ngSwitch]="data.type">
    <div *ngSwitchCase="data.type=='multi-choice' ||data.type== 'singe-choice'?data.type:'notInCase'">FORM 1</div>
<div *ngSwitchCase="'range'">FORM 2</div>
</div>

@peterhe2000
Copy link
Author

@kanlidy You solution may not work according to #12174.

@tbosch tbosch added area: common Issues related to APIs in the @angular/common package feature Issue that requests a new feature labels Apr 10, 2017
@waterplea
Copy link
Contributor

I'd also love that feature, since plain switch case in every language provide that functionality.

@kherock
Copy link

kherock commented Jul 16, 2017

Hi, I'm also interested in this! I also just had the thought of borrowing the new NgIf/Then/Else microsyntax here since it kind of makes sense for NgSwitch to be able to use a TemplateRef. This issue could be solved by allowing the following syntax:

<div [ngSwitch]="data.type">
  <div *ngSwitchCase="'multi-choice'; do form1"></div>
  <div *ngSwitchCase="'singe-choice'; do form1"></div>
  <ng-template #form1>FORM 1</ng-template>
  <div *ngSwitchCase="'range'">FORM 2</div>
</div>

Additionally, it allows for some other neat things that you can do with TemplateRefs.
Obviously it's not quite as terse as most languages' switch keyword but it seems to fit well. Also I can't think of a better keyword than do at the moment.

@Sky4CE
Copy link

Sky4CE commented Aug 4, 2017

any progress on this ?

@Ghostbird
Copy link

Ghostbird commented Aug 9, 2017

I'd like to see this too, I was surprised that angular does not offer this functionality, since it is one of the basic functionalities of switch statements in most languages.
For now I'll rewrite my code with *ngIf statements.

EDIT: Oh wait, then I'll have to use a crazy nested construction of else blocks to replace the default case :(

@Ghostbird
Copy link

Ghostbird commented Aug 9, 2017

Would it be sufficient to replace:

 _matchCase(value: any): boolean {
const matched = value == this._ngSwitch;

with:

 _matchCase(value: any): boolean {
const matched = value == this._ngSwitch
    || (value instanceof Array && value.some(x => x == value));

in packages/common/src/directives/ng_switch.ts

I guess it isn't that simple.

In the meantime I'm going to try the boolean evaluation trick mentioned by @kanlidy, thanks for that.

@rogerramosme
Copy link

+1 on this

@ngbot ngbot bot added this to the Backlog milestone Jan 23, 2018
@pmccloghrylaing
Copy link
Contributor

I don't mind the NgIf (...; do ...) syntax, that seems like a reasonable solution.

Anything else requires a separator, the best I could think of was or. The boolean and bitwise OR operators (|| and |) should behave as per usual in JS. Or you require the separator to be specified like in v1.

Another option could be defining the cases in child elements, e.g.:

<div [ngSwitch]="data.type">
  <div *ngSwitchMultiple>
    <ng-switch-case value="'multi-choice'"></ng-switch-case>
    <ng-switch-case value="'single-choice'"></ng-switch-case>
    FORM 1
  </div>
  <div *ngSwitchCase="'range'">FORM 2</div>
</div>

I think in comparison using the NgIf do is far simpler.

@trotyl
Copy link
Contributor

trotyl commented Jan 24, 2018

Relates to #20027 (which is possible while this one is not)

@kherock
Copy link

kherock commented Jan 24, 2018

Yeah I don't think the separator approach is that great of an idea. #20027 also seems reasonable.

Also, if anyone following this wants an actual workaround they can do now without having to duplicate code, NgTemplateOutlet can be used in the style I suggested before, though it's still a lot of boilerplate:

<div [ngSwitch]="data.type">
  <div *ngSwitchCase="'multi-choice'">
    <ng-container *ngTemplateOutlet="form1"></ng-container>
  </div>
  <div *ngSwitchCase="'single-choice'">
    <ng-container *ngTemplateOutlet="form1"></ng-container>
  </div>
  <ng-template #form1>FORM 1</ng-template>

  <div *ngSwitchCase="'range'">FORM 2</div>
</div>

@kkajdd
Copy link

kkajdd commented Jan 31, 2018

HI,
When i try to implement ngSwitch it works like charm but in the spec file i get an error as No provider for NgSwitch? what might be the cause?

@WKenya
Copy link

WKenya commented Feb 13, 2018

@specialkk For now I think this is a good approach. Its not ideal but it works and is what I ended up doing until I just fixed our switch statements to not need this. I would prefer somehow if we could move that template HTML into another file somehow. It feels odd just having your template at the bottom of your html (or wherever) to reference.

Another workaround would be this answer which I prefer: https://stackoverflow.com/a/45950368/8678531

@santialbo
Copy link

santialbo commented Apr 11, 2018

In order to keep it backwards compatible. Would it be acceptable to have something like the following:

<div [ngSwitch]="data.type">
  <div *ngSwitchCases="['multi-choice', 'single-choice']">FORM 1</div>
  <div *ngSwitchCase="'range'">FORM 2</div>
</div>

Notice it's a new directive ngSwitchCases. If someone from the angular team thinks it's a good solution I'm up for making a PR.

@pmccloghrylaing
Copy link
Contributor

@santialbo I like the sound of that. Implementation should be quite simple as well.

@trotyl
Copy link
Contributor

trotyl commented Apr 12, 2018

That's #20027 then.

@adamdport
Copy link

adamdport commented May 25, 2018

@santialbo IMHO it doesn't need to be a new directive. Why not leave it ngSwitchCase? If the case is an array, check each value, otherwise use the existing functionality

@pkozlowski-opensource
Copy link
Member

IMHO it doesn't need to be a new directive. Why not leave it ngSwitchCase? If the case is an array, check each value, otherwise use the existing functionality

What if you would like to match on an array instance ? Doing what you propose would break this use-case. So this needs to be a new directive to clearly indicate that we want to match on multiple values.

@jonrimmer
Copy link
Contributor

Until this is merged, I created a standalone directive that you can add to your project:

https://gist.github.com/jonrimmer/eaabd619e2edeaebed83b7bc68f33daf

Use it like this:

<div [ngSwitch]="value">
  <div *jrSwitchCases="['foo', 'bar']">
    Foo or bar
  </div>
</div>

@maxigimenez
Copy link

#27421 add the support of array on *ngSwithCase and is fully compatible with the current version.

@IlCallo
Copy link

IlCallo commented May 20, 2019

Any progress in this?
Considering that we are now going to Angular 8, this could be implemented even if it's a breaking change.
And if not Angular 8 (it's a bit late), we can put it inside Angular 9.

@Lakston
Copy link

Lakston commented Jun 2, 2020

Apparently it's not in A9 either, I'd love to see this feature someday I just had to hack it by doing this:

<ng-container [ngSwitch]="true">
<ng-container *ngSwitchCase="options === 'a'">Code A</ng-container>
<ng-container *ngSwitchCase="options === 'b'">Code B</ng-container>
<ng-container *ngSwitchCase="options === 'c'">Code C</ng-container>
<ng-container *ngSwitchCase="options === 'd' || options === 'e' || options === 'f'">Common Code</ng-container>
<ng-container *ngSwitchDefault>Code Default</ng-container>
</ng-container>

Hopefully if you end up in this issue it will at least give you a way to do it.

@angular-robot angular-robot bot added the feature: under consideration Feature request for which voting has completed and the request is now under consideration label Jun 4, 2021
@twittwer
Copy link

twittwer commented Aug 12, 2021

The PR of @maxigimenez was closed because of the breaking change in the introduced array support. #27421 (comment)

Would it be an option to reduce the breaking change by checking if the base value is no array?

-    const matched = Array.isArray(value) ?
+    const matched = Array.isArray(value) && !Array.isArray(this._ngSwitch) ?
      value.indexOf(this._ngSwitch) > -1 : value === this._ngSwitch;

@petebacondarwin petebacondarwin changed the title New feature: ng-switch-when-separator in Angular 2 to allow multiple values feature: support multiple case matching in ngSwitch Oct 26, 2021
@petebacondarwin petebacondarwin self-assigned this Oct 26, 2021
@MickL
Copy link

MickL commented Oct 26, 2021

This feature request is open since 2016 in #43950 and is a very common use case but each time very annoying not to find a pleasing solution for me. Personally I mostly end up using ngTemplateOutlet.

Possible implementations:

With or: <ng-container *ngSwitchCase="'cat' || 'dog'">
As array: <ng-container *ngSwitchCase="['cat', 'dog']">
In new expression: <ng-container *ngSwitchCases="'cat', 'dog'">
Multiple expressions on one tag: <ng-container *ngSwitchCase="'cat'" *ngSwitchCase="'dog'">

@samclaus
Copy link

samclaus commented Nov 3, 2021

Just to offer some framework cross pollination:

Svelte does not currently have a {#switch} directive. However, it does have full-fledged if-else-if-else-if-...-else logic support in templates. By comparison, Angular's over-engineered *ngIf "microsyntax" with support for else via a TemplateRef is a real PITA. I have not once needed to write a custom structural directive and I don't ever plan to: keeping any sort of complicated logic in TypeScript and keeping templates as a shallow/dumb layer which only handles matching on basic view state possibilities avoids uncanny-valley reimplementations of too many JS features in the Angular template compiler (why do the same work twice when the languages can work in tandem?).

The only reason I find myself on this old ngSwitch issue is because Angular's barebones if-else template logic is not very capable and requires you to write several <ng-templates> to achieve the logic chaining, although you can flatten template elements to the outermost level of Angular-HTML to avoid indentation hell so it is not the end of the world at least.

I think making *ngIf more powerful would alleviate many problems. Svelte is waiting on JS match syntax to be formalized before implementing a switch/match feature in templates, but it doesn't matter because their if functionality closes the gap and effectively makes such a feature nothing more than a cherry on top. And if you do not understand why switch is a bad--of course, it was made with good intentions--general language construct and match is a natural evolution for it, please see my comment on the aforementioned issue.

In any case, Angular currently has no way to nicely write a view with many different but flat (non-nested) states where some of the states overlap and want to re-use the same template fragment (chunk of code). It's an older framework and I want to be clear I'm NOT just here to talk s**t; this is intended as constructive criticism and I think Angular did a good job at a lot of things but some inevitable missteps were made. I don't know how the Angular team feels about large-scale template language changes vs. 80% fixes via the "standard library" but I do recall from other issues I have written that there is a somewhat-reasonable fear of introducing non-HTML-compliant syntax to templates because that has a good chance of breaking tooling which parses the templates as HTML. Of course, such tooling could be updated to use an Angular-provided parser which handles a superset of HTML. Food for thought.

@petebacondarwin
Copy link
Contributor

Thanks @samclaus - we did indeed when discussing this PR recently talk about the potential for offering more native control flow semantics in templates. This is actually much more achievable now that we have Ivy (and dropped ViewEngine) and would also potentially offer other benefits in type checking etc. That being said this would be a big project, to plan, implement, and roll out, which means that it is unlikely to happen very soon.

In the meantime, the expectation is that we will provide an interim improvement to the current directives - probably via a ngSwitcheCases directive and potentially being able to select cases exclusively (e.g. first match rather than all matches), to improve the developer experience, while we work out how more significant change might be done.

@samclaus
Copy link

samclaus commented Nov 3, 2021

@petebacondarwin That's awesome! Sounds like you guys have a good game plan. Of course it will take time. I don't think any serious developers would shame a team for taking a while to implement things cleanly. 😃

@michahell
Copy link

Any progress on this?

it feels really pretty dumb to have to include these kinds of comments in template code:

          <!--
            Why are there 2 <dwpc-key-value-text> instances here?
            Because NG cannot handle multiple ngSwitchCase conditions:
            https://github.com/angular/angular/issues/14659#issuecomment-959830827
          -->

@Harpush
Copy link

Harpush commented Nov 1, 2023

Ideally something like:

@case (1)
@case (2) {
  ...
}

@twittwer
Copy link

Or a list of options

@case (1, 2) {
  ...
}

@JoostK JoostK changed the title feature: support multiple case matching in ngSwitch feature: support multiple case matching in ngSwitch/@switch Nov 15, 2023
@ilyakonrad
Copy link

Surprised to not see this supported by the new control flow syntax, it's been awaited for over 7 years now 😞

@nook24
Copy link

nook24 commented Apr 30, 2024

Can't believe that this is still not possible

@BastienVH
Copy link

seems like it works with enums

Can you please give an example of the syntax that works with enums?
I've looked around and tried some things, but can't get it to work.
What version of Angular are you on?

@Para-FR
Copy link

Para-FR commented Aug 11, 2024

Adam Lambert So What

@for (column of props().columns; track $index) {

            @switch (column.field) {
              @case ('status') {
                <td>
                  <app-badge [props]="{ label: item[column.field] }"/>
                </td>
              }
              @case ('date' || 'createdAt' || 'updatedAt') { // or @case 1 @case 2 @case 3
                <td>{{ item[column.field] | date }}</td>
              }
              @default {
                <td>{{ item[column.field] }}</td>
              }
            }
          }```

@sklei
Copy link

sklei commented Oct 23, 2024

@for (column of props().columns; track $index) {

        @switch (column.field) {
          @case ('status') {
            <td>
              <app-badge [props]="{ label: item[column.field] }"/>
            </td>
          }
          @case ('date' || 'createdAt' || 'updatedAt') { // or @case 1 @case 2 @case 3
            <td>{{ item[column.field] | date }}</td>
          }
          @default {
            <td>{{ item[column.field] }}</td>
          }
        }
      }```

This doesn't work for me. Only the first condition is evaluated. So in my case:

@case (QuestionType.FileUpload || QuestionType.MultipleFileUpload) {
    <app-case-summary-question-file-upload [attachments]="question.attachments" />
}

QuestionType.MultipleFileUpload is ignored. If I make both cases explicit it works as expected:

@case (QuestionType.FileUpload) {
    <app-case-summary-question-file-upload [attachments]="question.attachments" />
}
@case (QuestionType.MultipleFileUpload) {
    <app-case-summary-question-file-upload [attachments]="question.attachments" />
}

@khadden-compline
Copy link

@for (column of props().columns; track $index) {

        @switch (column.field) {
          @case ('status') {
            <td>
              <app-badge [props]="{ label: item[column.field] }"/>
            </td>
          }
          @case ('date' || 'createdAt' || 'updatedAt') { // or @case 1 @case 2 @case 3
            <td>{{ item[column.field] | date }}</td>
          }
          @default {
            <td>{{ item[column.field] }}</td>
          }
        }
      }```

Doesn't work. Results in X [ERROR] NG2: This kind of expression is always truthy. [plugin angular-compiler]

@Ketec
Copy link

Ketec commented Feb 13, 2025

Ran into the same issue, wanted to reduce duplicate code.
Right now i have to hack it with @let (including any because of the annoying any indexing issue/template vars lacking type)

    @let nameKey = $any(({name: 'name', firstName: 'firstName', lastName: 'lastName'}))[column.key];
    @switch (column.key) {
      @case (nameKey) { shared content }
      @case ('other key') { diff content}
      ....
    }

even with templates it is still a lot of duplication of ng-containers and template outlets/context etc.

@joafic
Copy link

joafic commented Mar 18, 2025

Still no solution for this case? It would be nice to have something like this:
@case(1) {
//code
}
@case (2), @case (3) {
//other code
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: common Issues related to APIs in the @angular/common package feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.