将数据传递到“router-outlet”子组件(角度2)

时间:2017-01-03 19:58:21

标签: angular dependency-injection angular2-routing

我有一个父组件进入服务器并获取一个对象。

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

}

似乎没想到我可以将数据注入路由器插座。看起来我在Web控制台中收到了错误......

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

这有点道理,但我怎么做以下......

1) Grab the "node" data from the server, from within the parent component
2) Pass the data I have retrieved from the server into the child router-outlet

看起来路由器插座的工作方式并不相同。

7 个答案:

答案 0 :(得分:61)

唯一的方法是使用共享服务。

<router-outlet [node]="..."></router-outlet> 

无效。路由器添加的组件将作为兄弟添加到<router-outlet>,并且不会替换它。

另见https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-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);
    }
}

答案 1 :(得分:34)

Günters的答案很棒,我只想指出不使用Observables的另一种方式。

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

@Injectable()
export class SharedService {

    sharedNode = {
      // properties
    };
}

在您的父母中,您可以指定值:

this.sharedService.sharedNode = this.node;

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

node: Node;

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

正如纽曼亲切地指出的那样,你也可以在html模板中使用this.sharedService.sharedNode或者获取一个getter:

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

答案 2 :(得分:5)

服务:

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

答案 3 :(得分:3)

有3种方法将数据从父母传递给孩子

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

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

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

注意1::您可以阅读有关解析器here的信息。还有一个解析器示例,以及如何将解析器注册到模块中,然后如何将数据从解析器检索到组件中。解析程序的注册在父母和孩子上都相同。

注2 :您可以阅读有关 ActivatedRoute here的信息,以便能够从路由器获取数据

答案 4 :(得分:2)

是的,您可以设置插座组件的输入,但必须以编程方式进行。如其他答案所述,您不能通过路由器出口的模板绑定将数据传递到路由器出口加载的组件。

(1)在父级的模板文件中,连接到路由器出口的activate事件:

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

(2)在父级的打字稿文件中,设置子级属性

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

在某些情况下,此方法比使用服务传递数据更好,因为它不会将父组件紧密耦合到子组件。即,子组件不了解父组件,从而允许它们以其他方式重用(例如,在完全不同的父组件中,该父组件没有共享服务的概念,并通过模板绑定传递相同的数据)。

为清楚起见,简化了上述onOutletLoaded版本,但是如果可以保证所有子组件都具有要分配的属性,则可以使用上述版本。如果您的组件具有不同的属性,则只需使用类型防护:

onChildLoaded(component) {
  if (component instanceof MyComponent1) {
    component.someValue = 123;
  } else if (component instanceof MyComponent2) {
    component.anotherValue = 456;
  }
}

注意事项

此方法的警告是,由于您以编程方式传递数据,因此您不再可以选择通过Angular异步指令传递可观察的流,该指令将为您管理可观察的生命周期。如果父母有很多可观察到的东西,那么这种方法可能就没有吸引力了。

答案 5 :(得分:0)

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 :(得分:0)

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

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

{ 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>