1

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();
  }

}
2
  • Why exactly are you having two different lists when you already have a isWhitelisted boolean on each Tag. I think that should just be sufficient and you should toggle it based on which one of the tags was clicked. Commented Oct 11, 2018 at 20:29
  • stackoverflow.com/a/46330816/3186873 could help! Commented Oct 11, 2018 at 20:29

4 Answers 4

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I LOVE what you did for the whitelistAll and blacklistAll refactor. This cleans things up so much. Thank you!
Glad I could help. :)
1

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.

2 Comments

PS the other answers given here are also correct and provide an alternative approach of doing exactly the same thing using getters and setters
This works perfect and is so clean. Thank you danday74!
1

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.

Comments

1

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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.