Binding to Input Properties of Child Component within Router Outlet

≯℡__Kan透↙ 提交于 2019-12-11 04:43:22

问题


I want to bind a parent component input element value to a child component property. The wrinkle is that the child component is nested within the parent via router-outlet. It seems like the best approach is to use a shared service between the parent and child component as demonstrated here.

Here's my testing implementation:

Parent component:

import { Component } from '@angular/core';
import { Router }   from '@angular/router';
import { ThreadsService }   from './threads.service';

@Component({
    moduleId: module.id,
    selector: 'threads',
    templateUrl: 'threads.component.html',
    styleUrls: ['threads.component.css'],
    providers: [ ThreadsService ]
})


export class ThreadsComponent {

    constructor(private router: Router,
    private threadsService: ThreadsService){}

    emit(value: string) {
        let emission = value;
        this.threadsService.emissionReceived(emission);

    }

}

Parent template

<div class="col-sm-9 threads">
    <div class="threads">
        <div class="thread-container">
            <ul class="nav nav-tabs">
                <li [class.active]="router.url == '/featured'">
                    <a routerLink="/featured">Featured</a>
                </li>
                <li [class.active]="router.url == '/new'">
                    <a routerLink="/new">New</a>
                </li>
                <li [class.active]="router.url == '/trending'">
                     <a routerLink="/trending">Trending</a>
                </li>
                <div class="col-sm-3 search-div">
                    <input #threadsearch (keyup.enter)="emit(threadsearch.value)" type="text" class="form-control  search-box" placeholder="Search topics...">
                </div>
            </ul>
            <!--Child component in here-->          
            <router-outlet></router-outlet> 
        </div>
    </div>
</div>

Child component:

import { Component, Input } from '@angular/core';
import { ThreadsService } from './threads.service'
import { Subscription }   from 'rxjs/Subscription';

@Component({
    moduleId: module.id,
    selector: 'featured-threads',
    template: `
        <div class="featured">
        <thread [threads]="threads" [emission]="emission"></thread>
        </div>`, 
    providers: [ ThreadsService ]
})

export class FeaturedThreadsComponent {
    subscription: Subscription;
    emission = '';

    constructor(private threadsService: ThreadsService) { 
        this.subscription = threadsService.searchResults$.subscribe(
            emission => {
                this.emission = emission;
                console.log(emission)
            }
        )

    ngOnDestroy() {
    // prevent memory leak when component destroyed
        this.subscription.unsubscribe();

    }
}

I can log emission to console from the parent component and from the shared service, but not from this child component. As you can, see the child component has a child of its own, a template in which emission will eventually be rendered.


回答1:


Günther is completely right in his comment:

"you get an instance per provider. If you provide it on two components an instance will be created for each instance of these components. If you provide it only on the parent, then the child gets the instance of the parent (or any ancestor) injected. Therefore provide it only once on the component that contains the router-outlet (for one of its ancestors). At the top of the tree (root injector) is AppModule. When no provider is found on any ancestor component the root injector is consulted"

Either you provide your service in your ngModule:

providers: [ ThreadsService ]

and therefore remove the providers from your child and parent.

OR you delete the provider from your child-component.

Here is a plunker with your code, where providers is set only in the parent:

plunker

When looking at the console, you can see that the data is now emitted to child as well.

Finally some excerpts from angular.io:

When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector. If the component's injector lacks the provider, it passes the request up to its parent component's injector. If that injector can't satisfy the request, it passes it along to its parent component's injector. The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors. If we run out of ancestors, Angular throws an error.

... Suppose we configured the root injector (marked as A) with providers for Car, Engine and Tires. We create a child component (B) that defines its own providers for Car and Engine This child is the parent of another component (C) that defines its own provider for Car.

Behind the scenes each component sets up its own injector with one or more providers defined for that component itself.




回答2:


first of all

providers: [ ThreadsService ]

all your code is creating new instance for Child component as well as new one for Parent this will cause harm to your code. Coz service must be singleton and must be added in NgModule

For example

import { NgModule }           from '@angular/core';
import { BrowserModule }      from '@angular/platform-browser';
import { AppComponent }       from './app.component';

import { ThreadsService }        from './threads.service';

import { FormsModule }        from '@angular/forms';

@NgModule({
  imports: [ BrowserModule,  FormsModule ],
  declarations: [AppComponent],
  providers: [ ThreadsService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }


来源:https://stackoverflow.com/questions/41818977/binding-to-input-properties-of-child-component-within-router-outlet

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