角度2-ng-bootstrap如何为NgbRadio指令提供NgbRadioGroup和NgbButtonLabel? [英] Angular 2 - How does ng-bootstrap provide the NgbRadioGroup and NgbButtonLabel to their NgbRadio directive?

查看:78
本文介绍了角度2-ng-bootstrap如何为NgbRadio指令提供NgbRadioGroup和NgbButtonLabel?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是标签代码:

import {Directive} from '@angular/core';

@Directive({
  selector: '[ngbButtonLabel]',
  host:
      {'[class.btn]': 'true', '[class.active]': 'active', '[class.disabled]': 'disabled', '[class.focus]': 'focused'}
})
export class NgbButtonLabel {
  active: boolean;
  disabled: boolean;
  focused: boolean;
}

这是单选按钮代码:

import {Directive, forwardRef, Input, Renderer2, ElementRef, OnDestroy} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

import {NgbButtonLabel} from './label';

const NGB_RADIO_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => NgbRadioGroup),
  multi: true
};

let nextId = 0;

/**
 * Easily create Bootstrap-style radio buttons. A value of a selected button is bound to a variable
 * specified via ngModel.
 */
@Directive({
  selector: '[ngbRadioGroup]',
  host: {'data-toggle': 'buttons', 'role': 'group'},
  providers: [NGB_RADIO_VALUE_ACCESSOR]
})
export class NgbRadioGroup implements ControlValueAccessor {
  private _radios: Set<NgbRadio> = new Set<NgbRadio>();
  private _value = null;
  private _disabled: boolean;

  get disabled() { return this._disabled; }
  set disabled(isDisabled: boolean) { this.setDisabledState(isDisabled); }

  /**
   * The name of the group. Unless enclosed inputs specify a name, this name is used as the name of the
   * enclosed inputs. If not specified, a name is generated automatically.
   */
  @Input() name = `ngb-radio-${nextId++}`;

  onChange = (_: any) => {};
  onTouched = () => {};

  onRadioChange(radio: NgbRadio) {
    this.writeValue(radio.value);
    this.onChange(radio.value);
  }

  onRadioValueUpdate() { this._updateRadiosValue(); }

  register(radio: NgbRadio) { this._radios.add(radio); }

  registerOnChange(fn: (value: any) => any): void { this.onChange = fn; }

  registerOnTouched(fn: () => any): void { this.onTouched = fn; }

  setDisabledState(isDisabled: boolean): void {
    this._disabled = isDisabled;
    this._updateRadiosDisabled();
  }

  unregister(radio: NgbRadio) { this._radios.delete(radio); }

  writeValue(value) {
    this._value = value;
    this._updateRadiosValue();
  }

  private _updateRadiosValue() { this._radios.forEach((radio) => radio.updateValue(this._value)); }
  private _updateRadiosDisabled() { this._radios.forEach((radio) => radio.updateDisabled()); }
}


/**
 * Marks an input of type "radio" as part of the NgbRadioGroup.
 */
@Directive({
  selector: '[ngbButton][type=radio]',
  host: {
    '[checked]': 'checked',
    '[disabled]': 'disabled',
    '[name]': 'nameAttr',
    '(change)': 'onChange()',
    '(focus)': 'focused = true',
    '(blur)': 'focused = false'
  }
})
export class NgbRadio implements OnDestroy {
  private _checked: boolean;
  private _disabled: boolean;
  private _value: any = null;

  /**
   * The name of the input. All inputs of a group should have the same name. If not specified,
   * the name of the enclosing group is used.
   */
  @Input() name: string;

  /**
   * You can specify model value of a given radio by binding to the value property.
   */
  @Input('value')
  set value(value: any) {
    this._value = value;
    const stringValue = value ? value.toString() : '';
    this._renderer.setProperty(this._element.nativeElement, 'value', stringValue);
    this._group.onRadioValueUpdate();
  }

  /**
   * A flag indicating if a given radio button is disabled.
   */
  @Input('disabled')
  set disabled(isDisabled: boolean) {
    this._disabled = isDisabled !== false;
    this.updateDisabled();
  }

  set focused(isFocused: boolean) {
    if (this._label) {
      this._label.focused = isFocused;
    }
  }

  get checked() { return this._checked; }

  get disabled() { return this._group.disabled || this._disabled; }

  get value() { return this._value; }

  get nameAttr() { return this.name || this._group.name; }

  constructor(
      private _group: NgbRadioGroup, private _label: NgbButtonLabel, private _renderer: Renderer2,
      private _element: ElementRef) {
    this._group.register(this);
  }

  ngOnDestroy() { this._group.unregister(this); }

  onChange() { this._group.onRadioChange(this); }

  updateValue(value) {
    this._checked = this.value === value;
    this._label.active = this._checked;
  }

  updateDisabled() { this._label.disabled = this.disabled; }
}

请注意

@Directive({
  selector: '[ngbButton][type=radio]',
  host: {
    '[checked]': 'checked',
    '[disabled]': 'disabled',
    '[name]': 'nameAttr',
    '(change)': 'onChange()',
    '(focus)': 'focused = true',
    '(blur)': 'focused = false'
  }
})

没有提供程序部分,但构造函数具有NgbRadioGroup和NgbButtonLabel.此外,在使用指令时,请不要使用ngbButtonLabel,如下所示:

has no providers section, but the constructor has a NgbRadioGroup and NgbButtonLabel. Further more, when using the directives, leaving off the ngbButtonLabel like this:

<div [(ngModel)]="model" ngbRadioGroup>
  <label>
    <input ngbButton type="radio" name="radio" [value]="values[0]"/> {{ values[0] }}
  </label>
</div>

导致NgbButtonLabel的否提供程序!错误.我缺少什么声明?这是他们完整存储库的链接: https://github.com/ng-bootstrap/ng -bootstrap

causes a No provider for NgbButtonLabel! error. What piece of declaration am I missing? Here is a link to their full repository: https://github.com/ng-bootstrap/ng-bootstrap

推荐答案

ng-bootstrap 包期望元素

<input ngbButton type="radio" ...>

在其上提供了NgbRadio指令的

,将具有在其上提供了NgbButtonLabel指令的父元素.

, on which you provided NgbRadio directive, will have parent element on which you provided NgbButtonLabel directive.

因此您的模板应如下所示:

So your template should looks like:

<label ngbButtonLabel> <======== add ngbButtonLabel attribute
  <input ngbButton type="radio" name="radio" [value]="values[0]"/> {{ values[0] }}
</label>

要了解为什么会这样,您需要了解

To understand why this is so you need to know how angular gets dependencies from hierarchical tree of elements.

假设我们在根组件中具有以下模板:

Let's say we have the following template in our root component:

app.component.html

<div dirA>
  <comp-b dirB>
    <span dirC>
      <i dirD></i>
    </span>
  </comp-b>
</div>

和以下指令集:

@Directive({
  selector: '[dirA]',
  providers: [{ provide: 'A', useValue: 'dirA provider' }]
})
export class DirA {}

@Component({
  selector: 'comp-b',
  template: '<ng-content></ng-content>',
  providers: [{ provide: 'B', useValue: 'comp-b provider'}]
})
export class ComponentB {}

@Directive({ selector: 'dirB' })
export class DirB {}

@Directive({ selector: 'dirC' })
export class DirC {}

@Directive({ selector: 'dirD' })
export class DirD {
  constructor(private dirB: DirB) {}
}

注意: private dirB: DirB在您的情况下类似于private _label: NgbButtonLabel

Note: private dirB: DirB is like private _label: NgbButtonLabel in your case

Angular编译器为我们的模板创建视图工厂:

Angular compiler creates view factory for our template:

注意:我在组件上使用了新的preserveWhitespaces: false选项,因此我们在工厂看不到textDef.

Note: i used new preserveWhitespaces: false option on component so we don't see textDef in the factory.

然后选择从该工厂创建ViewDefinition ,并实例化宿主元素的提供程序.

Then angular creates ViewDefinition from this factory and also instantiates providers for host elements.

您应该知道的主要事情是

Main thing you should know is that each directive provides its own token:

因此这里的提供程序可能如下所示:

So providers here could look as follows:

<div dirA>               [DirA]
  <comp-b dirB>          [ComponentB, DirB]
    <span dirC>          [DirC] 
      <i dirD></i>       [DirD]
    </span>
  </comp-b>
</div>

以下规则是我们在指令元数据(providers数组)中声明的提供程序,也将添加到主机元素提供程序中:

The following rule is providers that we are declaring within directive metadata(providers array) will also be added to host element providers:

<div dirA>               [DirA, { provide: 'A', useValue: 'dirA provider' }]
  <comp-b dirB>          [ComponentB, DirB, { provide: 'B', useValue: 'comp-b provider'}]
    <span dirC>          [DirC] 
      <i dirD></i>       [DirD]
    </span>
  </comp-b>
</div>

现在angular正在尝试获取DirB指令的提供者

Now angular is trying to get provider for DirB directive

@Directive({ selector: 'dirD' })
export class DirD {
  constructor(private dirB: DirB) {}
}

角度依赖关系解析机制从<i dirD></i>节点开始,一直扩展到<div dirA>:

Angular dependency resolution mechanism starts with <i dirD></i> node and goes up to <div dirA>:

              null or throw error
                    /\
                 @NgModule
                    /\
                  my-app
<div dirA>          /\     [DirA, { provide: 'A', useValue: 'dirA provider' }]
  <comp-b dirB>     /\     [ComponentB, DirB, { provide: 'B', useValue: 'comp-b provider'}]
    <span dirC>     /\     [DirC]   
      <i dirD></i>  /\     [DirD]  
    </span>
  </comp-b>
</div>

因此angular将在<comp-b dirB>主机元素上找到DirB提供程序.我们可能会认为,Angular将获得三个步骤,以获取DirB提供程序 BUT 实际上,棱角使用原型继承在元素上定义提供程序.

So angular will find DirB provider on <comp-b dirB> host element. We might think that angular will make three steps up to get DirB provider BUT Indeed angular uses prototypical inheritance to define providers on elements.

这样,我们的树将如下所示:

This way our tree will look like:

              null or throw error
                    /\
                 @NgModule
                    /\
                  my-app
<div dirA>          /\     [
                             DirA, { provide: 'A', useValue: 'dirA provider' }
                           ]
  <comp-b dirB>     /\     [
                             ComponentB, 
                             DirB, { provide: 'B', useValue: 'comp-b provider'}, 
                             DirA, { provide: 'A', useValue: 'dirA provider' }
                           ]
    <span dirC>     /\     [
                             DirC, ComponentB, 
                             DirB, { provide: 'B', useValue: 'comp-b provider'}, 
                             DirA, { provide: 'A', useValue: 'dirA provider' }
                           ]  
      <i dirD></i>  /\     [
                             DirD, DirC, ComponentB, 
                             DirB, { provide: 'B', useValue: 'comp-b provider'}, 
                             DirA, { provide: 'A', useValue: 'dirA provider' }
                           ]  
    </span>
  </comp-b>
</div>

正如我们所看到的,angular实际上仅需一步就可以从<i dirD></i>宿主元素中找到DirB提供程序.

As we can see actually angular uses only one step to find DirB provider from <i dirD></i> host element.

这篇关于角度2-ng-bootstrap如何为NgbRadio指令提供NgbRadioGroup和NgbButtonLabel?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆