Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing Values Between Non-Parent/Child Relationship Components in Angular 2

From my understanding, in Angular 2, if you want to pass values between unrelated components (i.e., components that don't share a route and thus don't share a parent-child relationship), you do so via a shared service.

So that's what I've set up in my Angular2 app. I am checking to see if a certain series of characters exist in a url and returning true if it does.

  isRoomRoute(routeUrl) {
      if ((routeUrl.includes('staff') || routeUrl.includes('contractors'))) {
          console.log('This url: ' + routeUrl + ' is a roomRoute');
          return true;
      } else {
          console.log('This url: ' + routeUrl + ' is NOT a room route');
          return false;
      }
  }

In the constructor of the root app.component, I'm subscribing to routing events:

constructor(private routeService: RouteService,
            private router: Router)  {
    this.router.events.subscribe((route) => {
    let routeUrl = route.url;
    this.routeService.sendRoute(routeUrl);
    this.routeService.isRoomRoute(routeUrl);
    });
}

... and then using those provided urls to check whether or not a url contains the specific string. This is evaluated every time the route changes.

So that's all working as expected.

However, I'm running into a problem in passing the result of that check to a different, non-related (non-parent-child) component.

Even though I'm using a shared service (routeService) in both the app.component and the un-related (room.component) component, what works in one doesn't work in the other. From my understanding, the "truthiness" of what's being checked here should be enough to return a true statement.

But in the secondary, unrelated component, I get an "undefined" error when I call the function, like this:

  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }

So this is where I'm stuck. Basically the evaluation as to whether a url contains a certain string has already happened. Now I just need to pass the boolean result of that check to the secondary, non-related component. How can I best do this in Angular 2?

like image 956
Muirik Avatar asked Jan 10 '17 23:01

Muirik


2 Answers

Your understanding is correct, an injectable shared service is a common way of communication between multiple, unrelated components.

Here is the walk-through of such a use case.

Firstly, suiting your situation, we will listen the Router events in AppComponent, obtain the active route, and pass it to RouteService so the service can manipulate it, and/or serve it to other components.

This is how the AppComponent should look like:

export class AppComponent {

    constructor(private _router: Router,
                private _routeService: RouteService) {

        this._router.events.subscribe(event => {
            if (event instanceof NavigationEnd) {
                let url = event.urlAfterRedirects;
                this._routeService.onActiveRouteChanged(url);
            }
        });
    }

}

When it comes to the service, here we'll introduce the BehaviorSubject as a delegate, so the components using the service can subscribe to a service data changes. For more information about BehaviorSubject and other Subjects, visit: Delegation: EventEmitter or Observable in Angular2

Here is the implementation of our shared RouteService (components need to use the single instance of the service, so make sure you've provided it at the root level):

@Injectable()
export class RouteService {

    isRoomRouteSource: BehaviorSubject<boolean> = new BehaviorSubject(false);

    constructor() { }

    onActiveRouteChanged(url: string): void {
        let isRoomRoute = this._isRoomRoute(url);
        this.isRoomRouteSource.next(isRoomRoute);
        // do other stuff needed when route changes
    }

    private _isRoomRoute(url: string): boolean {
        return url.includes('staff') || url.includes('contractors');
    }
}

The example of another component using the service, and subscribing to our BehaviorSubject changes:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this._routeService.isRoomRouteSource.subscribe((isRoomRoute: boolean) => {
            this.isCurrentRouteRoomRoute = isRoomRoute;
            // prints whenever active route changes
            console.log('Current route is room route: ', isRoomRoute);
        });
     }

}

If subscribing to isRoomRouteSource changes isn't necessary, say we just need the last value stored, then:

export class AnotherComponent {

    isCurrentRouteRoomRoute: boolean;

    constructor(private _routeService: RouteService) {
        this.isCurrentRouteRoomRoute = this._routeService.isRoomRouteSource.getValue(); // returns last value stored
        console.log('Current route is room route: ', this.isCurrentRouteRoomRoute);
     }

}

Hope this helped!

like image 197
seidme Avatar answered Oct 13 '22 01:10

seidme


Just looking at your code it looks like something is incorrect here.

  isRoomRoute() {
       if (this.routeService.isRoomRoute(this.routeUrl)) {
           return true;
       }
     }
It looks to me as if this.routeUrl in the above code will likely be undefined unless it is defined elsewhere and defined before . What you could do is instead set a property in the service on the route event and then in the isRoomRoute you would read that property.

@Injectable()
class routeService {
  constructor(private router: Router) {
    // subscribe to event
    router.subscribe((url) => {
      this.routeUrl = url;
      // other things?  sendRoute??
    });

  }

  // Other methods for this class
  isRoomRoute() {
    return this.routeUrl && (this.routeUrl.includes('staff') || this.routeUrl.includes('contractors'));
  }
}

// Usage later where this service has been injected
@Component({
 // ... other properties
 providers: [routeService]
})
class someComponent {
  constructor(private routeService: routeService) {}
  someMethod() {
    this.routeService.isRoomRoute();  // Check if we are in a room route.
  }
}

In a case like this, I am not sure why you can't simply get the URL and parse it when isRoomRoute called instead of setting something on routing events.

like image 29
Goblinlord Avatar answered Oct 13 '22 03:10

Goblinlord