mehta
mehta

Reputation: 755

How to pass data from parent to child more than once

I am building an Angular 2 application with a search form and result display on same page. I have used routing to display the result section. So the parent component is search form with multiple fields and child component is getting result from backend and displaying it.

Since the search criteria involves multiple fields, I am communicating the search criteria to child component through a shared service.

Parent component:

 @Component({
       templateUrl : 'app/search-form.template.html'
    })
    export class SearchFormComponent {

         constructor(private  searchService : SearchService,
                private router: Router){}

         submitSearch(){
          this.searchService.setSearchCriteria(this.model);
          this.router.navigate(['search-form/search-result']);
      }

Child Component:

 @Component({
         templateUrl: 'app/search-result/search-result.template.html'
    })
    export class SearchResultComponent implements OnInit{
        private searchResult = [] ;
        constructor(private searchService: SearchService) { }

         ngOnInit() {
                this.searchService.getSearchResult().subscribe(data => {
              this.searchResult = data;
          });

    }

Here is the service code:

 @Injectable()
    export class SearchService{
       constructor (private http: Http) {}

    setSearchCriteria(searchCriteria : SearchCriteria){
      this.searchCriteria = searchCriteria;
    }

    getSearchResult() : Observable<any> {
        let body = JSON.stringify(this.searchCriteria);
        let headers = new Headers({'content-type' : 'application/json'});
        let options = new RequestOptions({headers : headers});
        return this.http.post(this.baseUrl , body, options)
            .map((res: Response) => res.json().root || {});
    }

Parent and child are loaded using routing. Here is routing details

 const appRoutes : Routes = [
      {
        path: '',
        redirectTo: '/search-form',
        pathMatch: 'full'
    },
    {
        path: 'search-form', component: SearchFormComponent,
        children: [
            { path: ''},
            { path: 'search-result', component: SearchResultComponent }
        ]
        }
      ];

main.html looks like this. Here the search form (parent) is rendered 'search-form'

    <nav class="navbar" >
        <div class="container-fluid">
            <router-outlet></router-outlet>
        </div>
      </nav>

search-form.template.html snippet

  

      <form #searchForm="ngForm" class="form-horizontal"    (ngSubmit)="submitSearch()">
         <!-- form fields -->
         <button type="submit" class="btn btn-sm btn-primary pull-right">Submit</button>
    
        <div class="panel panel-primary">
           <!-- Search Result here -->
            <router-outlet></router-outlet>
        </div>

The submitSearch() method of parent is called on form submit from parent. It navigates to child component and show the searchResult. Till now it is working fine.

What I want is the user should be able to change the search criteria on the screen and hit submit button again. The submitSearch() method is called, but results in child component are not updated. I think the root cause it because child component is calling the service in ngOnInit method. After displaying the result, as I am on the same page, rehitting the submit button doesn't create the child component again and hence data is not refreshed.

How can I re-load the child with new searchResult on submit from parent component.

Upvotes: 1

Views: 1019

Answers (2)

Paul Samsotha
Paul Samsotha

Reputation: 208994

You should a Subject inside the SearchService. Call it something like currentResult. A subject is like a two-way observable, it's both a producer and a consumer. So on the parent end you can be the producer, and the child end the consumer.

You probably don't want to set the search criteria in the onInit, but when some action happens in the parent. Then you can produce some value for the child, which will be listening.

Maybe something like this

import { Subject } from 'rxjs/Subject;

@Injectable()
class SearchService {
  private currentSearch = new Subject<any>();

  searchAndSetCurrent(criteria) {
    this.http.get(..).toPromise().then(result => {
      currentSearch.next(result);
    });
  }

  getCurrentSearch(): Observable<any> {
    // asObservable is not really necessary as you can subscribe
    // to a subject. But to make sure people calling this method
    // don't try to emit anything, we should just limit it
    // to the capabilities of an Observable
    return this.currentSearch.asObservable();
  }
}

Now the parent can just call searchAndSetCurrent and the child can subscribe. Whenever the search is called by the parent, when the result comes in the child will get it.

If you want to do something with the result in the parent, you can make the search method return a promise

searchAndSetCurrent(criteria)L Promise<any> {
  return this.http.get(..).toPromise().then(result => {
    currentSearch.next(result);
    return this;
  });
}

For more about Subject, you can check this out

Upvotes: 1

Mark
Mark

Reputation: 92440

It's not clear how you have this wired up. You say the SearchResultComponent is a child of SearchFormComponent but you navigate to it as if where its own component after you run setSearchCriteria. Since we can't see the template it's hard to understand what's going on.

The way I would normally do something like this is to run getSearchResult() in the parent right after you setSearchCriteria() and pass the results to the child in an @Input. If you are navigating the parent using something like the ActivatedRoute service, then move the child code from ngOnInit to ngOnChanges where it will get refreshed when the input changes.

Upvotes: 1

Related Questions