Angular 2 RC5 providers at component level get new instance every time - javascript

Until RC5 we could declare providers at component level like this:
#Component({
providers: [SomeService]
})
And every component would get a new instance of SomeService, but now with RC5 the component providers as been deprecated so how we achieve this effect?

One way would be to add a factory method to your service that returns a new instance.
export class SomeService {
constructor(private http: Http) {}
create() {
return new SomeService(this.http);
}
}
That is very much a hack and requires you to call create in your components.
It seems the subscribed solution is to use a Factory Provider.
Essentially you create factory method and provider object:
import { Http } from '#angular2/core';
import { SomeService } from './SomeService';
let someServiceFactory = (http: Http) => {
return new SomeService(http);
};
export let SomeServiceProvider =
{ provide: SomeService,
useFactory: someServiceFactory,
deps: [Http]
};
So now you inject SomeService as you did before, and you will always get a new transient instance.
Import the provider into your module or component and register it as providers: [someServiceProvider]
Or inline it as:
providers: [{
provide: SomeService,
useFactory: (http: Http) => { return new SomeService(http)},
deps: [Http]
}]

According to the quick start here services have not really changed. Still need the 4 core things
A service is created via injectable
2.import that service into component
3.provide that service
4 and construct it in the export

Related

Angular: understanding how DI works in dynamicaly loaded component

I'm experiencing strange behavior of services injected to a component that is loaded dynamically. Consider the following service
#Injectable({
providedIn: 'root'
})
export class SomeService {
private random = Math.random() * 100;
constructor() {
console.log('random', this.random);
}
}
The service is added as a dependency to two components. First component is a part of lazy-loaded module. While second one is loaded dynamically. The following service loads modules with dynamic component
export const COMPONENT_LIST = new InjectionToken<any>('COMPONENT_LIST');
export const COMPONENT_TYPE = new InjectionToken<any>('COMPONENT_TYPE');
#Injectable({
providedIn: 'root'
})
export class LoaderService {
constructor(
private injector: Injector,
private compiler: Compiler,
) { }
getFactory<T>(componentId: string): Observable<ComponentFactory<T>> {
// COMPONENT_LIST is passed through forRoot() from the module that declares first component
const componentList = this.injector.get(COMPONENT_LIST);
const m = componentList.find(m => m.componentId === componentId);
const promise: Promise<ComponentFactory<T>> = (!m) ? null :
m.loadChildren
.then((mod: any) => {
return this.compiler.compileModuleAsync(mod);
})
.then((mf: NgModuleFactory<any>) => {
const mr: NgModuleRef<any> = mf.create(this.injector);
const type: Type<T> = mr.injector.get<Type<T>>(COMPONENT_TYPE); // DYNAMIC_COMPONENT is provided in loaded module
return mr.componentFactoryResolver.resolveComponentFactory<T>(dynamicComponentType);
});
return from(promise);
}
}
In the same module I declare the following component (to place component that is loaded dynamically) and directive
#Component({
selector: 'dynamic-wrapper',
template: `<ng-container dynamicItem></ng-container>`
})
export class DynamicWrapperComponent implements AfterViewInit, OnDestroy {
#Input() itemId: string;
#Input() inputParameters: any;
#ViewChild(DynamicItemDirective)
private dynamicItem: DynamicItemDirective;
private unsubscribe$: Subject<void> = new Subject();
constructor(
private loaderService: LoaderService
) { }
ngAfterViewInit(): void {
this.loaderService.getComponentFactory(this.itemId).subscribe((cf: ComponentFactory<any>) => {
this.dynamicItem.addComponent(cf, this.inputParameters);
});
}
}
...
#Directive({
selector: '[dynamicItem]'
})
export class DynamicItemDirective {
constructor(protected viewContainerRef: ViewContainerRef) { }
public addComponent(cf: ComponentFactory<any>, inputs: any): void {
this.viewContainerRef.clear();
const componentRef: ComponentRef<any> = this.viewContainerRef.createComponent(cf);
Object.assign(componentRef.instance, inputs);
// if I do not call detectChanges, ngOnInit in loaded component will not fire up
componentRef.changeDetectorRef.detectChanges();
}
}
SomeService is defined in a separate module that is imported in both lazy-loaded module with first component and dynamically loaded module.
After both components are initialed I see output of console.log('random', this.random) with two different numbers in console, despite providedIn: 'root' in decorator. What is the reason for such a strange behavior?
Lazy loaded modules won't react to providedIn: 'root' as one might expect.
This option will push services to the AppModule(root module) only if the module that they are imported in is not lazy loaded.
Why do you see different random numbers?
Because the module that SomeService is defined in is initiated twice! (feel free to put console.log in the constructor of this module) - therefore the service is initiated twice (I encountered this issue myself in my project lazy load platform).
Why the lazy module that contain SomeService is initiated twice?
loadChildren or moduleFactory.create(this.injector) don't hold a cache of lazy loaded modules. They initiate a module and attach its injector as a leaf to the input injector.
If the module that contains SomeService was created from moduleFactory.create - you can add a cache layer to return the cached initiated lazy loaded module.
For example:
private lazyLoadedModulesCache: Map<Type<any>, NgModuleRef<any>>;

Angular Dart providers

I want to make GET by web app on angular dart, but I have error in console when I try to open page with app:
Uncaught Error: Invalid argument(s): No provider found for Client0.
And this entrypoint
void main() {
bootstrap(AppComponent, [BrowserClient]);
}
And I have only one component from and one default component, because I use AngularDart Web App - a web app with material design component template from Web Storm:
#Component(
selector: 'watch-counter',
styleUrls: const [
'package:angular_components/app_layout/layout.scss.css',
'watch_counter_component.css'],
templateUrl: 'watch_counter_component.html',
directives: const [
CORE_DIRECTIVES,
materialDirectives,
],
providers: const [UserService]
)
class WatchCounterComponent implements OnInit {
final UserService userService;
WatchCounterComponent(this.userService);
int tabIndex = 0;
void onTabChange(TabChangeEvent event) {
tabIndex = event.newIndex;
}
final loginModalTabs = const<String>[
'Login',
'Registration'
];
#override
Future<Null> ngOnInit() async {
}
}
And with this service
#Injectable()
class UserService {
final Client http;
UserService(this.http);
dynamic extractData(Response resp) => JSON.decode(resp.body)['data'];
Future login(String login, String password) async {
print("login");
}
void register() {
print("register");
}
}
All failings when I add http Client into service.
You are providing BrowserClient but injecting a Client.
Change your bootstrap to:
bootstrap(AppComponent, [
provide(Client, useClass: BrowserClient),
]);
I don't believe BrowserClient is marked with #Injectable(), so you might need:
bootstrap(AppComponent, [
provide(Client, useFactory: () => new BrowserClient()),
]);
Or add in the bootstrap (or the Componente providers list):
const clientProvider = [
ClassProvider(Client, useClass: BrowserClient),
];

How to pass an object to service constructor in NgModule of angular 2/4 app

So here is a service say MyService
MyService has a constructor in its implementation
#Injectable()
export class MyService {
constructor(myObject: MyClass) {}
}
Now myObject is of type MyClass which I need to pass while injecting the service
One way by which I can use MyService is
_myService = new MyService(new Myclass())
and then access methods in MyService
this._myService.someMethod();
But with this approach I have to do this in every component where I use MyService
I want to pass the value to MyService constructor in NgModule in providers array
So that in any component I need MyService I can just use by
export class MyComponent {
constructor( _myService: MyService) {}
this._myService.someMethod();
}
One way is to make the Myclass object also an injectable using ValueProvider
{ provide: Myclass, useValue: new Myclass() }
and add it to the providers. Then DI mechanism can know about the Myclass type and inject correctly. The constructor of service must provide the type for the parameter
export class MyService {
constructor(myObject: MyClass) {}
}
and inject MyService as you want
export class MyComponent {
constructor( _myService: MyService) {}
}
Also you can just create that instance in the service if it is the same for all of them and omit from the constructor.
So I figured out how to do it
It can be done by using useFactory in the following manner
{
provide: MyService,
useFactory: MyServiceFactory
}
export function MyServiceFactory() {
return new MyService(new MyClass());
}
This will pass the MyClass object which is required by MyService constructor and the service can be injected henceforth into any component.
export class MyComponent {
constructor( _myService: MyService) {}
}

Angular 4 Singleton Services

I'm trying to create a service to share the data between two components. I injected the service into root module to make it accessible throughout the application by doing DI into the root module provider. My code looks roughly like this.
Service
#Injectable(){
export class ForumService{
forum: any;
setForum(object){
this.forum = object;
}
getForum(){
return this.forum;
}
}
Root Module
.......
import { ForumService } from 'forumservice';
.......
#NgModule({
declarations: [.....],
imports: [.....],
providers: [....., ForumService],
bootstrap: [AppComponent]
})
export class AppModule{}
Component One
//A bunch of import statements
import { ForumService } from 'forumservice'; //Without this Angular throws a compilation error
#Component({
selector: 'app-general-discussion',
templateUrl: './general-discussion.component.html',
styleUrls: ['./general-discussion.component.css'],
providers: [GeneralDiscussionService] //Not injecting ForumService again
})
export class GeneralDiscussionComponent implements OnInit{
constructor(private forumService: ForumService){}
ngOnInit(){
helperFunction();
}
helperFunction(){
//Get data from backend and set it to the ForumService
this.forumService.forum = data;
console.log(this.forumService.forum); //prints the data, not undefined
}
}
Component Two
//A bunch of import statements
import { ForumService } from 'forumservice'; //Without this Angular throws a compilation error
#Component({
selector: 'app-forum',
templateUrl: './forum.component.html',
styleUrls: ['./forum.component.css'],
providers: []
})
export class ForumComponent implements OnInit {
forumData: any;
constructor(private forumService: ForumService){}
ngOnInit(){
this.forumData = this.forumService.forum; // returns undefined
}
}
Once I navigate from Component One to Component Two I'm expecting "This is a string". However I get undefined. Is it because of the import statements in the component? If I remove that I see a compilation error saying that ForumService is not found.
Instead of using getter and setter, use the object (not primitibe such as string) directly In your components.
Your service
#Injectable(){
export class ForumService{
forum:any = {name:string};
}
Component one
export class GeneralDiscussionComponent implements OnInit{
constructor(private forumService: ForumService){}
ngOnInit(){
this.forumService.forum.name="This is a string";
}
}
Component two
export class ForumComponent implements OnInit {
// forumTitle: string; // do not need this anymore
forum:any; // use the forum.name property in your html
constructor(private forumService: ForumService){}
ngOnInit(){
this.forum = this.forumService.forum; // use the
}
}
I know encapsulating is preferable, and with your current code you are probably encountering some timing problems. But when working with shared data in a service you can two-way bind the variable like above, and your components will be in sync.
EDIT:
Also an important notice, the variable you want to sync between components needs to be an object. instead of forumTitle:string, make it forumTitle:any = {subject:string} or something similar.
Otherwise you need to make your components as listeners for data when data changes in your service.
I'd use BehaviorSubject in this case, should be something like that:
#Injectable(){
export class ForumService{
private _forum: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public forum: Observable<any> = this._forum.asObservable();
setForum(object){
this._forum.next(object);
}
}
Then just bind it in template with async pipe: {{forumService.forum|async}} or subscribe to it.

How to Consume Http Component efficiently in a service in angular 2 beta?

I'm trying to play with Angular 2-beta and I want to work with Http component. But there is a serious problem here:
I read this and
I know in Angular 2(Unlike Angular 1), Http component is not a service that returns a Promise. It returns something called Observable. We know that a Component is better not to use Http directly. Efficient way is to make a service that is responsible to consume Http. But how?! Should this after completing a request, return a promise? (look at here)
Does it make sense at all?!
It's possible with Angular 2 to implement services. They simply correspond to injectable classes as described below. In this case this class can be injected into other elements like components.
import {Injectable} from 'angular2/core';
import {Http, Headers} from 'angular2/http';
import 'rxjs/add/operator/map';
#Injectable()
export class CompanyService {
constructor(http:Http) {
this.http = http;
}
}
You can inject an Http object in it (using its constructor) at the condition you specified HTTP_PROVIDERS when bootstraping the main component of your application:
import {bootstrap} from 'angular2/platform/browser'
import {HTTP_PROVIDERS} from 'angular2/http';
import {AppComponent} from './app.component'
bootstrap(AppComponent, [
HTTP_PROVIDERS
]);
This service can be then injected into a component, as described below. Don't forget to specify it within the providers list of the component.
import { Component, View, Inject } from 'angular2/core';
import { CompanyService } from './company-service';
#Component({
selector: 'company-list',
providers: [ CompanyService ],
template: `
(...) `
})
export class CompanyList {
constructor(private service: CompanyService) {
this.service = service;
}
}
You can then implement a method leveraging the Http object in your service and return the Observable object corresponding to your request:
#Injectable()
export class CompanyService {
constructor(http:Http) {
this.http = http;
}
getCompanies() {
return this.http.get('https://angular2.apispark.net/v1/companies/')
.map(res => res.json());
}
}
The component can then call this getCompanies method and subscribe a callback on the Observable object to be notify when the response is there to update the state of the component (in the same way you did with promises in Angular1):
export class CompanyList implements OnInit {
public companies: Company[];
constructor(private service: CompanyService) {
this.service = service;
}
ngOnInit() {
this.service.getCompanies().subscribe(
data => this.companies = data);
}
}
Edit
As foxx suggested in his comment, the async pipe could be also used to implicitly subscribe on the observable object. Here is the way to use it. First update your component to put the observable object in the attribute you want to display:
export class CompanyList implements OnInit {
public companies: Company[];
constructor(private service: CompanyService) {
this.service = service;
}
ngOnInit() {
this.companies = this.service.getCompanies();
}
}
Use then the async pipe in your template:
#Component({
selector: 'company-list',
providers: [ CompanyService ],
template: `
<ul>
<li *ngFor="#company of companies | async">{{company.name}}</li>
</ul>
`
})
export class CompanyList implements OnInit {
(...)
}
This article in two parts could give more details as well:
http://restlet.com/blog/2015/12/30/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-1/
http://restlet.com/blog/2016/01/06/implementing-an-angular-2-frontend-over-an-apispark-hosted-web-api-part-2/
Hope it helps you,
Thierry
There is no need to convert the observable returned by Http's get() method into a promise. In most cases, the service can simply return the observable.
If we are fetching an array or a primitive type (i.e., string, number, boolean) from the server, we can simplify our controller logic by using the returned observable directly in our template, with the asyncPipe. This pipe will automatically subscribe to the observable (it also works with a promise) and it will return the most recent value that the observable has emitted. When a new value is emitted, the pipe marks the component to be checked for changes, hence the view will automatically update with the new value.
If we are fetching an object from the server, I'm not aware of any way to use asyncPipe, we could use the async pipe, in conjunction with the safe navigation operator as follows:
{{(objectData$ | async)?.name}}
But that looks complicated, and we'd have to repeat that for each object property we wanted to display.
Instead, I suggest we subscribe() to the observable in the component and store the contained object into a component property. We then use the safe navigation operator (?.) or (as #Evan Plaice mentioned in a comment) NgIf in the template. If we don't use the safe navigation operator or NgIf, an error will be thrown when the template first tries to render, because the object is not yet populated with a value.
Note how the service below always returns an observable for each of the get methods.
service.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map'; // we need to import this now
#Injectable()
export class MyService {
constructor(private _http:Http) {}
getArrayDataObservable() {
return this._http.get('./data/array.json')
.map(data => data.json());
}
getPrimitiveDataObservable() {
return this._http.get('./data/primitive.txt')
.map(data => data.text()); // note .text() here
}
getObjectDataObservable() {
return this._http.get('./data/object.json')
.map(data => data.json());
}
}
app.ts
import {Component} from 'angular2/core';
import {MyService} from './my-service.service';
import {HTTP_PROVIDERS} from 'angular2/http';
#Component({
selector: 'my-app',
providers: [HTTP_PROVIDERS, MyService],
template: `
<div>array data using '| async':
<div *ngFor="#item of arrayData$ | async">{{item}}</div>
</div>
<div>primitive data using '| async': {{primitiveData$ | async}}</div>
<div>object data using ?.: {{objectData?.name}}</div>
<div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
})
export class AppComponent {
constructor(private _myService:MyService) { console.clear(); }
ngOnInit() {
this.arrayData$ = this._myService.getArrayDataObservable();
this.primitiveData$ = this._myService.getPrimitiveDataObservable();
this._myService.getObjectDataObservable()
.subscribe(data => this.objectData = data);
}
}
Note: I put "Observable" in the service method names – e.g., getArrayDataObervable() – just to highlight that the method returns an Observable. Normally you won't put "Observable" in the name.
data/array.json
[ 1,2,3 ]
data/primitive.json
Greetings SO friends!
data/object.json
{ "name": "Mark" }
Output:
array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark
Plunker
One drawback with using the async pipe is that there is no mechanism to handle server errors in the component. I answered another question that explains how to catch such errors in the component, but we always need to use subscribe() in this case.

Categories