3

I have several routes that are nothing more than a static page.
On each of these routes (over 50) I have to call a couple of methods (and more) on two different services when the route is initiated.
A simple working solution is that on each page I call the ngOnInit method and call the aforementioned methods.
The issue is that this means copying and pasting the same code over 50 different files. Copying and pasting is bad, it is not maintainable.

To make an example:

I have the page "FAQ" (manually assigned id: 52) and the page "Find Us" (manually assigned id: 13). These are 2 different routes.
I have the service "Editor", that is used to edit those page from an Admin panel and it needs to keep track of what page I'm seeing.
I have the service "Cache", that checks if the page was already queried to the backend before or if it needs to be pulled from the server.
Both these service want to know the ID that I manually assigned to that route.
The ID in this specific case is used to query the database/API, however this detail is not question-centric and shouldn't invalidate the answer for other people with similar issues.

// Page FAQ
id: number
constructor() {
  this.id = 52; // 52 is the id that I assigned to the page FAQ
}
ngOnInit () {
  editorService.keepTrack(this.id);
  editorService.moreMethod().notMaintainable().whatIfIChangeSomething(this.id/0);
  cacheService.keepTrack(this.id);
}

// Page Find Us
id: number
constructor() {
  this.id = 13; // 13 is the id that I assigned to the page Find Us
} 
ngOnInit () {
  editorService.keepTrack(this.id);
  editorService.moreMethod().notMaintainable().whatIfIChangeSomething(this.id/0);
  cacheService.keepTrack(this.id);
}

Now, this is a simplified example as I don't see the need to overload the question with details that are not related to the issue at hand.

The only solution that I can think of, is to make a third service. This will call the methods on other two.
So in every page I only have to call this single service method on the ngOnInit and leave the implementation about how to call the other two to this third service.
This would minimize copying and pasting and would leave the implementation in a single manteinable file.

To make an example:

// Page FAQ v2
id: number
constructor() {
  this.id = 52;
}
ngOnInit() {
  inceptionService.doStuff(this.id);
}

// Page Find Us v2
id: number
constructor() {
  this.id = 13;
}
ngOnInit() {
  inceptionService.doStuff(this.id);
}

// InceptionService
export class InceptionService {
  doStuff(data) {
  editorService.keepTrack(data);
  editorService.moreMethod().notMaintainable().whatIfIChangeSomething(data/0);
  cacheService.keepTrack(data);
  }
}

The question is:
Is there a better way to do this? I have a feeling that I'm not doing it using the best approach.
It still feels like I'm abusing the services.

Kunepro
  • 418
  • 2
  • 4
  • 18
  • It would be easier to follow if you could post some code that demonstrates what you're doing in `ngOnInit()`. – Günter Zöchbauer Nov 02 '16 at 13:18
  • I added the example. – Kunepro Nov 02 '16 at 13:39
  • Where does the `52` and `13` come from? Do you need the component for that or is there a way to get it at the top level like a global service? – Günter Zöchbauer Nov 02 '16 at 13:40
  • "52" and "13" are the ID to query on the database. They are manually assigned to the component, but could just be the name of the route itself, like "faq" and "find-us". Currently this information is stored inside the component, but by no means needs to stick there if I find another approach. – Kunepro Nov 02 '16 at 13:47

3 Answers3

8

You can create a base class for these routes and make your components inherit from this base class.

export class BaseComponent {
    ngOnInit() {
        //your service calls
    }
}

export class MyStaticComponent1 extends BaseComponent {
    ngOnInit() {
        super.ngOnInit();
        //component specific calls
    }
}
Michal Dymel
  • 4,312
  • 3
  • 23
  • 32
  • The problem with this approach is that I can no longer use ngOnInit inside the component itself for component's specific tasks. If I do, I would lose the extended ngOnInit and have no benefit from the extend. Since ngOnInit is an Angular2 specific method, I would prefer to abuse services rather than deal with this constrain. – Kunepro Nov 02 '16 at 14:47
  • 1
    You can call `super.ngOnInit()` from the component and then do specific tasks – Michal Dymel Nov 02 '16 at 15:05
1

You could subscribe to route events and call your methods from there. (see How to detect a route change in Angular 2?)

This also works from within a service.

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I like this approach, I only find it limiting in the fact that I can't hard code (or read hard coded) stuff in the component. For example I can't use a number as ID to query the DB. I would have to use the route, and that might be an issue if I want to change the route from "learn-english-for-dummies" to "learn-aladeen-for-aladeens", as I would also have to change the reference in the DB. – Kunepro Nov 02 '16 at 14:23
  • Guards might work for this as well https://angular.io/docs/ts/latest/guide/router.html#!#guards The components type is available from routes. There should be ways to get for example static values or to call static methods on these components. – Günter Zöchbauer Nov 02 '16 at 14:26
  • I'm not able to make the guard get a property or method from a component, can you give an example? – Kunepro Nov 02 '16 at 16:10
  • With `canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {` the route has a `component` property https://angular.io/docs/ts/latest/api/router/index/ActivatedRouteSnapshot-interface.html. I don't know what can be done with this component reference or what you need to do with it (don't know TS very well). – Günter Zöchbauer Nov 02 '16 at 16:14
  • I tried, but there's nothing there, as also Alex Rickabaugh @alxhub specifies: "No, since canActivate is deciding whether or not the component can actually be instantiated, there is no instance yet.", he means that we can't see inside the component to use the data or methods. Also router.events didn't help, but I found a different approach that satisfies all the needs based on this (see my answer, I'm still typing it). – Kunepro Nov 02 '16 at 18:13
1

Michal Dymel's alternative is valid and would satisfy all my needs with a better approach than my "InceptionService", but I felt that Günter Zöchbauer's answer used a better, more maintainable and more Angular approach, although his answer was incomplete.
Günter Zöchbauer answer also gave me the input to find this other solution that Alex Rickabaugh @alxhub helped me finalise.

Here I'm referring to angular-cli structure.

on app.component.html replace
<router-outlet>
with
<router-outlet (activate)="newComponentActivated($event)">

on app.component.ts

newComponentActivated(component) {
  // Updating tracked page ID
  this.editorService.updateCurrentPage(component);
}

in the "editor service"

updateCurrentPage(component) {
  console.log(component);
}

Now every time that the route changes an event will notify the editor service with a copy of the component, including arguments and methods.
This means that there is no need to extend or implement anything in the routed components.

Kunepro
  • 418
  • 2
  • 4
  • 18