1

I am a newbie and not sure what is wrong with my approach, please suggest how to resolve below issue.

Here is what I am trying to do, when I access main app page it calls getConfigs() from config.service.ts and get the data from backend the update this.configStringSource.next(config). Right after that it redirects to this.router.navigate(['/clone/status']), there I am not getting data from config.service.ts

If I use ngAfterContentChecked, config-resolver.service.ts getting executed befor status.component.ts and config-resolver.service.ts fetching the correct data as expected, but I am not able to get the same data from status.component.ts using this.route.data.subscribe, I am getting "undefined".

Please see the complete code below and your help would be appreciated.

config.ts

export class Config {
    configID: string;
    sourceDbNodes: string;
    targetDbNodes: string;
}

config.service.ts

import { Injectable, OnInit } from '@angular/core';
import { Http, Headers, Response } from '@angular/http';
//import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Router } from '@angular/router';
import {Subject} from 'rxjs/Subject';

import { Config } from '../_models/config';

@Injectable()
export class ConfigService {

    // Observable string source
    private configsStringSource = new Subject<Config>();

    // Observable string stream
    configsString$ = this.configsStringSource.asObservable();

    // Service message commands
    updateConfigs(configs: Config) {
      this.configsStringSource.next(configs)
    }

    constructor(private http: Http, private router:Router) { }

    getConfigs() {
      let headers = new Headers();
      headers.append('Content-Type','application/json');
      return this.http.get('http://localhost:8080/sample1/api/config', { headers: headers })
        .map((response: Response) => response.json());
    }

    getConfig(configID: string) {
      this.configsString$.subscribe(
        data => {
console.log('config-resolver.service.ts configsString$ = ', data[1]);
          return data[1];
      });
    }
}

config-resolver.service.ts

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';

import { ConfigService } from './config.service';
import { Config } from '../_models/config';

interface Server {
  id: number;
  name: string;
  status: string;
}

@Injectable()
export class ConfigResolver implements Resolve<Config> {

  config: Config;

  constructor(private configService: ConfigService) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Config> | Promise<Config> | Config {
    this.configService.configsString$.subscribe(
        data => {
console.log('config-resolver.service.ts data[1] = ', data[1]);
        this.config = data[1];
console.log('config-resolver.service.ts this.config = ', this.config);
     });
console.log('config-resolver.service.ts this.config = ', this.config);
     return this.config;
  }
}

app.module.ts

...
...
import { ConfigService } from './_services/config.service';
import { ConfigResolver } from './_services/config-resolver.service';

@NgModule({
  declarations: [
...
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    AppRouting,
    BrowserAnimationsModule,
  ],
  providers: [ConfigService, ConfigResolver],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.routing.ts

import { Routes, RouterModule } from '@angular/router';

import { CloneComponent } from './clone/clone.component';
import { StatusComponent } from './status/status.component';
import { ConfigurationComponent } from './configuration/configuration.component';
import { LogsComponent } from './logs/logs.component';
import { ConfigResolver } from './_services/config-resolver.service';

const appRoutes: Routes = [
    { path: 'clone', component: CloneComponent, children: [
        {path: 'status', component: StatusComponent, resolve: {config: ConfigResolver} },
        ]
    },
    { path: 'logstream', component: LogstreamComponent },
];

export const AppRouting = RouterModule.forRoot(appRoutes);

app.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { Config } from './_models/config';
import { ConfigService } from './_services/config.service';

@Component({
  moduleId: module.id.toString(),
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})

export class AppComponent implements OnInit {
  configs: Config[];

  constructor(private router:Router, private configService:ConfigService ) { }

  title = 'Angular 4 Proeject';

  private getConfigs() {
    this.configService.getConfigs().subscribe(configs => { 
        this.configs = configs;
                this.configService.updateConfigs(configs);
console.log('app.component.ts sourceDbNode = '+this.configs[0].sourceDbNodes);
    });
  }

  ngOnInit() {
    this.getConfigs();
    this.router.navigate(['/clone/status']);
  }

}

status.component.ts

import { Component, Input, OnInit, AfterContentChecked } from '@angular/core';
import { ActivatedRoute, Params, Router, Data } from '@angular/router';

import { Config } from '../_models/config';
import { ConfigService } from '../_services/config.service';

@Component({
  selector: 'app-status',
  template: `
    <p>
      status Works! {{config}}
    </p>
  `,
  styleUrls: ['./status.component.scss']
})

export class StatusComponent implements AfterContentChecked {

  configs: string;
  config: Config;
  servers: Array<any>;
  server: { id: number; name: string; status: string; };

  constructor(private configService:ConfigService,
              private route: ActivatedRoute,
              private router: Router) { }

  ngAfterContentChecked() {
    this.route.data.subscribe(
        (data: Data) => {
          this.config = data['config'];
console.log('status.component.ts data = ', data['config']);
console.log('status.component.ts this.config = ', this.config);
        }
    );
  }
}

console.log output

accordion.component.ts:54

accordion.component.ts this.accordions = undefined core.es5.js:2925

Angular is running in the development mode. Call enableProdMode() to enable the production mode. config-resolver.service.ts:69

config-resolver.service.ts this.config = undefined clone.component.ts:20

clone ngOnInit this.config = undefined status.component.ts:79

status.component.ts data = undefined status.component.ts:80

status.component.ts this.config = undefined status.component.ts:79

status.component.ts data = undefined status.component.ts:80

status.component.ts this.config = undefined status.component.ts:79

status.component.ts data = undefined status.component.ts:80

status.component.ts this.config = undefined config-resolver.service.ts:64

config-resolver.service.ts data[1] = {configID: "PRODDB_TO_DEVDB", sourceDbNodes: "dbnode21", targetDbNodes: "dbnode22"} config-resolver.service.ts:66

config-resolver.service.ts this.config = {configID: "PRODDB_TO_DEVDB", sourceDbNodes: "dbnode21", targetDbNodes: "dbnode22"} app.component.ts:27

app.component.ts sourceDbNode = dbnode11 accordion.component.ts:54

status.component.ts data = undefined status.component.ts:80

status.component.ts this.config = undefined

--

Thank you in advance for the help.

Guna M
  • 53
  • 2
  • 12

1 Answers1

1

Your resolver is wrong:

this.configService.configsString$.subscribe(
  data => {
    this.config = data[1];
  });
  return this.config;
}

That makes no sense. You're getting a value asynchronous, by subscribing to an observable, so that the callback is executed, later, when the data is available, and after this asynchonous call is made, you return immediately.

As the return type of themethod shows, just return an observable, and the router itself will subscribe, and navigate to your component only once the data is available:

return this.configService.configsString$.map(
  data => data[1];
});

That is only the first problem, though. Since you're subscribing to a subject, the router will wait until the next event is emitted by the subject. But there will never be one, because you've just emitted the last event before, when updating the config. You need to learn more about how observables work.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Hi JB Nizet, thank you for your quick response. I modified my resolver with resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | Config { return this.configService.configsString$.map( data => data[1]); }, now it's not even redirecting to clone/status component. What else is missing please? – Guna M Sep 05 '17 at 17:13
  • Read the last paragraph of my answer: you call updateConfigs(), which emits an event, then you navigate, which causes the router to subscribe, and wait for the next event, but it's never emitted, since it has been emitted before. You need to use a BehaviorSubject, or to change the way you're dealing with that stuff. – JB Nizet Sep 05 '17 at 18:55
  • Hi JB Nizet, you are so kind to answer my questions, thank you again. As I said I am newbie to Angular and just started learning I am not able to do it in right way. Would you mind pointing me some example how I can use BehaviorSubject in my case? or what other approach I could move forward to get this working? – Guna M Sep 05 '17 at 19:23
  • Hi JB Nizet, thank you for pointing me the link. As per https://stackoverflow.com/questions/39066604/angular-2-router-resolve-with-observable I imported 'rxjs/add/operator/first' in my config-resolver.service.ts and modified config.service.ts as configsString$ = this.configsStringSource.asObservable().first(); Now it's working. Can you please confirm what I am doing is correct? Thank you. – Guna M Sep 05 '17 at 19:41