4

I'm trying to implement a title service for my angular 10 app. I need to subscribe to router events, grab the activated route's component, see if it implements title() getter and then use it to set the page's title. Sounds easy...

The code:

    this.router.events
      .pipe(
        filter((event) => event instanceof NavigationEnd),
        map(() => this.rootRoute(this.route)),
        filter((route: ActivatedRoute) => route.outlet === "primary"),
        filter(
          (route: ActivatedRoute) =>
            ComponentWithTitleBase.isPrototypeOf(route.component as any)
        ),
        map((route) => (route.component as unknown) as ComponentWithTitleBase),
        tap(console.dir)
      )
      .subscribe((comp: ComponentWithTitleBase) => {
        this.titleSvc.title = comp.title;
      });

But the comp.title is ALWAYS undefined. Even though the component does implement get title() getter:

export class AboutComponent extends ComponentWithTitleBase implements OnInit {
  get title(): string {
    return "About the demo";
  }

  ...
}

I see that console.dir outputs AboutComponent. What am I missing here?

AlexB
  • 4,167
  • 4
  • 45
  • 117

3 Answers3

2

Based on @yurzui's idea, you can use a directive for this:

activated-component.directive.ts

@Directive({
  selector: 'router-outlet'
})
export class ActivatedComponentsDirective {

  constructor(r: RouterOutlet, titleService: TitleService) {
    r.activateEvents.pipe(
      // takeUntil(r.destroyed),
    ).subscribe(compInstance => compInstance.title && titleService.newTitle(compInstance.title))
  }

  ngOnDestroy () {
    // destroyed.next;
    // destroyed.complete();
  }
}

title.service.ts

@Injectable({
  providedIn: 'root'
})
export class TitleService {

  private src = new Subject<string>();

  newTitle (t: string) {
    this.src.next(t);
  }

  constructor() { this.initConsumer() }

  private initConsumer () {
    this.src.pipe(
      /* ... */
    ).subscribe(title => {
      console.log('new title', title);
    })
  }
}

ng-run demo.

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • For some reason, it doesn't work in other module. Basically, I have AppModule/router-outlet => ChildModule/router-outlet and it works for AppModule but not for subsequent outlets... Can't figure out why. The directive is in separate module, so it's imported in both. – AlexB Jul 28 '20 at 23:37
  • sorry, all good! it was because of CLI not picking up module changes. Thank you! – AlexB Jul 28 '20 at 23:40
1

there is a little misunderstanding. when you console.dir the .component you get not an instance of AboutComponent but a class of it. thus your getter should be static if you want to access it as component.title

static get title(): string {
  return "About the demo";
}
Andrei
  • 10,117
  • 13
  • 21
  • 1
    not sure why, but you can't get an instance from router logic. you can also try `component.prototype.title` as your problem solution if you like it more – Andrei Jul 27 '20 at 20:40
0

With the @bespunky/angular-zen library you can do this:

In the template holding the router outlet:

<router-outlet publishComponent></router-outlet>

Then, in the component that needs access to the instance:

import { Component     } from '@angular/core';
import { NavigationEnd } from '@angular/router';
import { RouteAware    } from '@bespunky/angular-zen/router-x';

@Component({
    selector   : 'app-demo',
    templateUrl: './demo.component.html',
    styleUrls  : ['./demo.component.css']
})
export class DemoComponent extends RouteAware
{
    protected onNavigationEnd(event: NavigationEnd): void
    {
        const currentInstance = this.componentBus.instance();

        console.log(`Navigation ended. Current component instance:`, currentInstance )
    }
}

It is open-source and you can install the library like this:

npm install @bespunky/angular-zen

Here's a live example with more details.

If your router outlet has a name, you can pass it to the instance() method and retrieve the corresponding outlet's component.

Shy Agam
  • 1,285
  • 1
  • 13
  • 37