【问题标题】:Passing data into "router-outlet" child components将数据传递到“router-outlet”子组件
【发布时间】:2017-05-18 00:13:40
【问题描述】:

我有一个父组件去服务器并获取一个对象:

// parent component

@Component({
    selector : 'node-display',
    template : `
        <router-outlet [node]="node"></router-outlet>
    `
})

export class NodeDisplayComponent implements OnInit {

    node: Node;

    ngOnInit(): void {
        this.nodeService.getNode(path)
            .subscribe(
                node => {
                    this.node = node;
                },
                err => {
                    console.log(err);
                }
            );
    }

并在几个孩子之一显示:

export class ChildDisplay implements OnInit{

    @Input()
    node: Node;

    ngOnInit(): void {
        console.log(this.node);
    }

}

我似乎无法将数据注入router-outlet。看起来我在 Web 控制台中收到错误:

Can't bind to 'node' since it isn't a known property of 'router-outlet'.

这有点道理,但我该怎么做:

  1. 从父级服务器中获取“节点”数据 组件?
  2. 将我从服务器检索到的数据传递到子路由器出口?

router-outlets 的工作方式似乎不同。

【问题讨论】:

    标签: angular dependency-injection angular2-routing


    【解决方案1】:
    <router-outlet [node]="..."></router-outlet> 
    

    只是无效。路由器添加的组件作为兄弟姐妹添加到&lt;router-outlet&gt;,并且不会替换它。

    另见https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service

    @Injectable() 
    export class NodeService {
      private node:Subject<Node> = new BehaviorSubject<Node>([]);
    
      get node$(){
        return this.node.asObservable().filter(node => !!node);
      }
    
      addNode(data:Node) {
        this.node.next(data);
      }
    }
    
    @Component({
        selector : 'node-display',
        providers: [NodeService],
        template : `
            <router-outlet></router-outlet>
        `
    })
    export class NodeDisplayComponent implements OnInit {
        constructor(private nodeService:NodeService) {}
        node: Node;
        ngOnInit(): void {
            this.nodeService.getNode(path)
                .subscribe(
                    node => {
                        this.nodeService.addNode(node);
                    },
                    err => {
                        console.log(err);
                    }
                );
        }
    }
    
    export class ChildDisplay implements OnInit{
        constructor(nodeService:NodeService) {
          nodeService.node$.subscribe(n => this.node = n);
        }
    }
    

    【讨论】:

    • 这个可以用来给孩子一个ID吗?我尝试将所有node 类型更改为string,但它似乎不起作用或我做错了什么
    • 您可以将任何数据传递给子组件,但子组件需要自己设置id。您可以尝试从路由器插座的事件中获取ElementRef 以使用它来设置 ID,但我不知道是否或如何做到这一点(仅在我的手机上)
    • 可以通过 router-outlet 指令传输数据。我们可以为此使用“激活”事件。更多详情请阅读我的文章另见angular.io/guide/…另见linkedin.com/pulse/…
    【解决方案2】:

    Günters 的回答很好,我只想指出另一种不使用 Observables 的方法。

    这里我们虽然必须记住这些对象是通过引用传递的,所以如果你想在子对象上做一些工作而不影响父对象,我建议使用 Günther 的解决方案。但如果这无关紧要,或者实际上是期望的行为,我会建议以下内容。

    @Injectable()
    export class SharedService {
    
        sharedNode = {
          // properties
        };
    }
    

    您可以在您的父母中分配值:

    this.sharedService.sharedNode = this.node;
    

    在您的孩子(和父母)中,在您的构造函数中注入共享服务。如果您希望在该模块中的所有组件上都提供单例服务,请记住在模块级别提供程序数组中提供服务。或者,只需将服务添加到父 only 的 providers 数组中,然后父子将共享同一个服务实例。

    node: Node;
    
    ngOnInit() {
        this.node = this.sharedService.sharedNode;    
    }
    

    正如 newman 所指出的,您还可以在 html 模板或 getter 中使用 this.sharedService.sharedNode

    get sharedNode(){
      return this.sharedService.sharedNode;
    }
    

    【讨论】:

    • 您可以/应该直接绑定到 html 中的 sharedService.sharedNode。这样,sharedNode 中的更新将保持同步。
    【解决方案3】:

    服务:

    import {Injectable, EventEmitter} from "@angular/core";    
    
    @Injectable()
    export class DataService {
    onGetData: EventEmitter = new EventEmitter();
    
    getData() {
      this.http.post(...params).map(res => {
        this.onGetData.emit(res.json());
      })
    }
    

    组件:

    import {Component} from '@angular/core';    
    import {DataService} from "../services/data.service";       
        
    @Component()
    export class MyComponent {
      constructor(private DataService:DataService) {
        this.DataService.onGetData.subscribe(res => {
          (from service on .emit() )
        })
      }
    
      //To send data to all subscribers from current component
      sendData() {
        this.DataService.onGetData.emit(--NEW DATA--);
      }
    }
    

    【讨论】:

      【解决方案4】:

      有 3 种方法可以将数据从父级传递给子级

      1. 通过可共享服务:您应该将希望与孩子共享的数据存储到服务中
      2. 如果您必须接收不同的数据,则通过子路由器解析器

        this.data = this.route.snaphsot.data['dataFromResolver'];
        
      3. 如果您必须从父级接收相同的数据,则通过父级路由器解析器

        this.data = this.route.parent.snaphsot.data['dataFromResolver'];
        

      注意1:您可以阅读解析器here。还有一个解析器的示例,以及如何将解析器注册到模块中,然后从解析器中检索数据到组件中。解析器注册在父子节点上是一样的。

      注意2:您可以阅读ActivatedRoute here 以便能够从路由器获取数据

      【讨论】:

        【解决方案5】:

        this 问题之后,在 Angular 7.2 中,您可以使用历史状态将数据从父级传递给子级。所以你可以做类似的事情

        发送:

        this.router.navigate(['action-selection'], { state: { example: 'bar' } });
        

        检索:

        constructor(private router: Router) {
          console.log(this.router.getCurrentNavigation().extras.state.example);
        }
        

        但要注意保持一致。例如,假设您想使用路由器插座在左侧栏上显示列表,在右侧显示所选项目的详细信息。比如:


        项目 1 (x) | ................................................

        项目 2 (x) | ......选定的项目详细信息............

        项目 3 (x) | ................................................

        项目 4 (x) | ................................................


        现在,假设您已经点击了一些项目。单击浏览器的后退按钮将显示上一项的详细信息。但是,如果同时单击 (x) 并从列表中删除该项目怎么办?然后执行返回单击,将显示已删除项目的详细信息。

        【讨论】:

          【解决方案6】:

          是的,您可以将数据直接传递到路由器插座组件。可悲的是,如其他答案中所述,您不能使用角度模板绑定来执行此操作。您必须在打字稿文件中设置数据。当涉及到 observables 时,有一个很大的警告(如下所述)。

          方法如下:

          (1) 在父模板中连接到 router-outlet 的 activate 事件:

          <router-outlet (activate)="onOutletLoaded($event)"></router-outlet>
          

          (2) 切换到父组件的 typescript 文件,并在每次激活子组件时以编程方式设置它们的输入:

          onOutletLoaded(component) {
              component.someProperty = 'someValue';
          } 
          

          完成。

          但是,为清楚起见,onOutletLoaded 的上述版本进行了简化。仅当您可以保证所有子组件都具有您分配的完全相同的输入时,它才有效。如果您有不同输入的组件,请使用类型保护:

          onChildLoaded(component: MyComponent1 | MyComponent2) {
            if (component instanceof MyComponent1) {
              component.someInput = 123;
            } else if (component instanceof MyComponent2) {
              component.anotherInput = 456;
            }
          }
          

          为什么这种方法比服务方法更受欢迎?

          这种方法和服务方法都不是与子组件通信的“正确方法”(这两种方法都远离纯模板绑定),因此您只需决定哪种方式更适合项目。

          然而,这种方法避免了与“为通信创建服务”方法相关的紧密耦合(即,父级需要该服务,而子级都需要该服务,从而使子级在其他地方无法使用)。

          在许多情况下,这种方法也感觉更接近“角度方式”,因为您可以继续通过 @Inputs 将数据传递给您的子组件。它也非常适合您不想或不能与您的服务紧密耦合的现有或第三方组件。

          另一方面,当...时,它可能感觉不像是角度方式。

          警告

          使用这种方法需要注意的是,由于您在 typescript 文件中传递数据,因此您不再可以选择使用模板中使用的管道异步模式(例如 {{ myObservable$ | async }})来自动使用和传递您的 observable数据到子组件。

          相反,每当调用onChildLoaded 函数时,您都需要设置一些东西来获取当前的可观察值。这可能还需要对父组件的 onDestroy 函数进行一些拆解。这没什么不寻常的,在某些情况下经常需要这样做,例如使用甚至无法访问模板的 observable 时。

          【讨论】:

          • 谢谢!这是一个很好的答案?
          • 如果child componentparent component 完成http 请求之前被激活怎么办?
          • @Kardon63,我认为这与这个问题无关。您所描述的可能发生在许多不同的情况下。您应该提出另一个问题并说明您的具体情况。
          • @uɥƃnɐʌuop 好的,感谢您的澄清
          • 解耦是路要走。但是在给定的示例中,通信仅发生在onOutletLoaded($event) 上,在此loaded 事件之后不可用。不是吗?
          【解决方案7】:

          还有另一种不同的方式。我相信我的方法可以解决很多问题:

          这是处理路由的默认方式:

          { path: 'sample/page1', component: sampleComponent1 },
          { path: 'sample/page2', component: sampleComponent2 },
          { path: 'sample/page3', component: sampleComponent3 },
          

          但不是这样,让我们​​编写如下路线:

          { path: 'sample/:sub', component: sampleComponent },
          

          如您所见,我们组合了路由并制作了一个路由参数。我们可以在组件上接收该参数值:

          // sampleComponents.ts :
          sub = this.route.snapshot.params['sub'];
          

          现在在该组件的 html 文件中,我们将这些页面作为子组件导入。然后我们可以直接向他们发送数据。

          // sampleComponent.html :
          <app-cmp-page1 *ngIf="sub==='page1'" [data]="data"></app-cmp-page1>
          <app-cmp-page2 *ngIf="sub==='page2'" [data]="data"></app-cmp-page2>
          <app-cmp-page3 *ngIf="sub==='page3'" [data]="data"></app-cmp-page3>
          

          【讨论】:

            猜你喜欢
            • 2022-10-13
            • 2020-06-06
            • 2017-12-29
            • 1970-01-01
            • 2017-10-05
            • 2020-11-23
            • 1970-01-01
            • 2020-01-19
            • 2019-02-27
            相关资源
            最近更新 更多