How to notify parent component of change and refresh view

隐身守侯 提交于 2019-12-11 17:07:23

问题


I want to notify a parent component from a child component to update a view in the parent component. I'm using the @Output annotation to do that.

In the parent component the function "loadPosts()" is actually invoked, but the view is not updated. Does anybody know why?

What happens:

  • place_component contains a "list-post" directive, which displays all posts.
  • place_component contains a modal, to add a new post with a directive "new-post"
  • When a new post is saved, a message is parsed back over @output to the "new-post" directive in the modal: (doneIt)="loadPosts()"
  • The loadPosts() function is executed, but the "list-post" directive does not reload.

Parent Component:

place_component.dart:

@Component(
  selector: 'my-place',
  directives: [coreDirectives, 
                formDirectives, 
                PostNewComponent, 
                PostListComponent,
                MaterialButtonComponent,
                MaterialDialogComponent,
                ModalComponent,
                MaterialTabPanelComponent,
                MaterialTabComponent],
  templateUrl: 'place_component.html',
  styleUrls: ['place_component.css'],
  providers: [materialProviders]
)
class PlaceComponent implements OnActivate, OnInit {
  Place place;

  final PlaceService _placeService;
  final Location _location;
  final ChangeDetectorRef cdRef;

  int _id;

  bool showBasicDialog = false;

  final tabLabels = const <String>[
    'Posts',
    'Pictures',
    'Pending Invitations'
  ];

  PlaceComponent(this._placeService, this._location, this.cdRef);

  @override
  Future<void> onActivate(_, RouterState current) async {
    _id = paths.getId(current.parameters);
    loadPosts();
  }

  @override
  Future<void> ngOnInit() async {
    print("init executed");
  }

  Future<void> loadPosts() async {
    if (_id != null) place = await (_placeService.get(_id));
    cdRef.detectChanges();
    print("loaded posts $_id");
  }

  void goBack() => _location.back(); 

  Future<void> save() async {
    await _placeService.update(place);
    goBack();
  }
}

place_component.html:

<div *ngIf="place != null">
    <h2>{{place.name}}</h2>
    <div class="grid">
      <div class="col-1-3">
        <div class="module">
          <material-button class="open-post-button" (trigger)="showBasicDialog = true" [disabled]="showBasicDialog" raised>
            New Post
          </material-button>
        </div>
      </div>
      <div class="col-2-3">
        <div class="module">
          <material-tab-panel class="tab-panel" [activeTabIndex]="0">
            <material-tab label="Posts">
              <div class="posts">
                <div class="post">
                  <list-posts [place]="place"></list-posts>
                </div>
              </div>
            </material-tab>
            <material-tab label="Pictures">
              Pictures
            </material-tab>
            <material-tab label="Videos">
              Videos
            </material-tab>            
          </material-tab-panel>
          <div class="divider10"></div>
        </div>
      </div>
    </div>  
</div>
<modal [visible]="showBasicDialog">
    <material-dialog class="basic-dialog">

      <h1 header>New Post</h1>

      <div class="new-post">
          <new-post (doneIt)="loadPosts()" [place]="place"></new-post>
      </div>

      <div footer>
        <material-button autoFocus clear-size (trigger)="showBasicDialog = false" class="close-button">
          Close
        </material-button>
      </div>

    </material-dialog>
  </modal>

Child Component

post_new_component.dart:

@Component(
  selector: 'new-post',
  directives: [coreDirectives, 
                formDirectives,
                FileUploader,
                materialInputDirectives,
                MaterialButtonComponent],
  templateUrl: 'post_new_component.html',
  styleUrls: ['post_new_component.css'],
  providers: [ClassProvider(PostService)]
)
class PostNewComponent {
  final PostService _postService;
  final _onDone = new StreamController.broadcast();

  String postText;
  Post post;

  @Input()
  Place place;

  @Output()
  Stream get doneIt => _onDone.stream;

  PostNewComponent(this._postService);

  Future<void> save() async {
    await _postService.create(postText,place.id).then(((_) => _onDone.add(1)));
  }
}

post_new_component.html:

<div class="post-new-component">
    <div>
        <material-input floatingLabel
            multiline
            rows="2"
            maxRows="4"
            label="Add a new post here...." 
            [(ngModel)]="postText"
            class="post-text">
        </material-input>
    </div>
    <div class="post-buttons">
        <file-uploader class="file-uploader"></file-uploader>
        <div><material-button (trigger)="save()" raised class="send-button">Post</material-button></div>
    </div>
    <div class="clear-float"></div>
</div>

I have now additionally also tried with an EventBus according to this example: AngularDart: How do you pass an event from a Child component to a second level parent

  PlaceComponent(this._placeService, this._location, this._postEvent, this.cdRef) {
    _postEvent.onEventStream.listen((int id) => loadPosts().then((_){cdRef.markForCheck();}));
  }

The behaviour is exactly the same. The loadPosts function is executed, but the view is not loading.


回答1:


Sometimes Angular does not trigger change detection after async call, you need to force it using ChangeDetectorRef

final ChangeDetectorRef cdRef;

PlaceComponent(this.cdRef);

Future<void> loadPosts() async {
 if (_id != null) place = await (_placeService.get(_id));
 ////

 cdRef.markForCheck();

 // or 

 cdRef.detectChanges();

 /// actually I don't know what is the best here
}



回答2:


I had following setup:

  • Parent Component places_component
  • Child Component post_new_component
  • Child Component post_list_component

To solve my problem I had to send the event not to the Parent Component, but to the other child component. So @Output wouldn't have worked. I just hooked up the EventBus to the other child component.

So the html of the parent component in short looked like this:

...
<div><new-post [place]="place"></new-post></div>
<div><list-posts [place]="place"></list-posts></div>
...

So the child component new-post needs to notify the child component list-posts, that a new post has been added and list-posts should re-fetch all posts related to a place.

post_event.dart (Event Bus Service)

The Event Bus service is setup between the post_new_component and the post_list_component.

I'm passing an int to the Stream (int id), this is not important right now, as I only need to check, if an event has fired, you could also parse a string, an object or anything else, if you need to send data with the event.

@Injectable()
class PostEvent {
  final StreamController<int> _onEventStream = new StreamController.broadcast();
    Stream<int> onEventStream = null;

    static final PostEvent _singleton = new PostEvent._internal(); 

    factory PostEvent() {
         return _singleton;
    }

    PostEvent._internal() {
         onEventStream = _onEventStream.stream;
    }

    onEvent(int id) {
         _onEventStream.add(id);
    }
}

post_new_component.dart

After the post has been added, _postEvent.onEvent(1) is executed. As explained above "1" is not important, as I only want to know, if an event was fired.

@Component(
  selector: 'new-post',
  directives: [coreDirectives, 
                formDirectives,
                FileUploader,
                materialInputDirectives,
                MaterialButtonComponent],
  templateUrl: 'post_new_component.html',
  styleUrls: ['post_new_component.css'],
  providers: [ClassProvider(PostService), ClassProvider(PostEvent)]
)
class PostNewComponent {
  final PostService _postService;
  final PostEvent _postEvent;

  String postText;
  Post post;

  @Input()
  Place place;

  PostNewComponent(this._postService, this._postEvent);

  // Save a new post
  Future<void> save() async {
    // Create a new post and then fire a post event to notify the post list component to update itself.
    await _postService.create(postText,place.id).then(((_) => _postEvent.onEvent(1)));
  }
}

post_list_component.dart

I setup the event listener in the constructor of the component, that listens for event changes from the post-new component. Each time an event is received, I fetch all posts through the _getPosts() function.

@Component(
  selector: 'list-posts',
  directives: [coreDirectives, formDirectives],
  templateUrl: 'post_list_component.html',
  styleUrls: ['post_list_component.css'],
  providers: [ClassProvider(PostService), ClassProvider(PostEvent)]
)
class PostListComponent implements OnInit {
  final PostService _postService;
  final PostEvent _postEvent;
  List<Post> posts;

  @Input()
  Place place;

  PostListComponent(this._postService, this._postEvent) {
    // listen for postEvents, if received re-fetch posts
    _postEvent.onEventStream.listen((int id) => _getPosts());
  }

  // Get all posts when page loads the first time
  void ngOnInit() => _getPosts();

  // Function to get all the posts related to a place
  Future<void> _getPosts() async {
    posts = await _postService.getPostsByPlace(place.id);
  }
}

If anybody knows a better way to do it, by any means correct me, as I'm not that familiar with the framework yet, and have a hard time to understand the concepts. The documentation covers much, but if somebody is totally new to the framework, like me, I'm missing some info. An extension to the Hero Documentation would be appreciated, that covers the more complex topics, such as communication between child components, and child to parent, but not sure, if this is explained somewhere, and I just missed it.



来源:https://stackoverflow.com/questions/52295624/how-to-notify-parent-component-of-change-and-refresh-view

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!