6

We were able to apply $watch on complex object, how to do the similar in Angular 2.

Angular 1

$scope.data = {name : "somvalue"}
$scope.$watch('data.name', function(newValue, oldValue) {
    scope.counter = scope.counter + 1;
});

Angular 2

export class MyData{
   name: string;
} 

export class MyComponent implements OnInit {
   @Input() data: MyData;

   constructor(private ds: MyService){
      this.data = ds.data;
   }

   // $watch('data.name', function(newValue, oldValue) {
   //   scope.counter = scope.counter + 1;
   // });
}

Now If data.name changes in service, How to watch for the changes in Component itself, Please note data is not an observable it is just a regular object.

Update

Please see Plunk for an example

Thanks in advance!!

johnny 5
  • 19,893
  • 50
  • 121
  • 195
Madhu Ranjan
  • 17,334
  • 7
  • 60
  • 69
  • `$watch` and `$digest` have no direct equivelents in nG2. However, checkout this other SO post about the topic http://stackoverflow.com/questions/34569094/what-is-the-angular2-equivalent-to-an-angularjs-watch – Pytth Jun 17 '16 at 19:24

4 Answers4

3

Angular checks properties, even deep inside objects if they are bound to in the template.

For complex objects the preferred option is to use Observable to actively notify Angular2 about changes.

You can also use custom change detection by implementing DoCheck

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Do you have some reference code how to create observable to achieve this? I know how an API call results in an observable, But how to do the same for a normal class object? I have created a Plunk to explain the scenario, can Observable fit in this situation? – Madhu Ranjan Jun 20 '16 at 17:05
  • https://angular.io/docs/ts/latest/cookbook/component-communication.html – Günter Zöchbauer Jun 20 '16 at 17:07
  • To add, I have a scenario where the property I want to observe is not directly bound on UI, But will be responsible for changes to UI. – Madhu Ranjan Jun 20 '16 at 17:07
  • You can subscribe imperatively to observables and update when an event is emitted. `Observable` is all about push to actively get notified about changes instead of polling. Angular2 did a great job on reducing the load caused by change detection but observables allow to reduce this further. – Günter Zöchbauer Jun 20 '16 at 17:09
0

Implement the component lifecycle hook "ngOnChanges":

import { OnChanges } from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <h2>Child component</h2>
        {{ person }}
    `
})
class ChildComponent implements OnChanges {
    @Input() person: string;

    ngOnChanges(changes: {[ propName: string]: SimpleChange}) {
        console.log('Change detected:', changes[person].currentValue);
    }

}

UPDATE

I found a possible workaround for this. Implement the DoCheck hook instead OnChanges.

ngDoCheck() {
   if(!this.inputSettings.equals(this.previousInputSettings)) {
       // inputSettings changed
       // some logic here to react to the change
       this.previousInputSettings = this.inputSettings;
   }
}

Remember that doCheck runs many times, and can cause performance issues if missused.

  • if the object is bound on UI the changes will reflect , but there is no ngOnChanges fired on just property change, I have added a Plunk in my question, it explains the scenaron, If you update the property ngOnChanges does not fire if you replace the entire object it will fired, you can see it in console log and clicking on respective buttons. – Madhu Ranjan Jun 20 '16 at 17:01
  • Ok! You need to use a direct reference for that property to work: See: http://victorsavkin.com/post/133936129316/angular-immutability-and-encapsulation – Rodolfo Jorge Nemer Nogueira Jun 21 '16 at 00:04
  • yes I know that as mentioned in above comment also, I have a scenario where the property I want to observe is not directly bound on UI, But will be responsible for changes to UI. – Madhu Ranjan Jun 21 '16 at 02:19
0

In general , your component will listen to all of it's object mutation, unless you use a different change detection strategy.

import { OnChanges } from '@angular/core';

@Component({
    selector: 'child',
    template: `
        <h2>Child component</h2>
        {{ person }}
    `,
   changeDetection:ChangeDetectionStrategy.OnPush/Default/CheckOnce/Always
})
class ChildComponent implements OnChanges {
    @Input() person: string;

    ngOnChanges(changes: {[ propName: string]: SimpleChange}) {
        console.log('Change detected:', changes[person].currentValue);
    }

This :

   changeDetection:ChangeDetectionStrategy.OnPush/Default/CheckOnce/Always

Will define how your component will behave when one of it's inputs gets updated.

Most important one is OnPush and Default .

Where OnPush says update the component only if the whole object has got replace with a new one and Default says update me if any of the nested values has got updated( mutated).

And, if you don't use that object inside your component, Angular will ignore and won't update the view ( why would it).

And then you can easily hook to ngOnChange life cycle hook and as the other answer suggest get the update.

Milad
  • 27,506
  • 11
  • 76
  • 85
  • `CheckOnce` and `Default` are the only options to stay public. The other options are for Angular2-internal use only and are not supposed to be used by developers and will be move to private enums eventually. – Günter Zöchbauer Jun 20 '16 at 16:56
  • if the object is bound on UI the changes will reflect , but there is no ngOnChanges fired on just property change, I have added a Plunk in my question, it explains the scenaron, If you update the property ngOnChanges does not fire if you replace the entire object it will fired, you can see it in console log and clicking on respective buttons. – Madhu Ranjan Jun 20 '16 at 17:01
0

It's an alternative solution for DoCheck if you want to have more customization and do something more manual. Of course, according to Günter Zöchbauer answer it's a kind of Observable solution.

I use a decorator to tell TS that property must be detected, so let's get start it.

export class ChangeDetection {
  private emitedChangeDetector: ChangeDetection;

  private _value;
  get value() {
    return this._value;
  }

  /** @warning Don't use it */
  __action: (value: any) => void;

  /** @warning Don't use it */
  __change(value: any) {
    this._value = value;
    this.__action && this.__action(value);
    this.emitedChangeDetector?.__change &&
      this.emitedChangeDetector.__change(this.emitedChangeDetector.value);
  }

  onChange<T = any>(action: (value: T) => void) {
    this.__action = action;

    return this;
  }

  emit(extendedObserver: ChangeDetection) {
    this.emitedChangeDetector = extendedObserver;
  }
}

// For manage list of changes
export class ChangeDetectionList {
  changeDetectors: ChangeDetection[];

  constructor(...changeDetectors: ChangeDetection[]) {
    this.changeDetectors = changeDetectors;
  }

  onChange(callback: (data: any) => void): ChangeDetectionList {
    this.changeDetectors.forEach((a: ChangeDetection) => a.onChange(callback));

    return this;
  }

  emit(changeDetector: ChangeDetection): ChangeDetection {
    this.changeDetectors.forEach((a: ChangeDetection) =>
      a.emit(changeDetector)
    );

    return changeDetector;
  }
}

/**
 * @usageNotes{
 * ```typescript
 * @ChangeDetector()
 * data : string
 * ```
 * Gives you a ChangeDetection object with the name of data$ that fires "onChange()" function when "data" is changed
 */
export function ChangeDetector(suffix: string = "$") {
  return function (prototype: any, key: string | symbol) {
    const changeDetectorName: string = `${key.toString()}${suffix}`;
    if (!prototype[changeDetectorName]) {
      Object.defineProperty(prototype, changeDetectorName, {
        value: new ChangeDetection(),
      });

      const changeDetectorObject = prototype[changeDetectorName] as ChangeDetection;
      Object.defineProperty(prototype, key, {
        set(value: any) {
          Object.defineProperty(this, key, {
            get() {
              return changeDetectorObject.value;
            },
            set(v: any) {
              changeDetectorObject.__change(v);
            },
            enumerable: true,
          });
          this[key] = value;
        },
        enumerable: true,
        configurable: true,
      });
    }
  };
}

Now, It's easy to use. Just put @ChangeDetector() decorator on each property what you want to detect or define a changeDetector property to raise onChange event when a property changes.

Here is an example show you how to use it.

export class PersonModel {
  @ChangeDetector()
  changeDetector: number;
  changeDetector$: ChangeDetection; // It's just for intellisense, you can ignore it.

  firstName: string;
  lastName: string;
  age: number;

  constructor() {
    this.age = 34;
    this.changeDetector = 0;
  }
}

export class ProfileModel {
  @ChangeDetector('Listener')
  person: PersonModel;
  personListener: ChangeDetection; // It's just for intellisense, you can ignore it.

  constructor() {
    this.person = new PersonModel();
  }
}

export class ProfileService {
  profile: ProfileModel;

  constructor() {
    this.profile = new ProfileModel();
    // calls 'profileChanged()' when 'person' is changed
    this.profile.personListener.onChange((_) => this.profileChanged());
  }

  setProfile() {
    Object.assign(this.profile.person, { firstName: 'Pedram', lastName: 'Ahmadpour' });
    this.profile.person.changeDetector++;
  }

  private profileChanged() {
    console.log('profile is', JSON.stringify(this.profile));
  }
}

export class ProfileComponent {
  constructor(private profileService: ProfileService) {
    // Now, profileService.profile listen to its own child changes
    this.profileService.profile.person.changeDetector$.emit(profileService.profile.personListener);
    this.profileService.setProfile();
  }

  happyBirthday() {
    this.profileService.profile.person.age = 35;
    console.log('Happy birthday');
    this.profileService.profile.person.changeDetector++;
  }
}

Or in real Angular project:

  @Input()
  @ChangeDetector()
  data: ProfileModel;
  data$: ChangeDetection;

And for the test:

const profileService = new ProfileService();
const profileComponent = new ProfileComponent(profileService);
profileComponent.happyBirthday();