angular
angular copied to clipboard
feature: support multiple case matching in `ngSwitch`/`@switch`
Like what anuglar 1 provide https://docs.angularjs.org/api/ng/directive/ngSwitch
Also mentioned in here: https://github.com/angular/angular/issues/12174
Angular version: 2.4.7
@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>
@kanlidy You solution may not work according to https://github.com/angular/angular/issues/12174.
I'd also love that feature, since plain switch case in every language provide that functionality.
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.
any progress on this ?
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 :(
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.
+1 on this
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.
Relates to #20027 (which is possible while this one is not)
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>
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?
@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
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.
@santialbo I like the sound of that. Implementation should be quite simple as well.
That's https://github.com/angular/angular/issues/20027 then.
@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
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.
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>
https://github.com/angular/angular/pull/27421 add the support of array on *ngSwithCase and is fully compatible with the current version.
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.
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.
The PR of @maxigimenez was closed because of the breaking change in the introduced array support. https://github.com/angular/angular/pull/27421#issuecomment-444682516
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;
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'">
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.
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.
@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. 😃
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
-->
Ideally something like:
@case (1)
@case (2) {
...
}
Or a list of options
@case (1, 2) {
...
}