DevGang
Авторизоваться

Переопределение зависимостей в иерархии инжекторов Angular 


Фреймворк Angular упрощает определение направления зависимостей потоков в приложении, тем самым упрощая отладку.

Angular позволяет использовать зависимости, предоставленные через инжектор родительского компонента, среди его дочерних компонентов, внедряя их в конструкторы дочерних компонентов.

Чтобы лучше понять это, давайте рассмотрим практический подход к внедрению зависимостей от родителей к детям.

Чтобы следовать этому руководству, у вас должны быть:

  1. Node.js v10.x
  2. Знание Angular
  3. Знание TypeScript

Начало

Выполните следующую команду, чтобы создать новое приложение Angular:

ng new phone-book

CD в ​​каталог телефонной книги и выполните следующую команду в терминале, чтобы создать модуль для правильной структуры нашего приложения:

ng generate module contacts

Создаем app/contacts/contact.model.ts

//app/contacts/contact.model.ts
export interface Contact {
  id: number;
  name: string;
  phone_no: string;
}

В приведенном выше фрагменте кода используется интерфейс TypeScript. Он создает модель для проверки данных, которые будут возвращены службой продукта.

Выполните следующую команду, чтобы создать службу контактов:

ng generate service contacts/contact

Обновляем contact.service.ts

//app/contacts/contact.service.ts
import { Injectable } from '@angular/core';
import { Contact } from './contact.model';
@Injectable({
  providedIn: 'root'
})
export class ContactService {
  constructor() { }
  getContacts(): Contact[] {
    return [
      { id: 1, name: 'Peter', phone_no: '09033940948' },
      { id: 2, name: 'Sam', phone_no: '07033945948'},
      { id: 3, name: 'Bryce', phone_no: '08033740948' },
      { id: 4, name: 'Lee', phone_no: '090339409321' },
      { id: 5, name: 'Albert', phone_no: '09066894948'  }
    ];
  }
}

Свойство providedIn создает поставщик для службы. В этом случае в providedIn: 'root' указано, что Angular должен предоставлять услугу в корневом инжекторе (т. е. сделать ее доступной во всем приложении).

Теперь ContactService можно ввести в любом месте нашего приложения.

Обновление contact-list.component.ts

//app/contacts/contact-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Contact } from '../contact.model';
import { ContactService } from '../contact.service';
@Component({
  selector: 'app-contact-list',
  templateUrl: './contact-list.component.html',
  styleUrls: ['./contact-list.component.css'],
  providers: [ContactService]
})
export class ContactListComponent implements OnInit {
  contacts: Contact[];
  constructor(private contactService: ContactService) { }
  ngOnInit(): void {
    this.contacts = this.contactService.getContacts();
  }
}

Приведенный выше фрагмент создает экземпляр частного свойства ContactService с использованием ключевого слова new в конструкторе компонента, вызывая метод getContacts внутри contactService метода ngOnInit и присваивает возвращаемое значение свойству contacts.

Обновление contact-list.component.html

//app/contacts/contact-list.component.html
<h3>My Contact List</h3>
<ul>
  <li *ngFor="let contact of contacts">
    {{contact.name}}
  </li>
</ul>
<app-recent-contact></app-recent-contact>

Приведенный выше фрагмент использует директиву ngFor для отображения списка контактов, а также принимает RecentContact в качестве своего прямого дочернего компонента.

Обновление app/contacts/recent-contact.component.ts

//app/contacts/recent-contact.component.ts
import { Component, OnInit } from '@angular/core';
import { ContactService } from '../contact.service';
import { Contact } from '../contact.model';
@Component({
  selector: 'app-recent-contact',
  templateUrl: './recent-contact.component.html',
  styleUrls: ['./recent-contact.component.css']
})
export class RecentContactComponent implements OnInit {
  contacts: Contact[];
  constructor(private contactService: ContactService) { }
  ngOnInit(): void {
    this.contacts = this.contactService.getContacts();
  }
}

RecentContactComponent, будучи прямым дочерним компонентом ContactListComponent, имеет доступ к предоставленной зависимости ContactListComponent, даже не предоставляя ее через свойство providers декоратора @component.

Обновление app/contacts/recent-contact.component.html

//app/contacts/recent-contact.component.html
<h3>My Recent Contacts</h3>
<ul>
  <li *ngFor="let contact of contacts | slice:0:3">
    {{contact.name}}
  </li>
</ul>

Здесь мы применяем SlicePipe к оператору ngFor, чтобы отобразить только последние три контакта.

Запустив приложение с помощью команды ng serve, мы должны иметь список всех контактов и последних контактов, отображаемых в браузере.

Переопределение зависимостей в Angular

Для переопределения зависимостей в Angular требуются два ключевых свойства:

  1. provide - это указывает на зависимость, которую вы хотите переопределить
  2. useClass- это указывает на новую зависимость, которая переопределит существующую зависимость в свойстве providers

Это имеет простой формат:

@Component({
 providers: [
  { provide: Service, useClass : FakeService }
 ]
})

Когда переопределять зависимости

Бывают случаи, когда вы хотите переопределить разрешение вашей конструкции по умолчанию.

Вот несколько типичных случаев:

  1. Переопределение поставщика при написании модульного теста
  2. Добавление уникальной функции в зависимость без внесения в нее изменений

Последнее достигается путем создания новой зависимости из существующей зависимости с помощью ключевого слова extend.

Давайте рассмотрим практический подход к последнему случаю, создав новую службу RecentContactService которая будет расширять ContactService и отфильтровывать данные с помощью метода массива slice, а не канала в шаблоне.

Обновление recent-contact.service.ts

//app/contacts/recent-contact.service.ts
import { Injectable } from '@angular/core';
import { ContactService } from './contact.service';
import { Contact } from './contact.model';
@Injectable({
  providedIn: 'root'
})
export class RecentContactService extends ContactService {
  constructor() {
    super();
  }
  getContacts(): Contact[] {
    return super.getContacts().slice(Math.max(super.getContacts().length - 3, 0))
  }
}

Здесь метод getContacts возвращает последние три элемента из результирующего массива из ContactService.

Теперь мы можем добавить эту новую услугу (зависимость) providers к свойствуRecentContactComponent

Обновление recent-contact.component.ts

//app/contact/recent-contact.component.ts
...
import { RecentContactService } from '../recent-contact.service';
@Component({
  selector: 'app-recent-contact',
  templateUrl: './recent-contact.component.html',
  styleUrls: ['./recent-contact.component.css'],
  providers: [{
    provide: ContactService,
    useClass: RecentContactService
  }]
})
export class RecentContactComponent implements OnInit {
  contacts: Contact[];
  constructor(private recentContactService: RecentContactService) {}
  ngOnInit(): void {
    this.contacts = this.recentContactService.getContacts();
  }
}

Здесь свойство useClass позволяет Angular переопределить нашу предыдущую зависимость (ContactService) нашей новой зависимостью (RecentContactService).

Переопределение зависимостей строковыми значениями

В случае, когда зависимость, которую мы хотим переопределить, является строкой, синтаксис useValue будет удобен.

Скажем, у нашего приложения есть файл DBconfig:

//app/db.config.ts
export interface DBConfig {
  name: string;
  version: number;
}
export const databaseSettings: DBConfig = {
  name: 'MongoDB',
  version: 2.0
};

Чтобы сделать конфигурации базы данных доступными в нашем приложении, нам необходимо предоставить объект InjectionToken:

//app/db.config.ts
import { InjectionToken } from '@angular/core';
export const DB_CONFIG = new InjectionToken<DBConfig>('db.config');
...

Теперь наш компонент может получить доступ к конфигурации базы данных с помощью декоратора @Inject:

//app/app.component.ts
import { Component, Inject } from '@angular/core';
import { DB_CONFIG, databaseSettings, DBConfig } from './db.config';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [{
    provide: DB_CONFIG,
    useValue: databaseSettings
  }]
})
export class AppComponent {
  name: string;
  version: number;
  constructor(@Inject(DB_CONFIG) config: DBConfig) {
    this.name = config.name;
    this.version = config.version;
  }
}


//app/app.component.html
<h4>This app uses {{name}} Database v. {{version}}</h4>
<app-contact-list></app-contact-list>

Переопределение зависимостей во время выполнения

Ключевое слово useFactory позволяет Angular решить, какую зависимость вводить в конструкцию во время выполнения:

import { RecentContactService } from './recent-contact.service';
import { ContactService } from './contact.service';
export function contactFactory(isRecent: boolean) {
  return () => {
    if (isRecent) {
      return new RecentContactService();
    }
    return new ContactService();
  };
}

Приведенный выше фрагмент - это фабрика, которая возвращает либо RecentContactService либо ContactService в зависимости от логического условия.

Теперь мы можем изменить свойство поставщиков RecentContactComponent следующим образом:

...
 providers: [{
    provide: ContactService,
    useClass: contactFactory(true)
  }]
...

Благодаря этому мы можем отменять любую зависимость столько раз, сколько это возможно. Это еще проще, поскольку нам нужно только добавить новые зависимости к contactFactory, настроить условия и, наконец, настроить свойство useClass соответствующего компонента новых зависимостей..

Ограничение переопределения зависимостей во время выполнения

Если какая-либо из этих зависимостей вводит другие зависимости в свой конструктор с предыдущей реализацией contactFactory, Angular выдаст ошибку.

Предполагая, что и RecentContactService, и ContactService зависят от зависимости AuthService, нам придется настроить contactFactory следующим образом:

export function contactFactory(isRecent: boolean) {
  return (auth: AuthService) => {
    if (isRecent) {
      return new RecentContactService();
    }
    return new ContactService();
  };
}

Затем мы должны добавить зависимость AuthService к свойству deps объекта providers в компоненте Recent Contact следующим образом:

...
 providers: [{
    ...
    deps: [AuthService]
  }]
...

Заключение

Работа над крупномасштабным приложением Angular требует хорошего понимания того, как работает внедрение зависимостей. Знание того, как и когда переопределить зависимость в Angular, необходимо при работе с крупномасштабными приложениями Angular, поскольку это помогает избежать ненужного дублирования кода в вашем приложении.

Исходный код этой статьи доступен на GitHub.

Источник:

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться