如何将自定义控件的提供程序扩展器与ngModel分离到Angular 2中的单独文件

时间:2016-04-29 09:00:29

标签: javascript angular

我创建了自定义控件并将其连接到ngModel。现在要在多个组件中重用此代码,我想将其分隔在不同的文件中。采用以下示例 -

import {Component, Provider, forwardRef} from "angular2/core";
import {ControlValueAccessor, NG_VALUE_ACCESSOR, CORE_DIRECTIVES} from "angular2/common";

const noop = () => {};

const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {
    useExisting: forwardRef(() => CustomInput),
    multi: true
  });

@Component({
  selector: 'custom-input',
  template: `
      <div class="form-group">
        <label><ng-content></ng-content>
          <input class="form-control" 
                 [(ngModel)]="value" 
                 (blur)="onTouched()">
        </label>
      </div>
  `,
  directives: [CORE_DIRECTIVES],
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CustomInput implements ControlValueAccessor{

    //The internal data model
    private _value: any = '';

    //Placeholders for the callbacks
    private _onTouchedCallback: (_:any) => void = noop;

    private _onChangeCallback: (_:any) => void = noop;

    //get accessor
    get value(): any { return this._value; };

    //set accessor including call the onchange callback
    set value(v: any) {
      if (v !== this._value) {
        this._value = v;
        this._onChangeCallback(v);
      }
    }

    //Set touched on blur
    onTouched(){
      this._onTouchedCallback();
    }

    //From ControlValueAccessor interface
    writeValue(value: any) {
      this._value = value;
    }

    //From ControlValueAccessor interface
    registerOnChange(fn: any) {
      this._onChangeCallback = fn;
    }

    //From ControlValueAccessor interface
    registerOnTouched(fn: any) {
      this._onTouchedCallback = fn;
    }

}

取自TableView.TableViewFocusModel class

的示例

然后我将这段代码分开 -

const noop = () => {};

const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {
    useExisting: forwardRef(() => CustomInput),
    multi: true
  });

external-file.ts

import { forwardRef } from 'angular2/core';
import { NG_VALUE_ACCESSOR } from 'angular2/common';

const fr = forwardRef;

export function createExtendedProvider(customInput) {  
  return new Provider(
    NG_VALUE_ACCESSOR, {
      useExisting: fr(() => { console.log(`Inside method ${customInput}`); return customInput; }),
      multi: true
    });
}

我在这样的组件中调用它 -  const MD_INPUT_CONTROL_VALUE_ACCESSOR = providerExtender.createExtendedProvider(CustomInput);

当我尝试运行应用程序时,我收到以下错误 -

browser_adapter.js:77 Error: Uncaught (in promise): Token must be defined!
    at resolvePromise (zone.js:538)
    at eval (zone.js:515)
    at ZoneDelegate.invoke (zone.js:323)
    at Object.NgZoneImpl.inner.inner.fork.onInvoke (ng_zone_impl.js:45)
    at ZoneDelegate.invoke (zone.js:322)
    at Zone.run (zone.js:216)
    at eval (zone.js:571)
    at ZoneDelegate.invokeTask (zone.js:356)
    at Object.NgZoneImpl.inner.inner.fork.onInvokeTask (ng_zone_impl.js:36)
    at ZoneDelegate.invokeTask (zone.js:355)
    at Zone.runTask (zone.js:256)
    at drainMicroTaskQueue (zone.js:474)
    at HTMLDocument.ZoneTask.invoke (zone.js:426)BrowserDomAdapter.logError @ browser_adapter.js:77ExceptionHandler.call @ exception_handler.js:60(anonymous function) @ application_ref.js:194schedulerFn @ async.js:123SafeSubscriber.__tryOrUnsub @ Subscriber.js:166SafeSubscriber.next @ Subscriber.js:115Subscriber._next @ Subscriber.js:74Subscriber.next @ Subscriber.js:51Subject._finalNext @ Subject.js:124Subject._next @ Subject.js:116Subject.next @ Subject.js:73EventEmitter.emit @ async.js:112NgZone._zoneImpl.ng_zone_impl_1.NgZoneImpl.onError @ ng_zone.js:120NgZoneImpl.inner.inner.fork.onHandleError @ ng_zone_impl.js:66ZoneDelegate.handleError @ zone.js:327Zone.runGuarded @ zone.js:233_loop_1 @ zone.js:487drainMicroTaskQueue @ zone.js:494ZoneTask.invoke @ zone.js:426
zone.js:461 Unhandled Promise rejection: Token must be defined! ; Zone: angular ; Task: Promise.then ; Value: BaseException {message: "Token must be defined!", stack: "Error: Token must be defined!↵    at new BaseExcep…oke (webpack:///./~/zone.js/dist/zone.js?:426:22)"}message: "Token must be defined!"stack: "Error: Token must be defined!↵    at new BaseException (webpack:///./~/angular2/src/facade/exceptions.js?:17:23)↵    at new Key (webpack:///./~/angular2/src/core/di/key.js?:26:19)↵    at KeyRegistry.get (webpack:///./~/angular2/src/core/di/key.js?:65:22)↵    at Function.Key.get (webpack:///./~/angular2/src/core/di/key.js?:40:60)↵    at resolveFactory (webpack:///./~/angular2/src/core/di/provider.js?:365:54)↵    at resolveProvider (webpack:///./~/angular2/src/core/di/provider.js?:385:66)↵    at Array.map (native)↵    at Object.resolveProviders (webpack:///./~/angular2/src/core/di/provider.js?:393:31)↵    at Function.Injector.resolve (webpack:///./~/angular2/src/core/di/injector.js?:426:27)↵    at Function.DirectiveProvider.createFromType (webpack:///./~/angular2/src/core/linker/element.js?:100:82)↵    at ResolvedMetadataCache.getResolvedDirectiveMetadata (webpack:///./~/angular2/src/core/linker/resolved_metadata_cache.js?:27:50)↵    at Function.AppProtoElement.create (webpack:///./~/angular2/src/core/linker/element.js?:159:45)↵    at eval (viewFactory_Login:833:41)↵    at Object.evalExpression (webpack:///./~/angular2/src/facade/lang.js?:457:94)↵    at TemplateCompiler._createViewFactoryRuntime (webpack:///./~/angular2/src/compiler/template_compiler.js?:184:27)↵    at eval (webpack:///./~/angular2/src/compiler/template_compiler.js?:144:49)↵    at ZoneDelegate.invoke (webpack:///./~/zone.js/dist/zone.js?:323:29)↵    at Object.NgZoneImpl.inner.inner.fork.onInvoke (webpack:///./~/angular2/src/core/zone/ng_zone_impl.js?:45:41)↵    at ZoneDelegate.invoke (webpack:///./~/zone.js/dist/zone.js?:322:35)↵    at Zone.run (webpack:///./~/zone.js/dist/zone.js?:216:44)↵    at eval (webpack:///./~/zone.js/dist/zone.js?:571:58)↵    at ZoneDelegate.invokeTask (webpack:///./~/zone.js/dist/zone.js?:356:38)↵    at Object.NgZoneImpl.inner.inner.fork.onInvokeTask (webpack:///./~/angular2/src/core/zone/ng_zone_impl.js?:36:41)↵    at ZoneDelegate.invokeTask (webpack:///./~/zone.js/dist/zone.js?:355:43)↵    at Zone.runTask (webpack:///./~/zone.js/dist/zone.js?:256:48)↵    at drainMicroTaskQueue (webpack:///./~/zone.js/dist/zone.js?:474:36)↵    at HTMLDocument.ZoneTask.invoke (webpack:///./~/zone.js/dist/zone.js?:426:22)"__proto__: ErrorconsoleError @ zone.js:461_loop_1 @ zone.js:490drainMicroTaskQueue @ zone.js:494ZoneTask.invoke @ zone.js:426

我注意到当我调用createExtendedProvider函数并在那里传递CustomInput时,它仍未定义。那么,有人可以给我一个提示,如果可以将它分成不同的文件,如果是 - 如何?

2 个答案:

答案 0 :(得分:1)

<强>更新

要在外部文件中创建提供程序,您可以像

一样使用它
@Component({
  selector: 'custom-input',
  template: `
...
  `,
  directives: [FORM_DIRECTIVES],
  providers: [providerExtender.createExtendedProvider(forwardRef(() => CustomInput))]
})
export class CustomInput implements ControlValueAccessor{

并在另一个文件中

export function createExtendedProvider(customInput) { 
  return new Provider(
    NG_VALUE_ACCESSOR, {useExisting: customInput, multi: true});
}

Plunker example

<强>原始

这似乎无效

export function createExtendedProvider(customInput) {  
  return new Provider(
    NG_VALUE_ACCESSOR, {
      useExisting: fr(() => { console.log(`Inside method ${customInput}`); return customInput; }),
      multi: true
    });
}

useExisting意味着需要出现一种应由Angulars DI解决的类型。

我想这就是你想要的:

export function createExtendedProvider(customInput) {  
  return new Provider(
    NG_VALUE_ACCESSOR, {
      useClass: customInput,
      multi: true
    });
}

答案 1 :(得分:0)

也许这是一个愚蠢的答案,但我想说你只需要将CustomInput组件指定到你想要使用它的组件(directives属性)中:

import {CustomInput} from './custom.input';

@Component({
  (...)
  template: `
    <custom-input [(ngModel)]="something" ngControl="test"></custom-input>
  `,
  directives: [CustomInput]
})
export class SomeOtherComponent {
  (...)
}

实际上,值访问器是在组件本身的providers内注册的,因此我认为没有必要将它们分成两个不同的模块。