Какой лучший способ внедрить одну услугу в другую в angular 2 (бета)?
Я знаю, как внедрить сервис в компонент (через @Component), но как я могу использовать DI для передачи сервисов за пределы компонентов?
Другими словами, я не хочу делать это:
export class MyFirstSvc {
}
export class MySecondSvc {
constructor() {
this.helpfulService = new MyFirstSvc();
}
}
export class MyThirdSvc {
constructor() {
this.helpfulService = new MyFirstSvc();
}
}
7 ответов
Да, первое, что нужно добавить @Injectable
декоратор на каждом сервисе, который вы хотите внедрить. На самом деле, Injectable
имя немного коварное Это не означает, что класс будет "инъецируемым", но он будет украшать, чтобы можно было вводить параметры конструктора. Посмотрите эту проблему Github для более подробной информации: https://github.com/angular/angular/issues/4404.
Вот мое понимание механизма впрыска. При настройке @Injectable
декоратор для класса, Angular попытается создать или получить экземпляры для соответствующих типов в инжекторе для текущей цепочки выполнения. На самом деле, существует не только один инжектор для приложения Angular2, но и дерево инжекторов. Они неявно связаны со всем приложением и компонентами. Одной из ключевых особенностей на этом уровне является то, что они связаны друг с другом иерархически. Это дерево инжекторов отображает дерево компонентов. Инжекторы не определены для "услуг".
Давайте возьмем образец. У меня есть следующее приложение:
Составная часть
AppComponent
: основной компонент моего приложения, который предоставляется при создании приложения Angular2 вbootstrap
функция@Component({ selector: 'my-app', template: ` <child></child> `, (...) directives: [ ChildComponent ] }) export class AppComponent { }
Составная часть
ChildComponent
: подкомпонент, который будет использоваться вAppComponent
составная часть@Component({ selector: 'child', template: ` {{data | json}}<br/> <a href="#" (click)="getData()">Get data</a> `, (...) }) export class ChildComponent { constructor(service1:Service1) { this.service1 = service1; } getData() { this.data = this.service1.getData(); return false; } }
Две услуги,
Service1
а такжеService2
:Service1
используетсяChildComponent
а такжеService2
отService1
@Injectable() export class Service1 { constructor(service2:Service2) { this.service2 = service2; } getData() { return this.service2.getData(); } }
@Injectable() export class Service2 { getData() { return [ { message: 'message1' }, { message: 'message2' } ]; } }
Вот краткий обзор всех этих элементов и их связей:
Application
|
AppComponent
|
ChildComponent
getData() --- Service1 --- Service2
В таком приложении у нас есть три инжектора:
- Инжектор приложения, который можно настроить с помощью второго параметра
bootstrap
функция AppComponent
инжектор, который можно настроить с помощьюproviders
атрибут этого компонента. Он может "видеть" элементы, определенные в инжекторе приложения. Это означает, что если поставщик не найден в этом поставщике, он будет автоматически искать этот родительский инжектор. Если в последнем случае не найдено, будет выдана ошибка "поставщик не найден".ChildComponent
инжектор, который будет следовать тем же правилам, что иAppComponent
один. Чтобы внедрить элементы, включенные в цепочку инъекций, выполняемых для компонента, поставщики будут искать сначала в этом инжекторе, затем вAppComponent
один и, наконец, в приложении один.
Это означает, что при попытке ввести Service1
в ChildComponent
конструктор, Angular2 рассмотрит ChildComponent
инжектор, затем в AppComponent
один и, наконец, в приложение один.
поскольку Service2
должен быть введен в Service1
будет выполнена та же обработка разрешения: ChildComponent
инжектор, AppComponent
один и приложение один.
Это означает, что оба Service1
а также Service2
можно указать на каждом уровне в соответствии с вашими потребностями, используя providers
атрибут для компонентов и второй параметр bootstrap
функция для применения инжектора.
Это позволяет обмениваться экземплярами зависимостей для набора элементов:
- Если вы определяете провайдера на уровне приложения, созданный соответствующий экземпляр будет совместно использоваться всем приложением (всеми компонентами, всеми службами,...).
- Если вы определяете поставщика на уровне компонента, экземпляр будет совместно использоваться самим компонентом, его подкомпонентами и всеми "службами", включенными в цепочку зависимостей.
Так что это очень мощно, и вы можете организовывать, как вы хотите и для ваших нужд.
Вот соответствующий plunkr, чтобы вы могли поиграть с ним: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.
Эта ссылка из документации Angular2 может помочь вам: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html.
Надеюсь, это поможет вам (и извините за длинный ответ), Тьерри
- "Предоставляйте" свои услуги где-то на уровне или выше, где вы собираетесь их использовать, например, вы можете поместить их в корень своего приложения, используя
bootstrap()
если вы только один раз экземпляр каждого сервиса (синглтоны). - Использовать
@Injectable()
Декоратор на любом сервисе, который зависит от другого. - Добавьте другие сервисы в конструктор зависимого сервиса.
boot.ts
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {MyFirstSvc} from '../services/MyFirstSvc';
import {MySecondSvc} from '../services/MySecondSvc';
bootstrap(AppComponent, [MyFirstSvc, MySecondSvc]);
MySecondSvc.ts
import {Injectable} from 'angular2/core';
import {MyFirstSvc} from '../services/MyFirstSvc';
@Injectable()
export class MySecondSvc {
constructor(private _firstSvc:MyFirstSvc) {}
getValue() {
return this._firstSvc.value;
}
}
Смотрите Plunker для других файлов.
Что немного странно в Service DI, так это то, что он все еще зависит от компонентов. Например, MySecondSvc
создается, когда компонент запрашивает его, и в зависимости от того, где MyFirstSvc
был "предоставлен" в дереве компонентов, что может повлиять на MyFirstSvc
экземпляр вводится в MySecondSvc
, Это обсуждается более подробно здесь: можете ли вы вводить сервисы в сервисы только через загрузчик?
Сервис считается разделенным между компонентами. Итак, скажем, если у меня есть один сервис, я могу использовать его в разных компонентах.
Здесь В этом ответе я показываю вам один сервис, который принимает данные от одного компонента и отправляет эти данные другому компоненту.
Я использовал концепцию маршрутизации, Shared-Service, Shared-Object. Я надеюсь, что это поможет вам понять основы Share-сервиса.
Примечание: @Injectable decorater используется, чтобы сделать сервис инъекционным.
Boot.ts
import {Component,bind} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS} from 'angular2/router';
import {SharedService} from 'src/sharedService';
import {ComponentFirst} from 'src/cone';
import {ComponentTwo} from 'src/ctwo';
@Component({
selector: 'my-app',
directives: [ROUTER_DIRECTIVES],
template: `
<h1>
Home
</h1>
<router-outlet></router-outlet>
`,
})
@RouteConfig([
{path:'/component-first', name: 'ComponentFirst', component: ComponentFirst}
{path:'/component-two', name: 'ComponentTwo', component: ComponentTwo}
])
export class AppComponent implements OnInit {
constructor(router:Router)
{
this.router=router;
}
ngOnInit() {
console.log('ngOnInit');
this.router.navigate(['/ComponentFirst']);
}
}
bootstrap(AppComponent, [SharedService,
ROUTER_PROVIDERS,bind(APP_BASE_HREF).toValue(location.pathname)
]);
FirstComponent
import {Component,View,bind} from 'angular2/core';
import {SharedService} from 'src/sharedService';
import {Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS} from 'angular2/router';
@Component({
//selector: 'f',
template: `
<div><input #myVal type="text" >
<button (click)="send(myVal.value)">Send</button>
`,
})
export class ComponentFirst {
constructor(service:SharedService,router:Router){
this.service=service;
this.router=router;
}
send(str){
console.log(str);
this.service.saveData(str);
console.log('str');
this.router.navigate(['/ComponentTwo']);
}
}
SecondComponent
import {Component,View,bind} from 'angular2/core';
import {SharedService} from 'src/sharedService';
import {Router,ROUTER_PROVIDERS,RouteConfig, ROUTER_DIRECTIVES,APP_BASE_HREF,LocationStrategy,RouteParams,ROUTER_BINDINGS} from 'angular2/router';
@Component({
//selector: 'f',
template: `
<h1>{{myName}}</h1>
<button (click)="back()">Back<button>
`,
})
export class ComponentTwo {
constructor(router:Router,service:SharedService)
{
this.router=router;
this.service=service;
console.log('cone called');
this.myName=service.getData();
}
back()
{
console.log('Back called');
this.router.navigate(['/ComponentFirst']);
}
}
SharedService и общий объект
import {Component, Injectable,Input,Output,EventEmitter} from 'angular2/core'
// Name Service
export interface myData {
name:string;
}
@Injectable()
export class SharedService {
sharingData: myData={name:"nyks"};
saveData(str){
console.log('save data function called' + str + this.sharingData.name);
this.sharingData.name=str;
}
getData:string()
{
console.log('get data function called');
return this.sharingData.name;
}
}
Я не уверен, что ответ все еще требуется, поэтому я бы попробовал ответить на него.
Рассмотрим следующий пример, где у нас есть Компонент, который использует сервис для заполнения некоторых значений в своем шаблоне, как показано ниже
testComponent.component.ts
import { Component } from "@angular/core"
import { DataService } from "./data.service"
@Component({
selector:"test-component",
template:`<ul>
<li *ngFor="let person of persons">{{ person.name }}</li>
</ul>
})
export class TestComponent {
persons:<Array>;
constructor(private _dataService:DataService){
this.persons = this._dataService.getPersons()
}
}
Приведенный выше код довольно прост, и он будет пытаться получить то, что getPersons возвращает из DataService. Файл DataService доступен ниже.
data.service.ts
export class DataService {
persons:<Array>;
constructor(){
this.persons = [
{name: "Apoorv"},
{name: "Bryce"},
{name: "Steve"}
]
}
getPersons(){
return this.persons
}
Приведенный выше фрагмент кода будет прекрасно работать без использования декоратора @Injectable. Но проблема начнется, когда нашему сервису (в данном случае DataService) потребуются некоторые зависимости, например, для. Http. если мы изменим наш data.service.ts
файл, как показано ниже, мы получим сообщение об ошибке Cannot resolve all parameters for DataService(?). Make sure they all have valid type or annotations.
import { Http } from '@angular/http';
export class DataService {
persons:<Array>;
constructor(){
this.persons = [
{name: "Apoorv"},
{name: "Bryce"},
{name: "Steve"}
]
}
getPersons(){
return this.persons
}
Это как-то связано с тем, как декораторы функционируют в Angular 2. Пожалуйста, прочтите https://blog.thoughtram.io/angular/2015/05/03/the-difference-between-annotations-and-decorators.html чтобы получить в глубоком понимании этого вопроса.
Вышеприведенный код также не будет работать, так как мы также должны импортировать HTTP в наш модуль начальной загрузки.
Но правило большого пальца, которое я могу предложить, состоит в том, что если ваш служебный файл нуждается в зависимости, то вы должны украсить этот класс декоратором @Injectable.
ссылка: https://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html
Каким-то образом @Injectable не работает для меня в Angular 2.0.0-beta.17 при подключении ComponentA -> ServiceB -> ServiceC.
Я выбрал этот подход:
- Ссылка на все услуги в поле поставщиков @ComponentA.
- В ServiceB используйте аннотацию @Inject в конструкторе, чтобы связать ServiceC.
Запустите этот Plunker, чтобы увидеть пример или посмотреть код ниже
app.ts
@Component({selector: 'my-app',
template: `Hello! This is my app <br/><br/><overview></overview>`,
directives: [OverviewComponent]
})
class AppComponent {}
bootstrap(AppComponent);
overview.ts
import {Component, bind} from 'angular2/core';
import {OverviewService} from "../services/overview-service";
import {PropertiesService} from "../services/properties-service";
@Component({
selector: 'overview',
template: `Overview listing here!`,
providers:[OverviewService, PropertiesService] // Include BOTH services!
})
export default class OverviewComponent {
private propertiesService : OverviewService;
constructor( overviewService: OverviewService) {
this.propertiesService = overviewService;
overviewService.logHello();
}
}
Обзор-service.ts
import {PropertiesService} from "./properties-service";
import {Inject} from 'angular2/core';
export class OverviewService {
private propertiesService:PropertiesService;
// Using @Inject in constructor
constructor(@Inject(PropertiesService) propertiesService:PropertiesService){
this.propertiesService = propertiesService;
}
logHello(){
console.log("hello");
this.propertiesService.logHi();
}
}
свойства-service.ts
// Using @Injectable here doesn't make a difference
export class PropertiesService {
logHi(){
console.log("hi");
}
}
Первое, что нужно сделать, это аннотировать все услуги с помощью @Injectable
аннотаций. Обратите внимание на круглые скобки в конце аннотации, без этого это решение не будет работать.
Как только это будет сделано, мы можем внедрить сервисы друг в друга, используя конструктор:
@Injectable()
export class MyFirstSvc {
}
@Injectable()
export class MySecondSvc {
constructor(helpfulService: MyFirstSvc) {
}
}
@Injectable()
export class MyThirdSvc {
constructor(helpfulService: MyFirstSvc) {
}
}
Во-первых, вы должны предоставить свои услуги
Вы можете предоставить это либо в методе начальной загрузки:
bootstrap(AppComponent,[MyFirstSvc]);
или в компоненте приложения, или в любом другом компоненте, в зависимости от ваших потребностей.
@Component({
...
providers:[MyFirstSvc]
}
...
затем просто добавьте свой сервис с помощью конструктора:
export class MySecondSvc {
constructor(private myFirstSvc : MyFirstSvc ){}
}