Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 - Router resolve not working with BehaviorSubject

Angular gurus your help is greatly appreciated in this regard.

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 then updates this.configStringSource.next(config). Right after that I am trying to redirect it to this.router.navigate(['/clone/status']), but redirect is not happening.

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);

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 BehaviorSubject<Config>({ configID: "", sourceDbNodes: "", targetDbNodes: ""});

    // 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());
    }
}

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 {
    return this.configService.configsString$.map(
      data => data[1]);
    }
}

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 OnInit {

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

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

  ngOnInit() {
    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);
        }
    );
  }
}
like image 367
Guna M Avatar asked Sep 06 '17 12:09

Guna M


2 Answers

Your issue is that resolved observables need to complete, so you just need to add .take(1) or .first() to your resolver observable like:

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Config> | Promise<Config> | Config {
    return this.configService.configsString$.map(
      data => data[1]).take(1);
}

However, there's a much cleaner way of loading a config service:

import { Injectable, APP_INITIALIZER } from '@angular/core';
import { Config } from './_models/config';

@Injectable()
export class ConfigService {
  protected config: Config;

  constructor(private http: Http) {
  }

  getConfigs(): Observable<any> {
    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());
  }

  public load() {
     return new Promise((resolve, reject) => {
       this.getConfigs()
         .subscribe(
           config => {
             this.config = config;
             resolve(true);
           },
           err => resolve(err)
         );
     });
  }
}

export function ConfigServiceInitFactory(configService: ConfigService) {
  return () => configService.load();
}

export const ConfigServiceInitProvider = {
  provide: APP_INITIALIZER,
  useFactory: ConfigServiceInitFactory,
  deps: [ConfigService],
  multi: true
}

then in your app module, import both ConfigService and ConfigServiceInitProvider and provide them both like this:

providers: [
  ConfigService,
  ConfigServiceInitProvider,
  ... remaining service providers..
]

this will ensure that your config is loaded before pretty much anything else, and you can inject your config service where ever it's needed and the config will always be available in a synchronous manner.

like image 198
bryan60 Avatar answered Nov 16 '22 15:11

bryan60


It helped to me. In config.service change

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

to stack.

Add .first() to .asObservable().

like image 28
Иван Ишмаметьев Avatar answered Nov 16 '22 17:11

Иван Ишмаметьев