24

I have 3 tabs in which one tab shows a table with list of employees. Works good when it is loaded the first time.ngOnInit Fetches data from server using http get. After that when I click add new employee to open a form, which take input from user and when that submit is clicked I call a function which calls the http post service to post that data to my server where it inserts the records and then after that it is redirected back to employee component, but now that employee component was already loaded, I cannot see the new record that I inserted in table unless I recompile my code.

employee.component.ts ( Which loads the employee Table)

import { Component, OnInit, OnDestroy } from '@angular/core';
import { EmployeeService } from '../employee.service';
@Component({
  selector: 'app-employees',
  templateUrl: './employees.component.html',
  styleUrls: ['./employees.component.css']
})
export class EmployeesComponent implements OnInit {

public employeeObj:any[] = [{emp_id:'',empname:'',joindate:'',salary:''}] ;
constructor(private employeService:EmployeeService) { }

ngOnInit() {    
this.employeService.getEmployees().subscribe(res => this.employeeObj = res);
}

}

form.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { EmployeeService } from '../../employee.service';
import { Router } from '@angular/router';

@Component({
    selector: 'app-form',
    templateUrl: './form.component.html',
    styleUrls: ['./form.component.css'],

})
export class FormComponent implements OnInit {
empform;

ngOnInit() { 
this.empform = new FormGroup({
    empname: new FormControl(""),
    joindate: new FormControl(""),
    salary: new FormControl("")
})
} 
constructor(private employeeService: EmployeeService, private router:Router) 
{ }

 onSubmit = function(user){
    this.employeeService.addEmployee(user)
    .subscribe(
        (response) => { this.router.navigate(['/employees']); }  
    );

}
}

employee.service.ts

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/Rx';
@Injectable()
export class EmployeeService{
constructor(private http:Http){}
addEmployee(empform: any[]){
    return this.http.post('MY_API',empform);
}

getEmployees(){
    return 
this.http.get('MY_API').map((response:Response)=>response.json());
}
}

AppModule.ts

    import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';

import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { EmployeeService } from './employee.service';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { NavComponent } from './nav/nav.component';
import { ContainerComponent } from './container/container.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { EmployeesComponent } from './employees/employees.component';
import { CompaniesComponent } from './companies/companies.component';
import { InternsComponent } from './interns/interns.component';
import { FormComponent } from './employees/form/form.component';
import { ComformComponent } from './companies/comform/comform.component';
import { InternformComponent } from './interns/internform/internform.component';

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    NavComponent,
    ContainerComponent,
    DashboardComponent,
    EmployeesComponent,
    CompaniesComponent,
    InternsComponent,
    FormComponent,
    ComformComponent,
    InternformComponent
  ],
  imports: [    
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpModule,
    RouterModule.forRoot([
            {
                path:'dashboard',
                component:DashboardComponent
            },
            {
                path:'employees',
                component:EmployeesComponent
            },
            {
                path:'companies',
                component:CompaniesComponent
            },
            {
                path:'interns',
                component:InternsComponent
            },
            {
                path:'addemployee',
                component:FormComponent
            },
            {
                path:'comform',
                component:ComformComponent
            },
            {
                path:'internform',
                component:InternformComponent
            }       
      ])
  ],
  providers: [EmployeeService],
  bootstrap: [AppComponent]
})
export class AppModule { }

The problem is I am calling my API from ngOnInit which loads perfectly the first time the component loads. When I submit the form it goes to my API and then it is redirected back to the employee component, but the data is not updated as it should.

P.S : I am sorry for such small post. I am kind of new to this website.

Update :

It has been more than a year now since I posted this thread and I see a lot of people have benefit from it or maybe not. However I would like to point that I have already understood what caused the error and I will now try to make you understand the solution.

The most important concept to adapt here is the Angular Life Cycle Hooks. What happens is, we call ngOnInit the first time a component is loaded and this will only fire once when the angular application is bootstrapped. This is similar to a class constructor but it only fires once. So you should not put any DOM related modifications here. You should understand Angular Life Cycle Hooks to solve this problem. I do not have a working solution with me as I moved to Vuejs since last 8 months but in some free time I will post an update here.

C.Champagne
  • 5,381
  • 2
  • 23
  • 35
Viral Patel
  • 485
  • 1
  • 5
  • 11

8 Answers8

11

Please try adding router event in employee component. So that every time when /employee url state is routed it will fetch the employee details.

employee.ts component

constructor(private employeeService: EmployeeService, private router:Router) 
{ }

ngOnInit() {    
  this.router.events.subscribe(
    (event: Event) => {
           if (event instanceof NavigationEnd) {
                this.employeService.getEmployees().subscribe(res => this.employeeObj = res);
           }
    });
}
Amit Chigadani
  • 28,482
  • 13
  • 80
  • 98
  • `return this.http.post('MY_API',empform)` alone will not fetch you the results. You have to map the results. https://angular.io/docs/ts/latest/guide/server-communication.html Please update the question if you have already done that. – Amit Chigadani Jun 02 '17 at 15:01
  • I have done that sir. Still I am not able to update the employee component. – Viral Patel Jun 02 '17 at 16:54
  • Yes sir, exactly. It is not. How do I show you my template files? – Viral Patel Jun 02 '17 at 17:21
  • Note that if your url route is not changed while your `form` component is loaded, then `employee` component will not be refreshed even when you do `router.navigate`. – Amit Chigadani Jun 02 '17 at 17:34
  • Sir, the URL changes from localhost:4200/employee to localhost:4200/form and then it changes back to localhost:4200/employee when I submit the form. – Viral Patel Jun 02 '17 at 17:41
  • I tried using the updated answer but sir this is what I got : http://prntscr.com/ffohfk – Viral Patel Jun 04 '17 at 07:25
  • I added a different class, now added as per your guidelines and now I am getting this error : Error: Uncaught (in promise): Error: No provider for EmployeeService! Error: No provider for EmployeeService! Here is an image : http://prntscr.com/ffovc0 – Viral Patel Jun 04 '17 at 08:33
  • I have done that sir, but now it is not fetching data from the API, that I have created. Do you want me to post all the file screenshots sir? – Viral Patel Jun 04 '17 at 08:39
  • I am not getting any errors anymore sir, but the data is not updating when I redirect it back from addemployee form to the employees component using router.navigate. – Viral Patel Jun 04 '17 at 08:50
  • I am getting data from API sir. The problem is when I open employee component for the first time which has all the list of employees I added in database it shows the perfect output. Now without leaving my app, I click on add employee which opens a page with a form, which submits data to my other API which stores into database and then I redirect it back to employee component, but the employee component does not show the newly added data. That is the main problem sir. – Viral Patel Jun 04 '17 at 09:06
  • This code is working... but ' this.router.events.subscribe((res:any) => { ' call multiple time when state changes. – Anil Kumar Jan 16 '19 at 09:20
  • You will have to unsubscribe to it within `ngOnDestroy()` life cycle hook. So that when component is destroyed, subscription will be killed – Amit Chigadani Jan 16 '19 at 09:22
  • Can you help me...I tried this ngOnDestroy(){ this.router.events.unsubscribe(); }.. but showing 'Property 'unsubscribe' does not exist on type 'Observable'. Did you mean 'subscribe'?'.. and while debugging ngOnDestroy() didn't called after state changes – Anil Kumar Jan 16 '19 at 09:40
  • That is not the way to unsubscribe router events. Assign a subscription variable to it, lets say `this.subRouter = this.router.events.subscribe(...)` and then do `this.subRouter.unsubscribe()` – Amit Chigadani Jan 16 '19 at 10:10
5

As a general rule, when routing, the angular router will re-use the same instance of a component if it can.

So, for example, navigating from /component/1 to /component/2, where the url is mapped to the same component (but with different params) will cause the router to instantiate an instance of Component when you navigate to /component/1, and then re-use that same instance when you navigate to /component/2. Based on what you're describing (where ngOnInit is only being called once), it seems as though this is what you're encountering. It's hard to say for sure, without seeing your templates and your route configuration. I know you say that your URL is changing from /employees to /form, but that may not matter, depending on how your templates and route configuration is set up. You can post that code (your templates and router configuration) here for examination if you wish.

Barring that, another option is that the router exposes all its events as a stream. So, you can subscribe to that stream and act on that, rather than just relying on ngOnInit.

In your employee.component.ts

.....
export class EmployeesComponent implements OnInit {

.....

ngOnInit() {


   this.router.events
              // every navigation is composed of several events,
              // NavigationStart, checks for guards etc
              // we don't want to act on all these intermediate events,
              // we just care about the navigation as a whole,
              // so only focus on the NavigationEnd event
              // which is only fired once per router navigation
              .filter(e => e instanceof NavigationEnd)
              // after each navigation, we want to convert that event to a call to our API
              // which is also an Observable
              // use switchMap (or mergeMap) when you want to take events from one observable
              // and map each event to another observable 
              .switchMap(e => this.employeeService.getEmployees())
              .subscribe(res => this.employeeObj = res);    
}

EDIT

I am seeing one piece of weird code:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { EmployeeService } from '../../employee.service';
import { Router } from '@angular/router';

@Component({
    selector: 'app-form',
    templateUrl: './form.component.html',
    styleUrls: ['./form.component.css'],

})
export class FormComponent implements OnInit {
  empform;

  ngOnInit() { 
    this.empform = new FormGroup({
      empname: new FormControl(""),
      joindate: new FormControl(""),
      salary: new FormControl("")
    })
  } 

  constructor(private employeeService: EmployeeService, private router:Router) { }

 onSubmit(user){   // <--  change this line
    this.employeeService.addEmployee(user)
    .subscribe(
        (response) => { this.router.navigate(['/employees']); }  
    );

  }
}

But overall, you are saying that if you navigate to your employee component, then to your form component and then back to your employee component, when you hit the employee component the second time, your employee list doesn't refresh.

Can you use console.log statements to ensure that ngOnInit is being called multiple times in your screen flow above? Because, based on your router configuration, when you navigate to employee list to form and back, your employee component should be re-initialized (by calling ngOnInit again)

snorkpete
  • 14,278
  • 3
  • 40
  • 57
  • Sir, this solution is also not working. Show I post screenshots of all the files? – Viral Patel Jun 04 '17 at 09:07
  • You can post the code of the files. What error are you getting? – snorkpete Jun 04 '17 at 16:38
  • Yes Sir, here are the screenshots : Employee Component from the table is loaded and fetches data from the API : http://prntscr.com/ffzznd Form Component which is used to insert data to database using the API : http://prntscr.com/ffzzxs The Service file to make http post and get request to my API : http://prntscr.com/fg006t – Viral Patel Jun 05 '17 at 05:07
  • Anything sir? Still cannot find any solution – Viral Patel Jun 09 '17 at 06:43
  • Once again, what's the error that you're getting? Also, as a general rule, please post the actual code rather than screenshots. i can't copy, paste and adjust code examples from a screenshot, which makes it very difficult to address issues in your code – snorkpete Jun 09 '17 at 12:43
  • also, could you post the code for your router configuration? – snorkpete Jun 09 '17 at 12:44
  • I will add all the code now sir. I am really sorry for not doing that in the first place. – Viral Patel Jun 09 '17 at 14:07
  • Sir, I updated the main thread. Thank you for all the help so far sir. – Viral Patel Jun 13 '17 at 07:30
4

When your component is linked with a route, better to add your code in the constructor subscribing to ActivatedRoute params and forcing change detection eg:

constructor(private route: ActivatedRoute, private changeDetector: ChangeDetectorRef) {
    super();
    this.route.params.subscribe((data) => {
    */update model here/*
    this.changeDetector.detectChanges();
   } 
}
Anji K
  • 41
  • 3
4

What I think it happens is your route call is outside the Angular lifecycle because you are doing an async call right?

First check if your log show something like this:

WARN: 'Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?'

If this is the case the solution is really simple, you have to tell Angular to call your routing directive inside their lifecycle.

Next code should fix your problem:

import { NgZone } from '@angular/core';
import { Router } from '@angular/router';

...
constructor(
    private employeeService: EmployeeService, 
    private router:Router,
    private ngZone: NgZone) { }

 onSubmit = function(user) {
   this.employeeService.addEmployee(user)
     .subscribe(
       (response) => { 
         this.ngZone.run(() =>
           this.router.navigate(['/employees']));
       }  
     );
}

Carlos Verdes
  • 3,037
  • 22
  • 20
  • You're a life saver. I was having some issues regarding the router and I haven't noticed the warning log about the Angular Zone. – Alécio Gomes May 29 '19 at 18:04
  • I moved to Ractive, Angular makes all really complex and time consuming, it´s like the developers fighting against the framework. – Carlos Verdes Jun 07 '19 at 14:51
4

add the following line in EmployeesComponent

ionViewWillEnter(){
  this.ngOnInit();
}

it will manually invoke ngOnInit after redirection

Prasanth M P
  • 848
  • 1
  • 6
  • 9
1

If you downgrade @angular/router@4.1.3 seems to be working where on routerLink if you navigate it will trigger ngOnInit

Rey Ramos
  • 689
  • 5
  • 2
1

Make sure you have <route-outlet></route-outlet> inside app.component.html. This should work just fine with the latest version of Angular 5.

Nizar B.
  • 3,098
  • 9
  • 38
  • 56
0

1.import component which is to be navigated

eg. import { SampleComponent} from './Sample.component';

2.Add component to constuctor to be navigated

constructor( private Comp: SampleComponent){}

3.Add this code where u need to navigate

this.Comp.ngOnInit();
this._router.navigate(['/SampleComponent']);--/SampleComponent-your router path

eg.After employee Insert it redirects to employee list page

 this.service.postData(Obj)
          .subscribe(data => {
            (alert("Inserted Successfully"));
            this.Comp.ngOnInit();
            this._router.navigate(['/SampleComponent']);
          },
            error => {
              alert(error);
          });
Community
  • 1
  • 1
Mali
  • 1