demilp
demilp

Reputation: 43

How can a ngFor on a filtered array be reevaluated?

I'm doing a twitter moderator in Angular. For that I have a list of tweets, each tweet being like this:

export class Tweet{
    id: number;
    text: string;
    image: string;
    profileImage: string;
    name: string;
    username: string;
    date: number;
    state: TweetState;
}

TweetState is an enum with 4 states, new, approved, favorite and denied.

I want to show them each one in a different tab, and when the user click on the corresponding button, change the state and therefor change the tab where it is.

This is my html:

<mat-tab-group mat-selected="selectedTab" mat-border-bottom mat-autoselect mat-swipe-content>
  <mat-tab label="NEW" class="tab" fxLayout="row">
      <app-tweet *ngFor="let tweet of campaign.tweets | new" [tweet]="tweet"></app-tweet>
  </mat-tab>
  <mat-tab label="FAVORITE" class="tab">
    <app-tweet *ngFor="let tweet of campaign.tweets | favorite" [tweet]="tweet"></app-tweet>
  </mat-tab>
  <mat-tab label="APPROVED" class="tab">    
    <app-tweet *ngFor="let tweet of campaign.tweets | approved" [tweet]="tweet"></app-tweet>
  </mat-tab>
  <mat-tab label="DENIED" class="tab">
    <app-tweet *ngFor="let tweet of campaign.tweets | denied" [tweet]="tweet"></app-tweet>
  </mat-tab>
</mat-tab-group>

The pipes work fine, at least at the initial state. When I click the button to change the state, the state changes correctly but it doesn't switches tab.

I imagine that the *ngFor directive runs when the DOM is created and I should remove it from one place and add it on the other manually. What is the best approach? An Observable?

This is the Tweet template

<mat-card class="tweet">
  <mat-card-header>
    <img matCardAvatar src="{{tweet.profileImage}}" alt="{{tweet.username}}">
    <mat-card-title>{{tweet.name}}</mat-card-title>
    <mat-card-subtitle>{{tweet.username}}</mat-card-subtitle>
  </mat-card-header>
  <img mat-card-image src="{{tweet.image}}" alt="{{tweet.username}}">
  <mat-card-content>
      <p>
        {{tweet.text}}
      </p>
      <p>
        {{tweet.state}}
      </p>
    </mat-card-content>
  <mat-card-actions>
    <div fxFlex="auto" fxLayout="row" fxLayoutAlign="space-around center">
      <span class="material-icons unselectable" (click)="setApproved()">thumb_up</span>
      <span class="material-icons unselectable" (click)="setDenied()">thumb_down</span>
      <span class="material-icons unselectable" (click)="setFavorite()">star</span>
    </div>
  </mat-card-actions>
</mat-card>

Upvotes: 0

Views: 320

Answers (1)

Mike Tung
Mike Tung

Reputation: 4831

If your issue is the data update, the reason why it isn't updating is because you didn't tell Angular that you have altered the state of tweets.

Where you have *ngFor="tweet of campaign.tweets", you should change over to an observable. *ngFor="tweet of (tweets$ | async)" where in your component or the parent component generate the Observable on the fly. For example in the same component for this template you could do.

//the component boiler plate stuff
export class MyTweetComponent implements OnInit, OnChanges {
  @Input() tweets$: Observable<Tweet[]> //parse your campaign object up front.

  ngOnInit() {} // initialize tweets$ here if you got a service or something.
  ngOnChanges() {} //tell angular to detect changes (you may add additional logic here for handling tweets)

  constructor() { } //if you have a service to grab tweets you can inject it here

}

The async pipe will handle the subscription for you, all you have to do is in the component pass the observable of tweets down from parent or initialize it.

Upvotes: 1

Related Questions