Smooth
Smooth

Reputation: 956

How to automatically update Angular 6 component variables when a change to a different variable is detected?

I have a "tags" component variable that generates a whitelist and a blacklist. Currently, I have a updateTagLists() function which will update the respective whitelist and blacklist but I have to make sure this function is called when necessary in order for the whitelist/blacklist to properly update. This seems counter-intuitive and feels incorrect. What's the correct way of having this.whitelist and this.blacklist automatically update when this.tags changes? Posted below is my component.

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

import { Tag } from '../../models/tag';
import { TagService } from '../../services/tag.service';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {
  tags: any;
  whitelist: Tag[];
  blacklist: Tag[];

  constructor(
    private tagService: TagService
   ) {}

  ngOnInit() {
    this.tagService.getTags(1).then((tags) => {
      this.tags = tags;
      this.updateTagLists
    });
  }

  updateTagLists() {
    this.whitelist = this.tags.filter((tag: Tag) => tag.isWhitelisted );
    this.blacklist = this.tags.filter((tag: Tag) => !tag.isWhitelisted );
  }

  whitelistAll() {
    // Todo: Is there a better way of doing this where we aren't specifying the exact keys needed?
    let updatedTags = this.tags.filter((tag) => !tag.isWhitelisted)
    updatedTags = updatedTags.map((tag) => {
      return { id: tag.id, name: tag.name, isWhitelisted: true, updated: true }
    });
    this.tags = updatedTags.concat(this.tags.filter((tag) => tag.isWhitelisted));
    this.updateTagLists();
  }

  blacklistAll() {
    // Todo: Is there a better way of doing this where we aren't specifying the exact keys needed?
    let updatedTags = this.tags.filter((tag) => tag.isWhitelisted);
    updatedTags = updatedTags.map((tag) => {
      return { id: tag.id, name: tag.name, isWhitelisted: false, updated: true }
    });
    this.tags = updatedTags.concat(this.tags.filter((tag) => !tag.isWhitelisted));
    this.updateTagLists();
  }

  handleToggle(event) {
    if (!event) return;
    let foundTag = this.tags.find( (tag) => event.id === tag.id );
    foundTag.isWhitelisted = !event.isWhitelisted;
    foundTag.updated = true;
    this.updateTagLists();
  }

}

Upvotes: 1

Views: 922

Answers (4)

SiddAjmera
SiddAjmera

Reputation: 39432

The best way is to just deal with one tags array and then do the needful based on that.

I've updated your whiteListAll, blackListAll, and handleToggle methods.

import { Component } from '@angular/core';
import { TagService } from './tag.service';

export interface Tag {
  id: number;
  name: string;
  isWhitelisted: boolean;
  updated: boolean;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  tags: Tag[];

  get whiteListedTags() {
    return this.tags.filter(tag => tag.isWhitelisted);
  }

  get whiteBlackTags() {
    return this.tags.filter(tag => !tag.isWhitelisted);
  }    

  constructor(private tagService: TagService) {}

  ngOnInit() {
    this.tagService.getTags(1).then((tags) => {
      this.tags = tags;
    });
  }

  whitelistAll() {
    this.tags = [...this.tags.map(tag => ({...tag, isWhitelisted: true, updated: true}))];
  }

  blacklistAll() {
    this.tags = [...this.tags.map(tag => ({...tag, isWhitelisted: false, updated: true}))];
  }

  handleToggle(index) {
    this.tags[index].isWhitelisted = !this.tags[index].isWhitelisted;
  }
}

In your template, you can check if a Tag is whitelisted by checking if it's isWhitelisted property is true.

Optionally, you can also create getters for getting Blacklisted and Whitelisted tags as I've done above.

Here's a Sample StackBlitz for your ref.

Upvotes: 1

Vivek Kumar
Vivek Kumar

Reputation: 5040

I don't completely understand the question, but if you want to perform some action on change of tags. then create a setter function for it, so that the this.tags are updating only through this function.

setTags(tags) {
   this.tags = tags;
   this.updateWhitelist();
   this.updateBlacklist();
}

From the code it seems that tags are only getting changed from onInit() function. But if you want to make the tags as input() then

private _tags;

@Input()
set tags(tags) {
   this._tags = tags;
   this.updateWhitelist();
   this.updateBlacklist();
}

get tags()  {
   return tags;
}

Upvotes: 1

mgm87
mgm87

Reputation: 894

You would use a setter on your tags variable.

Here's an example:

...

private _tags: any;

set tags(someTags: any) {
  this._tags = someTags;
  this.updateTagLists();
}

get tags(): any {
  return this._tags;
}

...

This will run the set function when the value is updated. The set function will set a private var that holds the current tags value and it will make a call to update the other lists. The getter gets the value from the private var and returns it.

Upvotes: 1

danday74
danday74

Reputation: 56966

Instead of using a function like this:

updateTagLists() {
  this.whitelist = this.tags.filter((tag: Tag) => tag.isWhitelisted );
  this.blacklist = this.tags.filter((tag: Tag) => !tag.isWhitelisted );
}

Just use 2 TypeScript getters like this:

get whitelist() {
  return this.tags.filter((tag: Tag) => tag.isWhitelisted )
}

get blacklist() {
  return this.tags.filter((tag: Tag) => !tag.isWhitelisted )
}

You can reference them in your HTML like this:

{{ whitelist | json }} {{ whitelist.length }}
{{ blacklist | json }} {{ blacklist.length }}

Or in your controller like this:

console.log(this.whitelist)
console.log(this.blacklist)

And they will always be 100% accurate. No need to worry about calling a function at the right time to keep their values updated.

Upvotes: 1

Related Questions