0

So previously in Angular 1.X i've been used to creating local variables on the html-side.

It usually looks something like this (AngularJS):

<div class="parentContainer" ng-repeat="error in $ctrl.systemErrorMessages>
    <div ng-click="showHide=(showHide ? false : true)">
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        Click me to toggle all selectors in the same hierarchical-level (scope) as me.
    </div>

    <div ng-if="showHide"> {{error.message}} </div>
</div>

The code above will create the variable and show the div inside.

However, if I in Angular 1.X put this within a ng-repeat tag, that variable would be local to its scope. Lets say there are 50 entries in systemErrMessages, when i now click the toggle-div, all 50 entries are reacting... it used to be so that - only the message whos toggle-div i clicked; reacted...

It seems that Angular2 breaks the scope of ngFor's, and affects ALL elements.

What i'm trying in (Angular 2):

<div *ngFor="let error of systemErrMessages">
     <div class="item" [hidden]="showHide">
        ErrorMessage: {{error}}
     </div>
     <div (click)="showHide=(showHide ? false : true)">TOGGLE above</div>
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</div>

To clarify (TL;DR):

HTML-structure (what the ngFor has generated):

<div class="parentContainer">
    <div class="item">
        <div class="information" [hidden]="showHide">lorem ipsum</div>
        <div (click)="showHide=(showHide ? false : true)">TOGGLE</div>
    </div>
    <div class="item">...</div>
    <div class="item">...</div>
    <div class="item">...</div>
    <div class="item">...</div>
    <div class="item">...</div>
    <div class="item">...</div>
</div>

When i click TOGGLE in angular2, ALL elements are visible.

In AngularJS only that .item's .information is visible.

Is it possible to get the same behaviour as in 1.X ?

Joel
  • 5,732
  • 4
  • 37
  • 65
  • where do you declare `showHide`? – ABOS Dec 20 '18 at 15:31
  • @ABOS Thats the point. I dont have to. Its dynamically created. – Joel Dec 20 '18 at 15:32
  • so it defaults to the component "scope" which is shared by all items – ABOS Dec 20 '18 at 15:33
  • you can either put showHide flag on the item object or make each item a componentize – ABOS Dec 20 '18 at 15:36
  • @ABOS is there any resources i can read where its specified why this behaviour has changed form 1.X to 2? – Joel Dec 20 '18 at 15:36
  • I don't know any specific resources just for that. Since angular 2 is component based, everything should be handled in a component way. that being said, old hierarchy scope concept does not exist any more – ABOS Dec 20 '18 at 15:40
  • @ABOS This project has been used with ng-metadata and components have been used. This is not a case of "angular2 being component-based". That is not what has changed, since i have been using components all along in 1.X. – Joel Dec 20 '18 at 15:41
  • what I tried to say is to think in a component way. Like in your case, everything is put on the parent component scope. – ABOS Dec 20 '18 at 15:43
  • @ABOS Don't see how that would answer my question. I know i can declare a variable to do this in my controller. But that would defeat the entire purpose of using dynamically created variables. – Joel Dec 20 '18 at 15:44

3 Answers3

2

Angular^7.x.x

If you want to create dynamic variable in template then just create it:

<div *ngFor="let error of systemErrMessages; let showHide = 'showHide'">
                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
     <div class="item" [hidden]="showHide">
        ErrorMessage: {{error}}
     </div>
     <div (click)="showHide = !showHide">TOGGLE above</div>
</div>

otherwise your showHide variable will have component scope.

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Just left work, but will try to verify solution tomorrow, looks like what I had in mind as a solution.Thanks – Joel Dec 20 '18 at 16:23
  • `Error: Cannot assign to a reference or variable!` and `Unhandled Promise rejection: Cannot assign to a reference or variable! ; Zone: ; Task: Promise.then ; Value` – Joel Dec 21 '18 at 08:11
  • Can you reproduce it on stackblitz? – yurzui Dec 21 '18 at 08:44
  • Here's an example https://stackblitz.com/edit/angular-exubr1?file=src%2Fapp%2Fapp.component.html – yurzui Dec 21 '18 at 09:04
  • Can't really reproduce it all, but, i can tell you that, the addition of this code causes the error. Here's a jsfiddle with the html atleast. Best i can do for now, sorry. https://jsfiddle.net/s8x1dkba/ – Joel Dec 21 '18 at 09:36
  • Can you show me the full error message in devtools? – yurzui Dec 21 '18 at 09:41
  • Which angular version are you using? – yurzui Dec 21 '18 at 10:18
  • i'm using the final version of angular2 (its written in the question also but), i included it in the title previously, but someone edited the question. – Joel Dec 21 '18 at 10:22
  • Try it with my code. And then add other parts consequentially. Or do you see this error even with my template? – yurzui Dec 21 '18 at 10:30
  • Most likely the cause of this: https://stackoverflow.com/questions/38582293/how-to-declare-a-variable-in-a-template-in-angular – Joel Dec 21 '18 at 10:34
  • That question has nothing to do what I use here. And it works for me even in production – yurzui Dec 21 '18 at 10:38
  • Have u tried in Angular2? (seems like this approach was an addition in a4) – Joel Dec 21 '18 at 10:38
  • Are you using angular@2.x.x? Not the latest 4-7.x.x? Which exactly version you're using? – yurzui Dec 21 '18 at 10:40
  • as stated multiple times, angular2 final version. i.e. ( 2.4.10) – Joel Dec 21 '18 at 10:41
  • When people say angular 2 they usually mean all versions of Angular that comes after AngularJs. Now I see that you're using 2.4.10. And seems yes, it is not supported in that version – yurzui Dec 21 '18 at 10:44
  • Nope thats incorrect, angular2 is angular2. "angular" is 2.X < : https://angular.io/guide/ajs-quick-reference read first sentance. anyway, enough semantics for now. Anway, I guess my link is relevant after all. I'll approve this as the answer (could you please add a note saying for Angular 4.x <), as i will come back to this method once all my components are 2.X. (migrating rn) – Joel Dec 21 '18 at 10:47
  • 1
    Our project is now using `Angular 7.2.2` - this approach worked! – Joel Feb 04 '19 at 15:13
0

It seems that Angular2 breaks the scope of ngFor's, and affects ALL elements.

Everything is component based, there is no Scope (global, implicit, or otherwise) like there was in angularjs. When referencing instances in your html they are assumed to be rooted in the component to which the template is associated with.

Is it possible to get the same behavior as in 1.X ?

No. You have to explicitly define where state (in this case) is tracked as scope is not a thing anymore in angular2+.

With the code you posted the easiest way to change it to be "angular2+ compliant" would be to track the visibility on the item in the array. In this case expand the string array to be an array of an object containing the string and if it should be visible or not.

See stackblitz

app.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  systemErrMessages: {error: string, hidden: boolean}[];

  ngOnInit(){
    // ['one','two','three'] could be any source
    this.systemErrMessages = ['one','two','three'].map(_ => {
      return {error: _, hidden: true};
    });

    this.systemErrMessages.push({error: 'any other error', hidden: false});
  }
}

app.component.html

<div *ngFor="let error of systemErrMessages">
     <div class="item" [hidden]="error.hidden">
        ErrorMessage: {{error.error}}
     </div>
     <button (click)="error.hidden = !error.hidden">Toggle</button>
</div>
Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    I don't mind downvotes but please leave me some constructive critisim on why the answer was downvoted so I can fix it. – Igor Dec 20 '18 at 15:51
  • I'm not sure why its being downvoted. However, there are obvious flaws in this answer. First one being: i have no property called `hidden` in systemErrMessages. I know how to accomplish a toggle-switch with the use of a component. BUT - the entire question is regarding the usage of dynamically created variables. Posting an answer where a variable is created in the component is cheating then ( : – Joel Dec 20 '18 at 15:53
  • 1
    @Joel - I am not sure what you mean by dynamically created variables. Yes, they are hard coded above but you could add a button and text to push items to the array or have them come from any other source. You just have to define what the default value would be for visibility when you add items to the array. – Igor Dec 20 '18 at 15:58
  • So, i'll try to adress your edit now: First of all, my 1.X application is component based. Secondly: no, i'm not sure the easiest way would be to `to track the visibility on the item in the array` ... `expand the string array to be an array of an object `. – Joel Dec 20 '18 at 15:59
  • dynamically created variable which has a property,type,value == `checkOutThisBooleanVariableThatCanBeUsedInItsScope = (checkOutThisBooleanVariableThatCanBeUsedInItsScope ? false : true)`. If you were somehow able to do a console.log on ^. It would present you with false or true and of type boolean. Yes, scope still exists in A2, but it has changed from 1.X. But creating the dynamically created variables still work. i'm just wondering about the scope. – Joel Dec 20 '18 at 16:01
  • 1
    @Joel - see the edit, the array of error messages can be easily transformed into an array of some other object type that tracks its own visibility. I initially had it hard coded simply as an [mcve]. It is easy to map it from an array of strings or anything else really. – Igor Dec 20 '18 at 16:03
  • TL;DR: Your suggestion would be to add a flag to each item in the array? R.I.P. Performance – Joel Dec 20 '18 at 16:05
  • 1
    @Joel - not sure if you saw but there is a working [stackblitz](https://stackblitz.com/edit/angular-3bs5wy) included with the answer. – Igor Dec 20 '18 at 16:05
  • 1
    @Joel - This would still be performant, adding ad additional boolean per item tracked would not be detrimental to performance at all. What do you think that the implicit addition of a scope per item did in `ng-repeat`? It also added a new object per iteration and then added a boolean tracking item on each created scope (based on your template). This would as performant as or faster than `ng-repeat` simply because the creation of scope is not done and tracking is set explicitly by your code (which could speed it up). – Igor Dec 20 '18 at 16:08
  • 1
    If performance is also a worry with this solution, then perhaps too many items are being rendered on the page as well.. – tymeJV Dec 20 '18 at 16:10
-1

Joel, in Angular the variable is created dinamically too, but it's always the same. Is like you declare in your .ts the variable showHide. As is the same variable affects all the divs. The solution is have severals variables -one for each div-

If you iterate over an array of object, as Igor say you, simply can be

<button (click)="error.hidden = !error.hidden">Toggle</button>

If you iterate over an array of strings, you need declare an array of checks, and use the index. that's

<!--take a look to ";let i=index"-->
<div *ngFor="let error of systemErrMessages;let i=index">
     <div (click)="showHide[i]=(showHide[i] ? false : true)">TOGGLE above</div>
</div>

//And in your .ts don't forget declare
showHide:boolean[]=[]
Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • The second solution assumes that `systemErrMessages` does not change after `showHide` has been initialized (otherwise you have to change this with any additions / removals). Also `showHide[i]=(showHide[i] ? false : true)` could be reduced to `showHide[i]=!showHide[i]` – Igor Dec 20 '18 at 16:14
  • Joel, In Angular ALL the variables are variables of the component (variables declares in the .ts after the declare the class and before any function). In the .ts, inside a function you refered to the variable using this.nameOfvariable, and you can declare temporaly variables using "let". You has only **one** variable that control if some is visible and change its value in several divs. You need so many variables as divs you have. – Eliseo Dec 20 '18 at 17:10
  • @Eliseo you are still missing the point here. this `works` in A2 the same way as it did in 1.X metadata (HOWEVER, the scope works differently). global and local scopes are still a thing, not sure why you are suggesting otherwise here. The question is simply regarding how to make this work dynamically, i.e. not creating anything in TS but rather make use of A2 to control it. See suggested solution above, its totally doable. You're approach isnt related to the question here. I know how to do it using the component, thats not what i'm asking. – Joel Dec 21 '18 at 08:25