Monitoring Object Changes in Angular 4

ETA: I am aware of different methods for monitoring my form for alterations. However, that is not the focus of my inquiry. As indicated by the title, my question pertains to observing changes within an object. The application displayed below is solely for demonstration purposes. Kindly provide a response to the specific query I have posed. Thank you!

Presented here is a straightforward app:

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

export class Customer {
    firstName: string;
    favoriteColor: string;
}

@Component({
    selector: 'my-app',
    template: `
        <div *ngIf="customer">
            <input type="text" [(ngModel)]="customer.firstName">
            <input type="text" [(ngModel)]="customer.favoriteColor">
        </div>
        `
})
export class AppComponent implements OnInit {

    private customer: Customer;

    ngOnInit(): void {

        this.customer = new Customer();

        // TODO: How can I establish a callback function that triggers whenever
        // any property of this.customer undergoes modification?

    }

}

Note the pending task. I am seeking to create a subscription mechanism that activates each time any property of this.customer is altered.

I am unable to utilize ngChange on the input elements. My requirement is to directly monitor changes within the model. The rationales behind this decision are specific to my project and will not be elaborated upon here. Please understand that this limitation is non-negotiable.

Is there a solution to this? Despite extensive online research efforts, I have yet to find a suitable answer.

Answer №1

Angular typically utilizes the injected KeyValueDiffers class in the constructor.

For your specific scenario, the code might resemble this:

import { KeyValueChanges, KeyValueDiffer, KeyValueDiffers } from '@angular/core';

export class Customer {
  firstName: string;
  favoriteColor: string;
}

@Component({
  selector: 'my-app',
  templateUrl: `./app.component.html`
})
export class AppComponent {
  private customerDiffer: KeyValueDiffer<string, any>;
  private customer: Customer;

  constructor(private differs: KeyValueDiffers) {}

  ngOnInit(): void {
    this.customer = new Customer();
    this.customerDiffer = this.differs.find(this.customer).create();
  }

  customerChanged(changes: KeyValueChanges<string, any>) {
    console.log('changes');
    /* If you want to see details then use
      changes.forEachRemovedItem((record) => ...);
      changes.forEachAddedItem((record) => ...);
      changes.forEachChangedItem((record) => ...);
    */
  }

  ngDoCheck(): void {
      const changes = this.customerDiffer.diff(this.customer);
      if (changes) {
        this.customerChanged(changes);
      }
  }
}

Example on Stackblitz

Another approach is to use setters on properties that require checking.

Further reading:

  • Custom Differ Performance Tips

Answer №2

I am looking to directly subscribe to any changes on the model.

To achieve this, you can listen for model changes using the ngModelChange event binding.

Template:

<input type="text" (ngModelChange)="doSomething($event)" [ngModel]="customer.firstName">

Class:

doSomething(event) {
  console.log(event); // logs the updated model value
}

Check out the live demonstration here!

Answer №3

Monitoring changes within an object is not possible in this scenario. This is not Angular 1, so traditional watchers are not utilized here. An alternative approach would involve using observables.

Utilize the following form structure:

<form #f="ngForm">
  <input type="text" name="firstName" [(ngModel)]="customer.firstName">
  <input type="text" name="favoriteColor" [(ngModel)]="customer.favoriteColor">
</form>

The corresponding code snippet would be as follows:

@ViewChild('f') f;

ngAfterViewInit() {
  this.f.form.valueChanges.subscribe((change) => {
   console.log(change)
  })
}

Answer №4

To implement a callback mechanism, custom setters can be used:

class Client {
  private _name: string
  get name(): string {
    return this._name
  }
  set name(name: string) {
    this.callback(this._name, name)
    this._name = name
  }

  private _email: string
  get email(): string {
    return this._email
  }
  set email(email: string) {
    this.callback(this._email, email)
    this._email = email
  }

  callback: (oldValue, newValue) => void

  constructor(callback?: (oldValue, newValue) => void) {
    // Default to an empty function if no callback is provided
    this.callback = callback || (() => {})
  }
}

Simply assign the callback when creating the object:

this.client = new Client((oldValue, newValue) => console.log(oldValue, newValue))

// or

this.client = new Client()
this.client.callback = (oldValue, newValue) => console.log(oldValue, newValue)

Answer №5

Check out https://github.com/cartant/rxjs-observe for more information. It utilizes rxjs and the proxy pattern.

import { observe } from "rxjs-observe";

const person = { name: "Alice" };
const { observables, proxy } = observe(person);
observables.name.subscribe(value => console.log(name));
proxy.name = "Bob";

Answer №6

We have been given the challenge of upgrading a legacy Angular 1.x application to Angular 9. One key feature of this app is its integration with ESRI maps, thanks to the robust tools provided by the ESRI framework. The watchUtils from ESRI offer extensive functionality beyond just monitoring changes.

While ESRI's tools are impressive, there's one thing I miss from Angular 1 - the simplicity of $watch. Additionally, in our application, we deal with Entities and Models that require occasional observation.

To tackle this, I devised an abstract class named MappedPropertyClass. This class utilizes a Map for mapping class properties, making it easy to implement functions like toJSON efficiently.

One notable map within this class is _propertyChangeMap: a Map>;

Another valuable function we introduced is $watch, which accepts a string and a callback function for operations.

The versatility of this class allows for extension by Entities, components, or services.

I'm open to sharing my work; the only requirement is that your properties adhere to a specific format:

public get foo(): string {
    return this._get("foo");
}  
public set foo(value:string) {
    this._set("foo", value);
}

--------------------------------------------------------------------
import { EventEmitter } from '@angular/core';

export abstract class MappedPropertyClass {
    private _properties: Map<string, any>;
    private _propertyChangeMap: Map<string, EventEmitter<{ newvalue, oldvalue }>>;

    protected _set(propertyName: string, propertyValue: any) {
        let oldValue = this._get(propertyName);
        this._properties.set(propertyName, propertyValue);
        this.getPropertyChangeEmitter(propertyName).emit({ newvalue: 
    propertyValue, oldvalue: oldValue });
    }

    protected _get(propertyName: string): any {
        if (!this._properties.has(propertyName)) {
            this._properties.set(propertyName, undefined);
        } 
        return this._properties.get(propertyName);
    }

    protected get properties(): Map<string, any> {
        var props = new Map<string, any>();
        for (let key of this._properties.keys()) {
            props.set(key, this._properties.get(key));
        }

        return props;
    }

    protected constructor() {
        this._properties = new Map<string, any>();
        this._propertyChangeMap = new Map<string, EventEmitter<{ newvalue: any, 
        oldvalue: any }>>();
    }

    private getPropertyChangeEmitter(propertyName: string): EventEmitter<{ 
                         newvalue, oldvalue }> {
        if (!this._propertyChangeMap.has(propertyName)) {
            this._propertyChangeMap.set(propertyName, new EventEmitter<{ newvalue, 
            oldvalue }>());
        }
        return this._propertyChangeMap.get(propertyName);
    }

    public $watch(propertyName: string, callback: (newvalue, oldvalue) => void): 
    any {
        return this.getPropertyChangeEmitter(propertyName).subscribe((results) => 
        {
            callback(results.newvalue, results.oldvalue);
        });
    }
}

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

What is the method for executing a for loop in AngularJS without relying on HTML elements?

I'm creating an application in AngularJS where the structure requires me to execute a for each loop without using any HTML elements or div tags with ng-repeat. In Django, we typically use the following syntax: {% for item in list %} {{ item }} {% ...

What is the best way to implement registry and login functionality using Angular?

I am currently facing an issue where I can log in and register with blank data. However, I need to figure out how to code a registration form that requires the "@" symbol to be present in the email input field. The programming language I am using is Angu ...

What could be causing the issue of console.log() being called multiple times in Angular when invoking a method through text interpolation?

Utilizing Text interpolation to invoke a method. home.component.html <p>{{myMethod()}}</p> home.component.ts import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-home', templateUrl: './h ...

Obtaining your CSGO inventory via Steam API using jsonp: A step-by-step guide

Hey there! I'm currently facing an issue while trying to access a player's CSGO inventory using Valve's API. Unfortunately, I keep running into the error message stating that no 'access-control-allow-origin' header is present on th ...

Are Twitter Bootstrap buttons compatible with iPad devices?

Have you ever tried using the attractive buttons provided by Twitter? They can be a great alternative to standard radio/checkbox options. If you have used them in your production, how effective were they? I am particularly curious about their compatibilit ...

Retrieving results from PostgreSQL database using pagination technique

When I'm pagination querying my data from a PostgreSQL database, each request involves fetching the data in this manner: let lastNArticles: Article[] = await Article.findAll({ limit: +req.body.count * +req.body.page, or ...

What is the best way to modify the text color within a Navbar, especially when the Navbar is displayed within a separate component?

First question on StackOverflow. I have a Next App and the Navbar is being imported in the _app.js file. import Navbar from "../Components/Navbar"; function MyApp({ Component, pageProps }) { return ( <> <Navbar /> ...

Guide on converting a JSON containing nested JSONs into an HTML table

Currently, I am working with a JSON data structure that contains nested dictionaries. My goal is to create an HTML table where each top-level key serves as a column in the table. The inner key-value pairs should be represented as a single text within cells ...

Adding an operation to a function that is linked to an event

On one of my pages, I have a button that triggers some ajax calls with data binding. The binding code is generic and used in various parts of the application, so altering its behavior could impact other users. However, I now have a specific requirement fo ...

What is the best way to specify a submission destination for a custom form component in Vue?

I am looking to streamline the use of a form component across my website, however, I need the submit button to perform different actions depending on which page calls the form-component. Experimenting with Vue components and data passing is new to me as I ...

Automatically insert a hyphen (-) between each set of 10 digits in a phone number as it is being typed into the text

I'm attempting to automatically insert a hyphen (-) in between 10 digits of a phone number as it is being typed into the text field, following North American standard formatting. I want the output to look like this: 647-364-3975 I am using the keyup ...

Is there a way to automatically start and reload index.js in Node.js when I make changes to my Angular frontend?

I'm feeling a bit lost when it comes to making changes to my package.json file. I attempted to use "watch" in the package.json scripts, but unfortunately, it didn't have the desired effect. I am new to Angular and Nodejs, so any guidance would be ...

The issue with VueEditor in Nuxt.js is that it's throwing an error

When working on my Nuxt.js project, I integrated the vue2-editor package for writing articles with HTML. Everything functions properly when I enter text and submit it, however, upon reloading the page, I encounter an error stating "document is not defined" ...

I'm curious if there is a method in Vue.js that allows for creating a separator while utilizing v-for, much like the way `Array.join()` or FreeMarker's `<#sep>` functions operate

My goal is to create a list of <span> elements separated by commas using vue.js and v-for. Is there an easy way to achieve this in vue.js? In JavaScript, I would typically use users.join(", "). In FreeMarker templates, you can do some very interes ...

Using form.submit() alongside preventDefault() will not yield the desired outcome

My login form needs to either close the lightbox or update the error box based on the success of the login attempt. I suspect the issue lies with the onclick="formhash(this.form, this.form.password);" which is a JavaScript function that hashes a passwor ...

What is the best way to retrieve AJAX post data from the request in Symfony2?

My goal is to efficiently upload a file using jquery-file-upload (blueimp) and include some data in a cross-domain environment. To achieve this, I have utilized code from the basic.html file provided by jqueryfileupload on the client side. Additionally, I ...

Waiting for asynchronous subscriptions with RxJS Subjects in TypeScript is essential for handling data streams efficiently

Imagine having two completely separate sections of code in two unrelated classes that are both listening to the same Observable from a service class. class MyService { private readonly subject = new Subject<any>(); public observe(): Observable&l ...

Issues with IonPicker displaying incorrect values

In my Ionic App, I am using the ion-picker component to display the selected text from an array of options. Although everything seems to be working fine and the console.log accurately displays the index value, there is a strange behavior when it comes to s ...

Vitest's behavior shows variance when compared to compiled JavaScript

Challenge Currently, I am developing a package that relies on decorators to initialize class object properties. The specific decorator is expected to set the name property of the class. // index.ts const Property = (_target: any, key: any) => { } cl ...

Ensure that the page is fully loaded before proceeding with the redirection

I have a webpage that currently performs a small task before redirecting to a third-party site. The redirection is initiated by using Response.Redirect(); Now, I am looking to integrate another function using JavaScript this time. To add the JavaScript ...