如何在ControlValueAccessor中正确获取对host指令的引用?

时间:2016-03-03 14:58:30

标签: typescript angular angular-ngmodel

如何在“角度编写代码方式”中的angular2中正确连接两个指令或指令到组件(也是一个指令)?

由于关于angular2的文档仍然非常缺乏,所以非常感谢任何见解或参考。

这是每个angular2示例显示的内容 - 通过string绑定到 ngModel

@Component({
    template: 'Hello <input type="text" [(ngModel)]="myVariable">!'
})
class ExampleComponent() {
    myVariable: string = 'World';
}

假设我想在自定义组件上使用ngModel,它不代表 string s ,而是任何其他值,例如a number或类或接口:

interface Customer {
    name: string;
    company: string;
    phoneNumbers: string[];
    addresses: Address[];
}
@Component({
    selector: 'customer-editor',
    template: `
        <p>Customer editor for {{customer.name}}</p>
        <div><input [(ngModel)]="customer.name"></div>`
})
class CustomerEditor {
    customer: Customer;
}

为什么我可以使用ngModel,当任何其他属性使数据绑定更容易时?好吧,我正在为angular2实现一个设计垫片,组件将像原生<input>一样使用(或旁边):

<input name="name" [(ngModel)]="user.name">
<pretty-select name="country" [(ngModel)]="user.country" selectBy="countryCode">
    <option value="us">United States of America</option>
    <option value="uk">United Kingdom</option>
    ...
</pretty-select>

user.country将是对象,而不是字符串

interface Country {
    countryCode: string,
    countryName: string
}

class User {
    name: string;
    country: Country;
    ...
}

到目前为止我的工作是什么,但“感觉不对”:

GitHub repository for this example

要将ngModel指令提供的引用与我的CustomerEditor组件相关联,目前我正在使用我自己的ControlValueAccessor(简化)

const CUSTOMER_VALUE_ACCESSOR: Provider = CONST_EXPR(
    new Provider(NG_VALUE_ACCESSOR, {
        useExisting: forwardRef(() => CustomerValueAccessor)
    })
);

@Directive({
    selector: 'customer-editor[ngModel]',
    providers: [CUSTOMER_VALUE_ACCESSOR]
})
@Injectable()
class CustomerValueAccessor implements ControlValueAccessor {
    private host: CustomerEditor;

    constructor(element: ElementRef, viewManager: AppViewManager) {
        let hostComponent: any = viewManager.getComponent(element);
        if (hostComponent instanceof CustomerEditor) {
            this.host = hostComponent;
        }
    }

    writeValue(value: any): void {
        if (this.host) { this.host.setCustomer(value); }
    }
}

现在,令我不安的是ControlValueAccessor是我获取对主机组件的引用的方式:

        if (hostComponent instanceof CustomerEditor) {
            this.host = hostComponent;
        }

这不仅需要3个依赖项,其中一个应该足够(ElementRefAppViewManagerCustomerEditor),在运行时进行类型检查也感觉非常错误。

如何在angular2?

中获取对主机组件的引用的“正确”方法

我尝试过的其他事情,但没有开始工作:

  • This answer by Thierry Templier注意到ControlValueAccessor构造函数中的组件类,它由angular注入:

    class CustomerValueAccessor implements ControlValueAccessor {
        constructor(private host: CustomerEditor) { }
    }
    

    不幸的是,这对我不起作用,并给了我一个例外:

      

    无法解析'CustomerValueAccessor'的所有参数(未定义)。确保所有参数都使用Inject进行修饰或具有有效的类型注释,并且'CustomerValueAccessor'使用Injectable进行修饰。

  • 使用@Host

    class CustomerValueAccessor implements ControlValueAccessor {
        constructor(@Host() private editor: CustomerEditor) { }
    }
    

    抛出与上述解决方案相同的例外情况。

  • 使用@Optional

    class CustomerValueAccessor implements ControlValueAccessor {
        constructor(@Optional() private editor: CustomerEditor) { }
    }
    

    不会抛出异常,但CustomerEditor未注入并保持null

由于角度变化/频繁变化,the specific versions I am working with might be relevant, which is angular2@2.0.0-beta.6

2 个答案:

答案 0 :(得分:2)

Günter Zöchbauer的评论指出了我正确的方向。

要使用ngModel绑定组件上的值,组件本身需要实现ControlValueAccessor接口并向自身提供forwardRef 在组件配置的providers:键中:

const CUSTOMER_VALUE_ACCESSOR: Provider = CONST_EXPR(
    new Provider(NG_VALUE_ACCESSOR, {
        useExisting: forwardRef(() => CustomerEditor),
        multi: true
    })
);

@Component({
    selector: 'customer-editor',
    template: `template for our customer editor`,
    providers: [CUSTOMER_VALUE_ACCESSOR]
})
class CustomerEditor implements ControlValueAccessor {
    customer: Customer;
    onChange: Function = () => {};
    onTouched: Function = () => {};

    writeValue(customer: Customer): void {
        this.customer = customer;
    }

    registerOnChange(fn: Function): void {
        this.onChange = fn;
    }

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

来自父组件的用法:

@Component({
    selector: 'customer-list',
    template: `
        <h2>Customers:</h2>
        <p *ngFor="#c of customers">
            <a (click)="editedCustomer = c">Edit {{c.name}}</a>
        </p>
        <hr>
        <customer-editor *ngIf="editedCustomer" [(ngModel)]="editedCustomer">
        </customer-editor>`,
    directives: [CustomerEditor]
})
export class CustomerList {
    private customers: Customer[];
    private editedCustomer: Customer = 0;

    constructor(testData: TestDataProvider) {
         this.customers = testData.getCustomers();
    }
}

ControlValueAccessor的每个示例总是显示如何在主机组件上使用单独的类或指令,从未在主机组件类本身上实现。

答案 1 :(得分:0)

在您的示例中,似乎您的CustomerValueAccessor指令附加在CustomerComponent组件(一个是选择器customer-editor)上,而不是CustomerEditor类型的指令。我认为这就是为什么你不能注射它的原因。

CustomEditor对应什么?