Continuously extract information by filtering through the array

Currently, I am in the process of developing an idle RPG game using Angular. One of the features I have implemented is a console log that displays events such as Damage Dealt and Experience Earned.

In order to manage messages efficiently, I have created a service called MessageService which contains an array property consisting of Messages with attributes like text, date, and type.

import { Message } from "@core/models/message";
import { Injectable } from "@angular/core";
import { MESSAGE } from "@core/constant/constant";

@Injectable({
    providedIn: "root"
})
export class MessageService {
    messages: Message[] = [];

    private add(message: String, type: string) {
        this.messages.push(new Message(message, type));
    }
    addGeneralMessage(message: String) {
        this.add(message, MESSAGE.GENERAL);
    }
    addCombatMessage(message: String) {
        this.add(message, MESSAGE.COMBAT);
    }
    clear() {
        this.messages = [];
    }
    constructor() {}
}

To enhance user experience, I have incorporated buttons above the console log that allow users to filter messages based on specific types (Combat / General / System).

Although I can successfully filter messages using

messages.filter(message => message.type == type)
, I am facing challenges in dynamically receiving new messages of the selected type.

import { Message } from "@core/models";
import { MESSAGE } from "@core/constant/constant";
import { MessageService } from "@core/services";
import { Component, OnInit } from "@angular/core";
@Component({
    selector: "app-message",
    templateUrl: "./message.component.html",
    styleUrls: ["./message.component.scss"]
})
export class MessageComponent implements OnInit {
    messages: Message[];
    constructor(public messageService: MessageService) {}

    ngOnInit() {
        this.messages = this.messageService.messages;
    }

    filterByType(type: String) {
        if (type == MESSAGE.ALL) {
            this.messages = this.messageService.messages;
        } else {
            this.messages = this.messageService.messages.filter(
                item => item.type == type
            );
        }
    }
}

Any suggestions or solutions would be greatly appreciated. I attempted to use observables without success, indicating a possible implementation error on my part.

EDIT : Here is how my message component looks:

<div class="log">
    <app-message-button-menu
        (filter)="filterByType($event)"
    ></app-message-button-menu>
    <app-message-chat [messages]="messages"></app-message-chat>
</div>

Details about my app-message-button-menu:

<div class="menuLog">
    <app-message-button
        [text]="'All'"
        [type]="MESSAGE.ALL"
        [active]="activeButton == MESSAGE.ALL"
        (messageType)="onFilter($event)"
    ></app-message-button>
    <app-message-button
        [text]="'General'"
        [type]="MESSAGE.GENERAL"
        [active]="activeButton == MESSAGE.GENERAL"
        (messageType)="onFilter($event)"
    ></app-message-button>
    <app-message-button
        [text]="'Fight'"
        [type]="MESSAGE.COMBAT"
        [active]="activeButton == MESSAGE.COMBAT"
        (messageType)="onFilter($event)"
    ></app-message-button>
    <app-message-button
        [text]="'System'"
        [type]="MESSAGE.SYSTEM"
        [active]="activeButton == MESSAGE.SYSTEM"
        (messageType)="onFilter($event)"
    ></app-message-button>
</div>

import { Component, OnInit, Output, EventEmitter, Input } from "@angular/core";

@Component({
    selector: "app-message-button",
    templateUrl: "./message-button.component.html",
    styleUrls: ["./message-button.component.scss"]
})
export class MessageButtonComponent implements OnInit {
    @Input() type: String;
    @Input() text: String;
    @Input() active: boolean;
    @Output() messageType = new EventEmitter<String>();
    constructor() {}

    ngOnInit() {}

    filter() {
        this.messageType.emit(this.type);
    }
}
import { Component, OnInit, Output, EventEmitter, Input } from "@angular/core";

@Component({
    selector: "app-message-button",
    templateUrl: "./message-button.component.html",
    styleUrls: ["./message-button.component.scss"]
})
export class MessageButtonComponent implements OnInit {
    @Input() type: String;
    @Input() text: String;
    @Input() active: boolean;
    @Output() messageType = new EventEmitter<String>();
    constructor() {}

    ngOnInit() {}

    filter() {
        this.messageType.emit(this.type);
    }
}

Information regarding app-message-button:

<button [ngClass]="{ active: active == true }" (click)="filter()" type="button">
    {{ text }}
</button>

import { Component, OnInit, Output, EventEmitter, Input } from "@angular/core";
import { MESSAGE } from "@core/constant/constant";
@Component({
    selector: "app-message-button-menu",
    templateUrl: "./message-button-menu.component.html",
    styleUrls: ["./message-button-menu.component.scss"]
})
export class MessageButtonMenuComponent implements OnInit {
    MESSAGE;
    activeButton: String;
    @Output() filter = new EventEmitter<String>();
    constructor() {}

    ngOnInit(): void {
        this.MESSAGE = MESSAGE;
        this.activeButton = MESSAGE.ALL;
    }
    onFilter(type: String) {
        this.activeButton = type;
        this.filter.emit(type);
    }
}

Lastly, here is information regarding app-message-chat:

<ul>
    <app-message-item
        *ngFor="let message of messages; trackBy: trackBy"
        [message]="message"
    ></app-message-item>
</ul>

import { Component, OnInit, Input } from "@angular/core";
import { Message } from "@core/models/message";
@Component({
    selector: "app-message-chat",
    templateUrl: "./message-chat.component.html",
    styleUrls: ["./message-chat.component.scss"]
})
export class MessageChatComponent implements OnInit {
    @Input("messages") messages: Message[];

    constructor() {}

    ngOnInit(): void {}

    trackBy(index: number, item: Message): Message {
        return item;
    }
}

UPDATE: Ling Vu's solution worked for me:

import { Message } from "@core/models/message";
import { Injectable } from "@angular/core";
import { MESSAGE } from "@core/constant/constant";
import { ReplaySubject } from "rxjs";

@Injectable({
    providedIn: "root"
})
export class MessageService {
    messages: Message[] = [];
    filteredMessages: ReplaySubject<Message[]> = new ReplaySubject(1);
    filter: String;

    private add(message: String, type: string) {
        this.messages.push(new Message(message, type));
        this.filterMessages();
    }
    addGeneralMessage(message: String) {
        this.add(message, MESSAGE.GENERAL);
    }
    addCombatMessage(message: String) {
        this.add(message, MESSAGE.COMBAT);
    }
    clear() {
        this.messages = [];
    }
    setFilter(filter: String) {
        this.filter = filter;
    }
    filterMessages() {
        if (!this.filteredMessages)
            this.filteredMessages = new ReplaySubject(1);
        if (this.filter === MESSAGE.ALL) {
            this.filteredMessages.next(this.messages);
        } else {
            this.filteredMessages.next(
                this.messages.filter(item => item.type === this.filter)
            );
        }
    }
    constructor() {}
}

And here's the updated message component:

export class MessageComponent implements OnInit {
    messages: Message[];

    constructor(public messageService: MessageService) {}

    ngOnInit() {
        this.messageService.setFilter(MESSAGE.ALL);
        this.messageService.filteredMessages.subscribe(
            messages => (this.messages = messages)
        );
    }
    filterByType(type: String) {
        this.messageService.setFilter(type);

        if (type === MESSAGE.ALL) {
            this.messages = this.messageService.messages;
        } else {
            this.messages = this.messageService.messages.filter(
                messages => messages.type === type
            );
        }
    }
}

Unfortunately, I wasn't able to implement the Observable property in my component as suggested. I will continue researching for further insight.

A special thanks to Ling Vu for the assistance!

Answer №1

Implement the usage of an observable in this manner:

MessageService

import { Message } from "@core/models/message";
import { Injectable } from "@angular/core";
import { MESSAGE } from "@core/constant/constant";

@Injectable({
    providedIn: "root"
})
export class MessageService {
    messages: Message[] = [];
    filteredMessages: ReplaySubject<Message[]>;
    filter: string;

    private add(message: String, type: string) {
        this.messages.push(new Message(message, type));
        this.filterMessages();
    }
    addGeneralMessage(message: String) {
        this.add(message, MESSAGE.GENERAL);
    }
    addCombatMessage(message: String) {
        this.add(message, MESSAGE.COMBAT);
    }
    clear() {
        this.messages = [];
    }

    setFilter(filter: string) {
       this.filter = filter
    }

    filterMessages() {
        if (!filteredMessages) filteredMessages = new ReplaySubject(1);

        this.filteredMessages.next(this.messageService.messages.filter(
             item => item.type === this.filter
        ));
    }
    constructor() {}
}

and then observe it within the component. Make sure your attribute in the component is of type Observable<Message[]>. This will allow you to utilize it using *ngIf along with the async pipe.

Answer №2

Recently, my focus has been on react, so I may not provide examples following best practices.

Nevertheless, the example below should demonstrate a functional app. Also, remember to use type string instead of String. You can read more about it here.

Message service:

import {Injectable} from '@angular/core';
import {Message} from './message';
import {MESSAGE_TYPE} from './messageType'

@Injectable()
export class MessageService {
  public messages: Message[] = [
    {message: 'general', type: MESSAGE_TYPE.GENERAL},
    {message: 'general1', type: MESSAGE_TYPE.GENERAL},
    {message: 'general2', type: MESSAGE_TYPE.GENERAL},
    {message: 'general3', type: MESSAGE_TYPE.GENERAL},
    {message: 'combat', type: MESSAGE_TYPE.COMBAT},
    {message: 'combat1', type: MESSAGE_TYPE.COMBAT},
    {message: 'combat2', type: MESSAGE_TYPE.COMBAT},
    {message: 'combat3', type: MESSAGE_TYPE.COMBAT},
  ];

  private add(message: string, type: MESSAGE_TYPE): void {
    this.messages.push({message, type});
  }
  public addGeneralMessage(message: string): void {
    this.add(message, MESSAGE_TYPE.GENERAL);
  }
  public addCombatMessage(message: string): void {
    this.add(message, MESSAGE_TYPE.COMBAT);
  }
  public clear(): void {
    this.messages = [];
  }
  constructor() {}
}

App Component ts

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

import {MessageService} from './message.service';
import {Message} from './message';
import {MESSAGE_TYPE} from './messageType';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular';
  
  public MESSAGE_TYPE = MESSAGE_TYPE;

  public messages: Message[] = [];
  public selectedType: MESSAGE_TYPE = MESSAGE_TYPE.GENERAL;

  public constructor(public messageService: MessageService) {}

  public ngOnInit() {
    this.goAndFilter();
  }

  public filterMessages(type: MESSAGE_TYPE) {
    this.selectedType = type;
    this.goAndFilter();
  }

  private goAndFilter(): void {
    this.messages = this.messageService.messages.filter(_ => _.type === this.selectedType);
  }
}

App Component html

<p>Selected type: {{selectedType}}<P>

<Button 
  (click)="filterMessages(MESSAGE_TYPE.COMBAT)"
>Show Combat</Button>
<Button 
  (click)="filterMessages(MESSAGE_TYPE.GENERAL)"
>Show General</Button>

<div *ngFor="let message of messages">
  <p>{{message.message}} - {{message.type}}</p>
</div>

Feel free to check out the working demo on StackBlitz: here.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

searchByTextContentUnderListItemAnchorTag

I would like to utilize the getByRole function for writing my test. However, I am encountering issues when using linkitem or 'link' as the role. It seems that I cannot find the desired element. // encountered error TestingLibraryElementError: The ...

Show the values in the second dropdown menu according to the selection made in the first dropdown menu using Angular 8

My goal is to retrieve data and populate two dropdowns based on user selection. However, the code I've written isn't giving me the desired output and instead, errors are occurring. Being new to Angular, I would appreciate a review of my code. Her ...

TypeScript: Type narrowing issue when deconstructing an array within a function

I am working with an object that has a const assertion: const foo = { bar: ['a', 'b'], } as const; My objective is to create a function that can update the bar array and accurately infer the new type. I have successfully achieved th ...

What is the best way to arrange and manage assets?

I created a component with an image in it. The current file structure is like this: https://i.sstatic.net/hkZWG.png Now, my query is whether placing the assets folder inside the web folder is the right choice or not? UPDATE This is what I tried: impor ...

Having trouble getting Bootstrap 5 modal to function properly in an Angular project

I'm currently working on integrating a modal into my Angular project. I've followed the example in the Bootstrap documentation and have also watched tutorials that suggest changing data-toggle to data-bs-target, but it's still not functionin ...

I encountered an error while attempting to utilize a Reference Template variable or $event in Angular. Why is this happening?

[Check out this Header.component.html fileI encountered an error while passing a function parameter ...

DuplicateModelError: Unable to duplicate model after it has been compiled, React.js, MongoDB, TypeScript

In the early stages of developing an application using Next.js, Mongoose, and Typescript, I encountered a persistent issue. Whenever I attempt to send a request through Postman after clicking save, it fails, displaying the error message: OverwriteModelErr ...

Guide to Implementing StoreApi in Zustand LibraryLearn how to utilize Store

While reading the documentation for zustand, I came across a useful piece of information. In addition to the standard `set` and `get` parameters, there is an extra parameter called `api` in the StateCreator function. Check out the example below: import cr ...

The Angular application shell build has produced two new folders within the dist directory. I'm curious about their purpose and how they can

Whenever I execute the ng run my-app:app-shell -c production command, it creates 2 folders within the dist directory - one for the browser and one for the server. Now, I find myself with 2 different commands to use: npm build --prod (for a regular build) ...

Setting up VSCode to run various tasks

My TypeScript project in Visual Studio Code has a specific task outlined as follows: { "version": "0.1.0", // The command is tsc. "command": "tsc", // Show the output window only if unrecognized errors occur. "showOutput": "silent", // Und ...

Error encountered during Jasmine unit testing for the ng-redux @select directive

Here is a snippet from my component.ts file: import { Component, OnInit } from '@angular/core'; import { select } from 'ng2-redux'; import { Observable } from 'rxjs/Observable'; import { PersonalDetailsComponent } from ' ...

Tips for customizing components in React-Table by overriding default columns

In a nutshell, I was tasked with developing a table component using react-table. By default, the table uses an input component that allows instant typing when double-clicked. Additionally, I wanted one of the columns in editableCell to use a dropdown. I ...

What is the best way to outline this model using typescript?

Here is a JSON model that I am working with: { "loggers" : { "logger1" : { "name" : "logger1", "level" : "DEBUG", "sub_loggers" :{ "logger1.nested_logger1" : { "name": "lo ...

Using the Markdown attribute in this context prevents the translate pipe from translating the string

I have a paragraph within an Angular component that includes a markdown attribute. Occasionally, the markdown is causing the translation pipe to not translate the title.description string: <p class="someClass" markdown>{{tile.description | ...

Using React to simulate API calls outside of testing environments

For instance, I encounter issues when certain endpoints are inaccessible or causing errors, but I still need to continue developing. An example scenario is with a function like UserService.getUsers where I want to use fake data that I can define myself. I ...

Nest is unable to resolve DataSource in the dependency injection

I've encountered an issue with dependencies while working on my NestJS project. When I try to launch the app, the compiler throws this error at me: [Nest] 16004 - 09.04.2022, 16:14:46 ERROR [ExceptionHandler] Nest can't resolve dependencies of ...

Trigger a modal from one sibling Angular component to another

My application utilizes an Angular6 component architecture with the following components: <app-navbar></app-navbar> <app-dashboard></app-dashboard> The Dashboard component consists of: <app-meseros> </app-meseros> < ...

How to efficiently trigger service functions in Angular2 with @ngrx/store/effects?

Currently, I am working on developing an Angular 2 application using @ngrx/store and @ngrx/effects. However, I am facing challenges in determining the appropriate placement of logic outside of actions/effects and when to invoke service functions. For inst ...

Displaying an array assigned within an Angular class

I'm facing an issue displaying a list of content using an array in Angular. Despite the app functioning properly, no data seems to be returned. Other apps are loading fine. Although the service has been added to the app.module.ts file, there are no e ...

Having trouble with my Angular subscription - not behaving as I anticipated

I am facing an issue on my shop page where I have a FilterBarComponent as a child component. On initialization, I want it to emit all the categories so that all products are rendered by default. However, on my HomePageComponent, there is a button that allo ...