Search code examples
angularangular2-routingangular-component-router

Angular 4: How to use same component as both parent and child?


My requirement is to have tree of forms.In the parent form on click of button only child form will be created. It can go on like this upto n-levels.

My approach:

I have created one component. This is used as both parent and child. on click of button i am dynamically adding item to route.routeConfig.children of ActivatedRoute. Then navigating to the added path with relative to the current path. Till this part everything goes well. So i see all the forms in the scren. I want to make only the active form visible on screen. Then on click of a button (Back button) I want to go to its relaive parent and show that form only. I am able to hide the parent form when opening child form by using *ngif in the template. But not able to make it visible while coming back frm child. Below is my code. Please let me know if any other approach i should take or what extra i need to add to make it work my way.

My component.

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css'],
  providers: [ConfigService]
})
export class TestComponent implements OnInit, OnDestroy {
  name: string;
  age: Number;
  showParent: boolean = true;
  childrenRoute: Routes;
  message: any;
  subscription: Subscription;
  private sub: any;

  constructor(private router: Router, private route: ActivatedRoute, private _configService: ConfigService) {
    this.name = 'test data';
    this.age = 20;
    this.router = router;
    if(this._configService.getConfig('showParent') != null){
      this.showParent=false;
    }
    this.subscription = router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        if (_configService.getConfig('showParent') != null) {
          this.showParent =  _configService.getConfig('showParent');
        }
      }

    });


  }

  ngOnInit() {

  }

  ngOnDestroy() {
    // unsubscribe to ensure no memory leaks
    this.subscription.unsubscribe();
  }

  showChildRecord() {
    this.childrenRoute = [{
      path: 'child',
      component: TestComponent
    }];
    if (this.route.routeConfig != undefined) {
      this.route.routeConfig.children = this.childrenRoute;
    }

    this._configService.setOption('showParent', false);
    this.router.navigate(['./child'], { relativeTo: this.route });
  }

  back() {
    this._configService.setOption('showParent', true);
    this.router.navigate(["../"], { relativeTo: this.route });

  }

}

I tried using a service to share the data between each instance of the same component.

@Injectable()
export class ConfigService {
    private config = {};

    setOption(option, value) {
        this.config[option] = value;
    }

    getConfig(option: string) {
        if(option != undefined){
            return this.config[option] != undefined ? this.config[option]: null;
        }else
            return null;
    }

}

the template test.component.html

<div *ngIf="showParent">
<p>Name: 
 <input type="text" [(ngModel)]="name">
</p>
<p>Age:
 <input type="text" [(ngModel)]="age">
</p>
{{name}} : {{age}}
<button  (click)="showChildRecord();" > Go to Child Record</button>
<button (click)="back();">Back</button>
</div>

<router-outlet></router-outlet>

App.module.ts routes:

const appRoutes: Routes = [ 
  { 
    path: 'child',
    component: TestComponent,
    children: [
        { path: 'child', 
          component: TestComponent         
        }
      ]
  }

];

I am trying to store the "showParent" information in the service and toggling it depending on actions. Under routing event "NavigationEnd" I am reading it and assigning it to the showParent propery.But it does not work. Still my parent form is hidden. And i just see blank screen. One problem i have noticed is the "NavigationEnd" event is fired multiple times may be becoz i am using same component and when a new child is created it adds another event.


Solution

  • I am able to find the solution for the above problem after spending some time. So thought of sharing here.

    So there were some mistakes and some learnings too.

    Mistakes in my approach:

    1. I had added serive ConfigService under testComponent. To share the same service across all the child components i should add the service under the root component. Else each component will have their own instance of the service and the data will be different.

    Means

    providers: [ConfigService]
    

    should be added to the AppModule.

    1. Since it is just a service it does not listen to the change of value. So i should make it an observable.

    So instead of using my above service i created another service like below.

    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { Subject } from 'rxjs/Subject';
    
    @Injectable()
    export class MessageService {
        private subject = new Subject<any>();
    
        sendMessage(message: any) {debugger
            this.subject.next(message);
        }
    
        clearMessage() {
            this.subject.next();
        }
    
        getMessage(): Observable<any> {
            return this.subject.asObservable();
        }
    }
    

    In the testComponent i am subscribing to it. I am only adding the changed section. So When we navigate back to parent route ngOnDestroy of child gets called.Here we are chaning the value using the serivce which is later caught in the subscription callback of parent.

    constructor(private router: Router, private route: ActivatedRoute, private _configService: ConfigService,private _msgService:MessageService) {
        this.name = 'test data';
        this.age = 20;
        this.router = router;
        this.subscription = this._msgService.getMessage().subscribe(message => { 
    
          if(message['showParent']){
            this.showParent=true;
          }
        });
    }
    
       ngOnDestroy() {
        // unsubscribe to ensure no memory leaks  
        this._msgService.sendMessage({ 'showParent':true });
        this.subscription.unsubscribe();
      }
    

    Above thing solved the problem of getting the parent notified from child. But in my case because i can go to multiple levels. Back button click of the last children fires this subscribe callback of each parent components until the first one. So each parent component becomes visible instead of just the immediate parent. Though it did not solve my purpose but i learned this way of transferring data across components.May be i could use something like routing path to decide whther it is immediate parent or not. I haven't got hold of that yet.

    But i got to solve my problem using another easy approach.

    Approach 2:

    Under the template i added event listener for deactivate .

    <router-outlet (deactivate)='onDeactivateChild($event)' ></router-outlet>
    

    And as part of TestComponent i added below method.

    onDeactivateChild(){
        this.showParent=true;
      }
    

    Problem solved. On click of back button of child deactivate event gets fired and parent gets to know about it. Hope it will help someone facing this problem.

    Thanks.