3
votes

I have a component on my root page that exposes a few bindable properties and can be used like this:

<myprogress [value]="current" [max]="maximum"></myprogress>

In my application, various types objects require progress display. So I created some directives like below:

@Directive({
  selector: 'myprogress[order]'
})
export class OrderProgress {
  @Input('order') order: Order;
  constructor(private baseComponent: Progress) {}
  ngOnInit() {
    this.baseComponent.value = this.order.currentStage;
    this.baseComponent.max   = this.order.totalStages;
  }
}

This allows me to replace calls like:

<myprogress [value]="order.currentStage" [max]="order.totalStages"></myprogress>

with a more readable:

<myprogress [order]="order"></myprogress>

This works fine until some part of the application changes the order itself. In that case, myprogress components which have already been rendered, are not updated. And I can see why too. The ngOnInit function runs just once when the component is rendered and there is no way to indicate to the directive that some property of the order has changed and new values for value and max need to be calculated.

Is there a way to solve this problem?

One possible solution I have is this:

  1. Modify Progress component to accept numbers as well as a function that returns a number.
  2. In OrderProgress's ngOnInit, bind as follows: this.baseComponent.value = () => this.order.currentStage

But it requires me to change Progress component as well. Is there a way which doesn't require me to change Progress or Order?

2
I see the answers with Subject, if I understand you clearly. You want the changes in parent components order reflected in the child directive. Wouldnt change detection change value of order in child directive when value of order is mutated in parent ?raj
I'm sorry I didn't quite comprehend what you asked. Could you rephrase your question a bit?AweSIM

2 Answers

1
votes

It sounds like you need to implement an observable over the order object. If you're sharing that object across multiple components and need to have those components be made aware of changes to the order object, it's best to create an Observable. We use the BehavoirSubject class from rxjs to do just that.

RxJs BehaviorSubject

Here is and example of how you can create the BehavoirSubject:

export class TokenService {

    public tokenSubject: BehaviorSubject<OpenIdToken> = new BehaviorSubject<OpenIdToken>(<OpenIdToken>{});

OpenIdToken is a custom typescript class that i want to share across my app.

Here is how you notifiy subscribers of a change to the underlying object:

setObservableToken(token: OpenIdToken): void {
        if (token) {
            this.tokenSubject.next(token);
        }
    }

after .next is called the new token object value will propigate to all subscribers. Here is how you subscribe to the BehaviorSubject:

export class PatientComponent implements OnInit, OnDestroy {    

    patient: Patient;

    constructor(private _tokenService: TokenService, private _patientService: PatientService) { }

    ngOnInit() {
        this._tokenService.tokenSubject.subscribe(token => {
            if (token && token.access_token && token.access_token.length>0) {
                this._patientService.getPatient(token)
                    .subscribe(_patient => this.patient = _patient);
            }
        });
    }

subscribe is a delegate that will execute when the subscription broadcasts a change to the token( when it calls .next(token)). In this case when i get an OpenIdToken I want to fetch the patient using the token so I call another service.

1
votes

The order type must be a Subject or BehaviorSubject so when you update the order the template of myprogress should update as well. You will need to use the async pipe to subscribe to the observable.

<myprogress [order]="order$ | async"></myprogress>

Check this code. You will need to call this.newOrder({...}); to update the order.

@Component({
  selector: 'page-home',
  template: `
    <ion-header>
      <ion-navbar>
        <ion-title>Home</ion-title>
      </ion-navbar>
    </ion-header>

    <ion-content padding>
       <myprogress [order]="order$ | async"></myprogress> 
    </ion-content>
 `
})
export class HomePage implements OnInit,OnDestroy{

  order$: Subject<any> = new Subject();

  constructor(public navCtrl: NavController){
  }

  ngOnInit(){
  }

  newOrder(order : any){
    this.order$.next(order);
  }

  ngOnDestroy(){
    this.order$.unsubscribe();
  }

}