Angular2: внедрить поставщика в компонент с помощью специального декоратора или аннотации?

Я скрываю вкладки в Ionic 2 для определенных @Page (декоратор Ionic 2), используя простой TabsProvider:

tabs.ts

import { Injectable } from 'angular2/core';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class TabsProvider {
  currentState = new BehaviorSubject<boolean>(true);
  public showTabs(){
    this.currentState.next(true);
  }
  public hideTabs(){
    this.currentState.next(false);
  }
}

Компонент вкладок подписывается на currentState, а TabsProvider внедряется на различные страницы, как показано ниже:

sample-page.ts:

import {Page} from 'ionic-angular';
import { TabsProvider } from './tabs';

@Page({
  ...
})
export class SamplePage {
  tabsProvider: TabsProvider;

  constructor(tabsProvider: TabsProvider) {
    this.tabsProvider = tabsProvider;
  }

  onPageWillEnter(){
    this.tabsProvider.hideTabs();
  }

  onPageWillLeave(){
    this.tabsProvider.showTabs();
  }
}

Этот код практически полностью шаблонный, и было бы намного чище, если бы я мог определить эту функциональность в декораторе (или аннотации), например:

import { Page } from 'ionic-angular';
import { hideTabs } from './tabs';

@hideTabs()
@Page({
  ...
})
export class BuyPage {
}

Но у меня возникли проблемы с определением того, как внедрить TabsProvider и добавить методы onPageWillEnter и onPageWillLeave в SamplePage.

Может ли декоратор (или аннотация) как-то внедрить дополнительных поставщиков Angular?

Самое далекое, что я получил до сих пор:

in tabs.ts:

export function hideTabs() {
  return function(cls: any) {
    cls.prototype.onPageWillEnter = function() {
      this.tabsProvider.hideTabs();
    };
    cls.prototype.onPageWillLeave = function() {
      this.tabsProvider.showTabs();
    };
    return cls;
  }
}

Это дает нам часть того, что мы ищем, но по-прежнему необходимо импортировать и внедрить TabsProvider в качестве конкретного члена экземпляра:

sample-page.ts

import {Page, Events} from 'ionic-angular';
import { hideTabs, TabsProvider } from './tabs';

@hideTabs()
@Page({
  ...
})
export class SamplePage {
  constructor(public tabsProvider: TabsProvider) {
  }
}

Можно ли полностью абстрагировать это в @hideTabs()?

Редактировать:

Соответствующие части компонента вкладок (для всех, кто заинтересован в реализации) pages/tabs/tabs.ts:

import { Page } from 'ionic-angular';
import { TabsProvider } from './tabs';

@Page({
  ...
})
export class TabsPage {

  ...

  currentState: boolean;
  constructor(TabsProvider: TabsProvider) {
    TabsProvider.currentState.subscribe((state: boolean) => {
      this.currentState = state;
    });
  }
}

pages/tabs/tabs.html:

<div [ngClass]="{'hide-tabs': !currentState}">
  <ion-tabs>
    ...
  </ion-tabs>
</div>

pages/tabs/tabs.scss:

.hide-tabs ion-tabbar-section {
    display: none;
}

person Jason Dreyzehner    schedule 07.04.2016    source источник


Ответы (1)


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

  • Используйте Reflect.getOwnMetadata для чтения существующих зависимостей из класса.
  • Добавить в список зависимостей
  • Создайте новый конструктор, который обертывает старый конструктор и работает с этими зависимостями.

tabs.ts:

    import TabsProvider from "./tabs";

    export function hideTabs() {
      return function(cls: Function) : any {

        // Save existing onPageWillEnter and onPageWillLeave inplementations
        let enter = cls.prototype.onPageWillEnter || function () {};
        let leave = cls.prototype.onPageWillLeave || function () {};

        // Create new constructor for class
        const newCls = function (tabsProvider, ...args) {
            this.__hideTabs__tabsProvider = tabsProvider;
            return cls.apply(this, args);
        }

        // Copy prototype to new constructor
        newCls.prototype = constructor.prototype;

        // Copy metadata to new constructor
        let metadatakeys = Reflect.getMetadataKeys(cls);
        metadatakeys.forEach(function (key) {
          Reflect.defineMetadata(key, Reflect.getOwnMetadata(key, cls), newCls)
        });

        // Read dependencies list from 'cls', add our own dependency, and write list to 'newCls'
        let dependencies = Reflect.getOwnMetadata('design:paramtypes', cls);
        dependencies = [TabsProvider].concat(dependencies)
        Reflect.defineMetadata('design:paramtypes', params, newCls);

        // Define new onPageWillEnter and onPageWillLeave implementation which implement tab show/hide functionality
        // and also call the old implementation saved above (if they exist)
        newCls.prototype.onPageWillEnter = function() {
          this.tabsProvider.hideTabs();
          enter.apply(this, arguments);
        };

        newCls.prototype.onPageWillLeave = function() {
          this.tabsProvider.showTabs();
          leave.apply(this, arguments);
        };

        return newCls;
      }
    }

Я бы также рекомендовал переместить декоратор @hideTabs ниже декоратора @Page.

person Nico Burns    schedule 05.12.2016
comment
Вы, сэр, чертов гений! Идея потрясающая, я считаю, что это правильный подход. Благодарю вас! - person Nikola Svitlica; 08.04.2018