How to make injected service reactive in Angular2-

2019-07-24 03:55发布

问题:

I am new to Angular2 and am trying to get a custom Component to use a Service which manages a registry of Mongo.Collection objects. My problem is that I cannot seem to get Angular to refresh the UI when the Collection has loaded.

If you take a look at the code below you will see that there is a Component which has a DatasetService injected. This service manages a registry of Collections. I am guessing that the problem is related to the fact that the Meteor.autorun() method takes the processing out of the Angular 'zone' & I need to somehow re-inject the process into the Angular zone/digest?

Both client & server:

export const Meetings = new Mongo.Collection<any>('meetings');

Server only

Meteor.publish('meetings', function(options: any) {
  return Meetings.find({});
});

Client only

class Dataset {
  cursor: Mongo.Cursor<any>;
  loading: boolean = true;

  constructor( public collection: Mongo.Collection<any> ) {
    Tracker.autorun(() => {
      this.loading = true;
      Meteor.subscribe('meetings', {}, () => {
        this.cursor = this.collection.find({});
        this.loading = false;
      });
    });
  }
}

@Injectable()
class DatasetService {
  datasets: Dataset[] = [];

  register( id: string, collection: Mongo.Collection<any> ) : Dataset {
    return this.datasets[id] = new Dataset( collection, options );
  }
}

@Component({
  selector: 'meetings-list',
  template: `<ul><li *ngFor="let meeting of meetings.cursor">{{meeting.name}}</li></ul>`,
  viewProviders: [DatasetService]
})
class MeetingsListComponent extends MeteorComponent implements OnInit {
  meetings: Dataset;

  constructor(private datasetService: DatasetService) {
    super();
  }

  ngOnInit() {
    this.meetings = this.datasetService.register( 'meetings', Meetings );
  }

  checkState() {
    console.log(this.loading);
  }
}

If I load the page the list of meetings does not appear. However, if I manually call 'checkState()' by clicking on a button it then refreshes the UI & renders the meetings.

Any helps, clarity or an alternative way to achieve what I'm trying to do would be much appreciated!

回答1:

You are correct. The code that triggers the change is coming in from outside the Angular2 Zone, so you need to push it into the zone to execute the change. To do that, you need to inject the NgZone into your service. Then you probably need to pass it on to the Dataset instance, since that is where the code that should trigger the update is located. For example, this may work:

import { Injectable, NgZone, Inject } from '@angular/core';

class Dataset {
  cursor: Mongo.Cursor<any>;
  loading: boolean = true;

  constructor( public collection: Mongo.Collection<any>, zone: NgZone ) {
    Tracker.autorun(() => {
      this.loading = true;
      Meteor.subscribe('meetings', {}, () => {
        zone.run(() => {  //<-- new line
          this.cursor = this.collection.find({});
          this.loading = false;
        });               //<-- end of new line
      });
    });
   }
}

@Injectable()
class DatasetService {
  datasets: Dataset[] = [];
  private zone: NgZone;

  // Inject Here
  constructor(@Inject(NgZone)zone: NgZone) { this.zone = zone; }

  register( id: string, collection: Mongo.Collection<any> ) : Dataset {
    //Add zone to Dataset constructor, 
    // not sure before or after options (not sure where options comes from or goes
    return this.datasets[id] = new Dataset( collection, this.zone, options );
  }
}