Angular: How to override the shared module service from different module - javascript

I have multiple modules say SchoolModule, UniversityModule, SharedModule
SharedModule has BaseService to which both SchoolModule and UniversityModule providers are extending
Now when I load my SchoolModule, I want BaseService should get the implementation of schoolService, and the same goes for UniversityModule
Structure
app
-- SharedModule
-- base.service
-- secret.service uses base.service
-- shared.component uses secret.service
-- SchoolModule
-- school.component uses shared.component
-- school.service
-- UniversityModule
-- university.component uses shared.component
-- university.service
StackBlitz
So how I can achieve this with Dependency Injection?

You have to declare your base service class as an abstract class with an abstract method getName()
export abstract class BaseService{
abstract getName(): string { return 'Base Service' }
}
export class SchoolService extends BaseService{
getName(): string { return 'School Service' }
}

Unfortunately, Angular2 can not inject a class from another module without get the import between modules, and if you need to lazy load it, things do not work.
There is a project that load dynamically a component from another module.
It is difficult to import this in to your project, but saves you to not code twice.
This project can be found here.

The problem is that SecretService provided is the one build with build in UniversityModule last imported module in App Module so if you want to have SecretService update with correctly BaseService you have to provide both BaseService and SecretService in university and school component something like this in school.component.ts:
import { SchoolService } from './school.service'
import { BaseService } from '../shared/base.service'
import { SecretService } from '../shared/secret.service'
#Component({
selector: 'app-school',
template: `SchoolName :: <app-shared></app-shared>`,
providers: [
{ provide: BaseService, useClass: SchoolService },
SecretService]
})
export class SchoolComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
and in university.component.ts
import {  UniversityService } from './university.service'
import { BaseService } from '../shared/base.service'
import { SecretService } from '../shared/secret.service'
#Component({
selector: 'app-university',
template: `UniversityName :: <app-shared></app-shared>`,
providers: [{ provide: BaseService, useClass: UniversityService },
SecretService]
})
export class UniversityComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
But why you wanna wrap base service in SecretService instead directly use BaseService in shared component ?
Using BaseService like this in shared.component.ts
import { Component, OnInit } from '#angular/core';
import { BaseService } from './base.service'
#Component({
selector: 'app-shared',
template: `{{name}}`
})
export class SharedComponent implements OnInit {
name: string
constructor(private ss: BaseService) {
this.name = ss.getName()
}
ngOnInit() {
}
}
Using BaseService you will solve this injection problem without any workaround on SecretService

Related

how to pass data from file outside angular library to files inside of angular library

Below are the files of a library named posts-lib which makes http call inside posts.services.ts file and receives a list of posts and display them onto screen. It also consists a component named title.component.ts which is dependent on posts.services.ts and is responsible for displaying content on screen.
All of this works fine, but incase I want to move posts.service.ts folder out of the library and put it inside the app then how can I transfer the data from file which is outside of the library to the file title.component.ts which is dependent on it.
title.component.html
<h1>Testing titles api call</h1>
<ul>
<li *ngFor="let item of data">{{item.title}}</li>
</ul>
title.component.ts
import { Component, OnInit } from '#angular/core';
import { PostsService } from '../posts.service';
#Component({
selector: 'lib-tilte',
templateUrl: './tilte.component.html',
styleUrls: ['./tilte.component.css']
})
export class TilteComponent implements OnInit {
data: any;
constructor(private postData: PostsService) { }
ngOnInit() {
this.postData.getPosts().subscribe((result) => {
console.warn("reult",result);
this.data = result;
})
}
}
posts-lib.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'lib-posts-lib',
template: `
<p>
posts-lib works!
</p>
`,
styles: [
]
})
export class PostsLibComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
posts-lib.module.ts
import { NgModule } from '#angular/core';
import { PostsLibComponent } from './posts-lib.component';
import { TilteComponent } from './tilte/tilte.component';
import { HttpClientModule } from "#angular/common/http";
import { CommonModule } from '#angular/common'
#NgModule({
declarations: [
PostsLibComponent,
TilteComponent
],
imports: [
HttpClientModule,
CommonModule
],
exports: [
PostsLibComponent,
TilteComponent
]
})
export class PostsLibModule { }
posts.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from "#angular/common/http";
#Injectable({
providedIn: 'root'
})
export class PostsService {
url = "https://jsonplaceholder.typicode.com/posts";
constructor(private http: HttpClient) { }
getPosts() {
return this.http.get(this.url);
}
}
public-api.ts
export * from './lib/tilte/tilte.component';
export * from './lib/posts-lib.service';
export * from './lib/posts-lib.component';
export * from './lib/posts-lib.module';
export * from './lib/posts.service';
Ignoring all the issues the commenters are making - all valid - it sounds like you want to just remove the dependency on the service.
Or not, actually.
Yay, options!
Remove usage of the service
Just turn the component around from getting its own data, to being given its data. I.e. #Input.
Still with #Input, but instead, input the service itself rather than the values.
So either:
#Input() public data: any;
or
#Input() public set service(value: PostsService) {
this.postsService = value;
this.getData();
}
private getData(): void {
this.postsService.getPosts().subscribe(...);
}
Either way if you're moving the service out and no longer expecting the service and component to work as a functional pair within a system, you have to extract the component and feed it information instead with #Inputs.
Whether that's just feeding it the data from [a wrapper] service, or feeding it the service itself from wherever it now lives, you still need to give it to it.

Angular 5 service replacement/override

I created a core library for my project containing some components and services. I built the library with ng-packagr. In the consuming project which references the library I built my webapp containing components provided by the library. Nothing special so far. But sometimes I want a component (coming from my lib) calling a method from a Service outside of the lib. Is this possible? Can I somehow inject a service to a component which is defined inside a library?
Cheers
I've achieved this before with something like this:
Your library's service(s) should be defined as an interface rather than as a concrete implementation (as is done in OO languages quite often). If your implementing application will only sometimes want to pass in its own version of the service then you should create a Default service in your library, and use it as so:
import { Component, NgModule, ModuleWithProviders, Type, InjectionToken, Inject, Injectable } from '#angular/core';
export interface ILibService {
aFunction(): string;
}
export const LIB_SERVICE = new InjectionToken<ILibService>('LIB_SERVICE');
export interface MyLibConfig {
myService: Type<ILibService>;
}
#Injectable()
export class DefaultLibService implements ILibService {
aFunction() {
return 'default';
}
}
#Component({
// whatever
})
export class MyLibComponent {
constructor(#Inject(LIB_SERVICE) libService: ILibService) {
console.log(libService.aFunction());
}
}
#NgModule({
declarations: [MyLibComponent],
exports: [MyLibComponent]
})
export class LibModule {
static forRoot(config?: MyLibConfig): ModuleWithProviders {
return {
ngModule: LibModule,
providers: [
{ provide: LIB_SERVICE, useClass: config && config.myService || DefaultLibService }
]
};
}
}
Then in your implementing application you have the ability to pass in the optional config via your library's forRoot method (note that forRoot should only be called once per application and at the highest level possible). Note that I've marked the config parameter as optional, so you should call forRoot even if you have no config to pass.
import { NgModule, Injectable } from '#angular/core';
import { LibModule, ILibService } from 'my-lib';
#Injectable()
export class OverridingService implements ILibService {
aFunction() {
return 'overridden!';
}
}
#NgModule({
imports: [LibModule.forRoot({ myService: OverridingService })]
})
export class ImplementingModule {
}
This was from memory as I don't have the code to hand at the moment so if it doesn't work for any reason let me know.

Angular 2 Failed to compile

i created a new component in angular 2 with this:
ng g component todos
So it created the new component, I went to the component and I noted that I had a new folder with the files:
todos.component.css, todos.component.html, todos.component.spec.ts, todos.component.ts
Then I openened todos.component.ts and it had:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.css']
})
export class TodosComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Then I put the new second line because I am learning with a tutorial:
import { Component, OnInit } from '#angular/core';
import { TodosComponent } from './todos/todos.component';
#Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.css']
})
export class TodosComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
When I did that and I ran the server it showed me this:
Failed to compile.
C:/angular2/proyecto/src/app/todos/todos.component.ts (2,10): Individual declarations in merged declaration 'TodosComponent' must be all exported or all local.
I'd like to know what is it bad? why does it show that error?
Thanks!
You are importing the class into it's own file.
No need to import your own component, you should import it in other files, where you use it.

How to send data from one component to another using a shared service

I wanted to send data using subject to another component (for a earning purpose). I am not able to fetch back the data. Here is my code:
app.component.ts
import { Component } from '#angular/core';
import { shareService } from './share.service';
#Component({
selector: 'my-app',
template: `
<hello></hello>
<button (click)="passData()">
Start
</button>
`,
styleUrls: [ './app.component.css' ],
providers:[shareService]
})
export class AppComponent {
constructor(private service : shareService){}
passData(){
this.service.send("hello");
}
}
hello.component.ts
import { Component, Input } from '#angular/core';
import { shareService } from './share.service';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'hello',
template: `<h1>Hello!</h1>`,
styles: [`h1 { font-family: Lato; }`],
providers:[shareService]
})
export class HelloComponent {
subscription: Subscription;
constructor(private share : shareService){
this.subscription = share.subj$.subscribe(val=>{
console.log(val);
})
}
}
share.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class shareService{
private sub = new Subject();
subj$ = this.sub.asObservable();
send(value: string) {
this.sub.next(value);
}
}
I am not getting the value in console.
Here is the working Demo : DEMO
By putting:
#Component({
.....
providers: [sharedService]
})
in both components, you are creating two distinct instances of the shared service.
Each instance is not 'aware' of the data from each component.
Provide it at module level and create a singleton service:
#NgModule({
....
providers: [sharedService]
})
This way, you inject the service as a single instance in the both components, so they can share it as they will share the data.
Or using the Angular's preferred new way :
Beginning with Angular 6.0, the preferred way to create a singleton
service is to specify on the service that it should be provided in the
application root. This is done by setting providedIn to root on the
service's #Injectable decorator:
#Injectable({
providedIn: 'root',
})
Demo
See also
I dont know why sub$ is used but you dont need that
// just push data to subject. you can use BehavourSubject to initiatte a value.
#Injectable()
export class shareService{
private sub = new Subject();
confirmMission(astronaut: string) {
this.sub.next(astronaut);
}
}
And then in your 2nd component sub scribe it
#Component({
selector: 'hello',
template: `<h1>Hello!</h1>`,
styles: [`h1 { font-family: Lato; }`],
providers:[shareService] // this can be shared in module lebel or componenet level
})
export class HelloComponent {
subscription: Subscription;
constructor(private share : shareService){
this.subscription = share.subj.subscribe(val=>{
console.log(val);
})
}
}
make sure to provide your service in module level or provide it in both the component.

Using input/output events to trigger methods in a parent component in Angular 2

How can a child service notify a parent component of a change? I used to do this in angular 1 by $watching a variable in the child service. Unfortunately, this is no longer possible.
I tried injecting the service back into the component, but this fails, probably due to circular dependencies. Based on what I could find in current documentation, I came up with the code below:
AppComponent
|
SomeComponent
|
SomeService
AppComponent
#Component({
selector: '[app-component]',
templateUrl: 'partials/app.html',
directives: [
SomeComponent
],
providers: [
SomeService
]
})
export class AppComponent {
constructor() { }
}
bootstrap(AppComponent);
SomeComponent
import {Component, Input} from 'angular2/core'
import {SomeService} from '../services/some.service'
#Component({
selector: 'foo',
templateUrl: 'partials/foo.html'
})
export class SomeComponent {
constructor() {}
#Input set someEvent(value) {
console.log(value);
}
}
SomeService
import {EventEmitter, Output} from 'angular2/core'
export class CoreService {
constructor() {
this.someEvent = new EventEmitter();
}
#Output() someEvent: EventEmitter<any>;
public foo() {
this.someEvent.emit(true); // Or next(true)?
}
}
#Output must be used for components only not in services. At this level you can register on this event using the (...) syntax.
From the angular.io documentation (https://angular.io/docs/ts/latest/api/core/Output-var.html):
Declares an event-bound output property.
When an output property emits an event, an event handler attached to that event the template is invoked.
For a service you need to explicitly subscribe on this event, as described below:
import {Component, Input} from 'angular2/core'
import {SomeService} from '../services/some.service'
#Component({
selector: 'foo',
templateUrl: 'partials/foo.html'
})
export class SomeComponent {
constructor(service:CoreService) {
service.someEvent.subscribe((val) => {
console.log(value);
});
}
}

Categories