如何通知父组件更改和刷新视图 [英] How to notify parent component of change and refresh view

查看:55
本文介绍了如何通知父组件更改和刷新视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想从子组件中通知父组件以更新父组件中的视图.我正在使用@Output注释执行此操作.

在父组件中,实际上调用了函数"loadPosts()",但视图未更新.有人知道为什么吗?

会发生什么:

  • place_component包含一个"list-post"指令,该指令显示所有帖子.
  • place_component包含一个模态,用于使用指令"new-post"添加新帖子
  • 保存新帖子时,将通过@output将消息解析回模式中的"new-post"指令:(doneIt)="loadPosts()"
  • 已执行loadPosts()函数,但不会重新加载"list-post"指令.

父组件:

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>

子组件

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>

我现在还根据此示例尝试了EventBus:解决方案

我进行了以下设置:

  • 父组件places_component
  • 子组件post_new_component
  • 子组件post_list_component

要解决我的问题,我必须将事件发送给父组件,而不是父组件.所以@Output不会起作用.我只是将EventBus连接到另一个子组件.

因此,父组件的html看起来像这样:

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

因此,子组件new-post需要通知子组件list-post,已经添加了新帖子,并且list-post应该重新获取与该地点相关的所有帖子.

post_event.dart(事件总线服务)

事件总线服务是在post_new_component和post_list_component之间设置的.

我正在将一个int传递给Stream(int id),这现在并不重要,因为我只需要检查是否触发了一个事件,就可以解析一个字符串,一个对象或其他任何东西,如果您需要随事件发送数据.

@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

添加帖子后,将执行_postEvent.onEvent(1).如上所述,"1"并不重要,我只想知道事件是否被触发.

@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

我在组件的构造函数中设置了事件侦听器,该侦听器侦听来自新组件的事件更改.每次收到事件时,我都会通过_getPosts()函数获取所有帖子.

@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);
  }
}

如果有人知道更好的方法,则一定要纠正我,因为我还不熟悉该框架,并且很难理解这些概念.文档涵盖了很多内容,但是如果有人像我这样对框架完全陌生,则我会丢失一些信息.我们希望对Hero Documentation进行扩展,它涵盖了更复杂的主题,例如子组件之间的通信以及子与父之间的通信,但不确定是否在某处进行了说明,而我只是错过了.

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.

解决方案

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.

这篇关于如何通知父组件更改和刷新视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆