ngOnInit not being called when Injectable class is Instantiated - javascript

Why isn't ngOnInit() called when an Injectable class is resolved?
Code
import {Injectable, OnInit} from 'angular2/core';
import { RestApiService, RestRequest } from './rest-api.service';
#Injectable()
export class MovieDbService implements OnInit {
constructor(private _movieDbRest: RestApiService){
window.console.log('FROM constructor()');
}
ngOnInit() {
window.console.log('FROM ngOnInit()');
}
}
Console Output
FROM constructor()

Lifecycle hooks, like OnInit() work with Directives and Components. They do not work with other types, like a service in your case. From docs:
A Component has a lifecycle managed by Angular itself. Angular creates it, renders it, creates and renders its children, checks it when its data-bound properties change and destroy it before removing it from the DOM.
Directive and component instances have a lifecycle as Angular creates, updates, and destroys them.

I don't know about all the lifecycle hooks, but as for destruction, ngOnDestroy actually get called on Injectable when it's provider is destroyed (for example an Injectable supplied by a component).
From the docs :
Lifecycle hook that is called when a directive, pipe or service is destroyed.
Just in case anyone is interested in destruction check this question:

Adding to answer by #Sasxa,
In Injectables you can use class normally that is putting initial code in constructor instead of using ngOnInit(), it works fine.

Note: this answer applies only to Angular components and directives, NOT services.
I had this same issue when ngOnInit (and other lifecycle hooks) were not firing for my components, and most searches led me here.
The issue is that I was using the arrow function syntax (=>) like this:
class MyComponent implements OnInit {
// Bad: do not use arrow function
public ngOnInit = () => {
console.log("ngOnInit");
}
}
Apparently that does not work in Angular 6. Using non-arrow function syntax fixes the issue:
class MyComponent implements OnInit {
public ngOnInit() {
console.log("ngOnInit");
}
}

I had to call a function once my dataService was initialized, instead, I called it inside the constructor, that worked for me.

Related

Does Injector create a new instance of the service with ActivatedRoute

Background
I am using inheritance in Angular, the major issue was that I initially had to pass services from parent to child, something like
// Parent
export class ParentComponent {
protected myService: MyService;
constructor(private myService: MyService) {
}
}
// Child
export class ChildComponent extends ParentComponent {
constructor(private myService: MyService) {
super(myService);
}
}
To avoid this I found a different approach using Injector from #angular/core. In this approach we follow the below steps
Create a service
app-injector-service.ts
import { Injector } from '#angular/core';
export class AppInjectorService {
private static injector: Injector;
static setInjector(injector: Injector) {
AppInjectorService.injector = injector;
}
static getInjector(): Injector {
return AppInjectorService.injector;
}
}
in the main.ts file
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then((ref) => {
AppInjectorService.setInjector(ref.injector);
})
.catch((err) => console.error(err));
and parent class file would look like
export abstract class ParentComponent {
protected myService: MyService;
constructor() {
const injector = AppInjectorService.getInjector();
this.myService = injector.get(MyService);
}
}
With this approach, no need to pass service to the super call! Amazing
My Issue
The approach above worked very well untill I needed to extract the route param from the activated route so I followed the same structure. To my surprise It is not working. I tested with manually injecting the ActivatedRoute in the child class and the parameter exists. I dont understand why the parameter is not available in the class when we inject ActivatedRoute using the AppInjector. Ofcourse I can go back to injecting it in the child class then passing it to the super function so that it can be accessed by the Parent class but this is what I was trying to avoid...
Below is a stackblitz demo showing the issue, click on the route and you will notice that we get back null from the ActivatedRoute injected in the parent class but a value in the ActivatedRoute in the child class. Basically I am trying to find if there is anything am doing wrong or is there something am missing while using the Injector
Demo
There are two problems in your code: using inheritance for components and using a non-singleton service as a singleton service.
you are setting up injector in your service AppInjectorService.setInjector(ref.injector); after app is bootstrapped but what happens when one of your component is directly used in your root component(AppComponent) and that component inherited that parent comp class, as you are using injector inside parent to resolve all required dependencies, it won't work because you do not have access to injector by that time, setting up injector in constructor using that static method is a bad idea because constructors of components can be invoked even though they are not being rendered yet and they also may be called during some bootstrapping process so you see the problem? your parent component's constructor is called before AppInjectorService.setInjector(ref.injector);. This may have worked if all the components inheriting your parent component were initialized lazily/dynamically so that app is bootstraped and your injector is setup in your service before using it.
Inheritance is a bitch, especially with components it'll bite you in the ass sooner or later, use DI to reduce repetitive code, in your case you can create a service that can contain all the required dependencies and inject it into whatever component you want to, the lines of code to inherit a component vs injecting a service are typically the same though this comes with the limitation that you can't mix and match services of different scopes(Singletons/Module level/Component level) but that is expected to be honest.
Second problem is using a contextual service out of context, ActivatedRoute is meant to be injected into components that are part of route config, let's takte example of this config:
{ path: ":id", component: ChildComponent }
if you inject ActivatedRoute into ChildComponent, it'll work as expected but if you inject it into AppComponent, it won't work it's not gonna throw any errors but when you'll try to listen to changes in paramMap, you won't get it because it AppComponent is not connected to that specific path. If you want to globally listen to changes in route then prefer Router.
One thing to note here, when you pass that route config to RouterModule, it'll now be responsible for creating instances of components based on route path and it can then inject an injector with a newly created dependency(like ActivatedRoute) into that component so injecting dependencies like those anywhere may not work.

Angular service call a child method in constructor

Is there a way to call a child class method from an abstract service? When I do this, the if statement doesn't execute because onInit doesn't exist. I am not sure why it doesn't exist though. Maybe there is an "angular" way of doing this instead of triggering this on the init. Or maybe I just need to call the onInit manually in the component instead. Basically what I am doing is trying to get the initial application data from the server.
#Injectable({providedIn:'root'})
export class RootService {
public constructor(httpClient: HttpClient) {
if(typeof this.onInit === 'function') {
this.onInit()
}
}
}
#Injectable({providedIn:'root'})
export class MyService extends RootService {
public onInit() {
// Do stuff
}
}
#Component()
export MyComponent {
public constructor(myService: MyService) {}
}
Services do not have lifecycle events. However, components have lifecycle hooks such as:
ngOnChanges()
ngOnInit()
ngDoCheck()
...
So you can load data when your component is initialized. If yes, then just use ngOnInit:
import { Component, OnInit } from '#angular/core';
#Component()
export MyComponent implements OnInit {
yourData;
public constructor(myService: MyService) {}
ngOnInit(){
myService.loadData()
.subscribe(s=> yourData = s);
}
}
According to Angular docs:
OnInit is a lifecycle hook that is called after Angular has
initialized all data-bound properties of a directive.
If you add the service to the providers of your component, it will be in your components scope then you can call onInit in your service as well.
But the downside of this is you can no longer share the same instance of the service among your components.
This will be valid if your service only serves one component.

Angular 5: Using Service from inside Custom Decorator Function

I'm creating a #Log() Decorator Function for debugging purposes;
I want that Decorator to delegate some of it's logic to a LoggingService that in turn depends on other services from the app...
I've been trying a lot of different things, the simplest/most straightforward way was to cache the Main (or Shared) Module's Injector as a static prop on the module itself (see StackBlitz example linked below), and that works for lazy-loaded modules, but not for eagerly loaded ones...
Non-working poc: https://stackblitz.com/edit/angular-j1bpvx?file=app%2Fdecorator.ts
Is there a way I could mkae use of that Service in there??
Thanks!
Class decorator is executed once on class definition. In order to avoid race condition when calling AppModule.injector.get(LoggingService) it should be moved to the place where AppModule.injector is already defined, i.e. class method.
It should be:
constructor.prototype[hook] = function (args) {
const loggingService = AppModule.injector.get(LoggingService);
loggingService.log({ ... })
...
This also creates tight coupling with AppModule and prevents the units from being reused or tested separately from it. It's recommended to use another object to hold injector property, e.g. assign injector not in main but in child module that is imported into AppModule:
export class InjectorContainerModule {
static injector: Injector;
constructor(injector: Injector) {
InjectorContainerModule.injector = injector;
}
}
Try stackblitz fixed
This will print
LoggingService: HelloComponent - ngOnInit was called
Minor changes - basically using ReflectiveInjector as in angular Injector#example
import { ReflectiveInjector } from '#angular/core';
const injector = ReflectiveInjector.resolveAndCreate([
{provide: 'loggingService', useClass: LoggingService}
]);
const loggingService = injector.get('loggingService');
I am sure you can use useExisting and use LoggingService as provider in your app module.

angular 4. remove a component in the constructor based on an if

I want to add protection to components.
If a user doesn't have permission to see this component it wont be rendered.
I have tried putting my if in the constructor and return false but it still renders.
I also added the if to the template itself but then I didn't see the view but the component is still alive and it adds complexity to the code as I need to maintain several places of the same if.
Is there a way to tell the component to not render at all ?
constructor( private userService: UserService) {
if (this.userService.isAllowed("see_trade_groups") === false) {
return;
}
}
For that purpose you can see CanActivate. Put it on the component route and it will do the job you want.
In that you can write a logic, based on which the route will be navigated or not.
Component compilation lifecycle is handled by Angular compiler, so a component is unable to control its own lifecycle, and it should be controlled from the outside.
A common way to handle this is to use router. The lifecycle in route components differs from regular components because it's handled by router; they are attached to <router-outlet> component. It's possible to prevent compilation in route components but not in regular components.
Otherwise this should be handled with a directive. ngIf is built-in way to prevent the compilation of regular components.
So it becomes
<foo *ngIf="userService.isAllowed('see_trade_groups')"></foo>
Since this requires to inject userService to parent component every time it's needed, this will result in a lot of boilerplate code. An appropriate solution is to create a directive that behaves similarly to ngIf - or extend it to provide desired functionality:
import {Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '#angular/core';
import {NgIf, NgIfContext} from '#angular/common';
...
#Directive({
selector: '[role]'
})
class Role extends NgIf {
#Input() role: string;
constructor(
viewContainer: ViewContainerRef,
templateRef: TemplateRef<NgIfContext>
public userService: User
) {
super(viewContainer, templateRef);
}
ngOnChanges({ role }: SimpleChanges) {
this.ngIf = this.userService.isAllowed(role);
// can also subscribe to some observable to add/remove a component any time
}
}
Which is used like:
<foo *role="'see_trade_groups'"></foo>
Notice that Role is * structural directive. This allows it to control the compilation of an element it was specified on, similarly to how ngIf and ngFor do.

Angular 2 - testing a component with #input used in ngOnInit lifecycle hook

Currently I am trying to test a child component which is accepting an input from the host component, and used within the ngOnInit life cycle hook like the code below.
#Component({
selector: 'my-child-component',
template: '<div></div>'
})
class ChildComponent implements OnInit {
#Input() myValue: MyObject;
transformedValue: SomeOtherObject;
ngOnInit():void {
// Do some data transform requiring myValue
transformedValue = ...;
}
}
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class HostComponent {
someValue: MyObject = new MyObject(); // how it is initialized it is not important.
}
How should the ChildComponent be tested in this case where myValue needs the to be present upon creation while being able to have access to ChildComponent.transformedValue for assertion.
I tried creating the ChildComponent using the Angular TestBed class like this
componentFixture = testBed.createComponent(LoginFormComponent)
however the ngOnInit would have already been called up to the point where I call
fixture.componentInstance.myValue = someValue;
I also tried creating a fixture of the HostComponent, and while that works, I got stuck at getting access to the ChildComponent instance that was created, which i require to perform assertions on the ChildComponent.transformedValue field.
Help is greatly appreciated!
Thanks a lot!
Angular offers the ability to inject children components to their parent components using the #ViewChild() decorator. See https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
By updating the TestHostcomponent (that is written within the .spec.ts file) to the following
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class TestHostComponent {
#ViewChild(MyChildComponent)
childComponent: MyChildComponent;
}
it exposes its child component instance ( and its variables ), making the assertion of the 'transformedValue' possible, as per below.
componentFixture = testBed.createComponent(TestHostComponent)
expect(componentFixture.componentInstance.childComponent.transformedValue).toEqual(...someValue);

Categories