如何使用模板服务减少HTML?
How to use a template service to reduce HTML?
我有一个包装表单控件的组件。这为每个控件添加了可重用代码,但为了简单起见,假设它所做的只是添加一个标签:
app.component:
<app-wrapper labelText="First Name">
<input type="text" >
</app-wrapper>
wrapper.component:
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-content></ng-content>
</div>
export class WrapperComponent {
@Input() labelText;
}
我想改为将包装器改为@Directive,以便进一步减少表单中 HTML 的数量,但我不太清楚它会如何工作。我的想法是这样的:
app.component:
@Component({
selector: 'my-app',
template: `
<input type="text" wrapper labelText="First Name">
<input type="text" wrapper labelText="Last Name" >
<ng-template #tpl>
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-content></ng-content>
</div>
</ng-template>`
})
export class AppComponent {
@ViewChild('tpl', {read:TemplateRef}) tpl
constructor(private service: TemplateService) { }
ngAfterViewInit() {
this.service.template = this.tpl;
}
}
wrapper.dir
@Directive({
selector: '[wrapper]'
})
export class WrapperDirective {
@Input('labelText') labelText;
constructor(
private vc:ViewContainerRef,
private service:TemplateService) { }
ngAfterViewInit() {
of(null).pipe(
delay(1)).subscribe(() => {
let tpl = this.service.template;
this.vc.createEmbeddedView(this.service.template)
})
}
}
不幸的是,我认为这会将标签放在输入标签之后而不是前面。怎么插在前面?
另一个问题是:如何访问模板中的#labelText?
我将创建一个带有属性选择器的组件,您可以在包装输入的 div 上设置该属性选择器。类似于:
import { Component, Input } from '@angular/core';
@Component({
selector: 'div[wrapper]',
template: `
<label>{{labelText}}</label>
<ng-content></ng-content>
`,
styles: [`
:host {
border:thin solid;
padding:10px;
}
`]
})
export class WrapperComponent {
@Input() labelText: string;
}
并像这样使用它:
<div wrapper [labelText]="'test text'">
<input type="text">
</div>
https://stackblitz.com/edit/attribute-selector-component?file=src%2Fapp%2Fwrapper.component.ts
访问模板中的 labelText
非常简单。您只需要将上下文(第二个参数)传递给 createEmbeddedView
({labelText: this.labelText}
)。您还可以通过将索引(第 3 个参数)作为 0.
传递给 createEmbeddedView
来将模板作为视图的第一个子项插入
https://angular.io/api/core/ViewContainerRef#createEmbeddedView
如果你真的想使用一个指令,如果你想改变 DOM 中的元素位置,它必须是一个结构指令(在它周围添加一个包装器)。这样你实际上得到了一个元素模板和一个包装器模板,你的指令将它们包装起来(当然,如果你愿意,你也可以使用模板服务......):
import { Directive, Input, ViewContainerRef, TemplateRef, EmbeddedViewRef } from '@angular/core';
@Directive({
selector: '[wrapperLabelText]',
})
export class WrapperDirective {
private _labelText: string;
private embedded: EmbeddedViewRef<any>;
@Input('wrapperLabelText')
get labelText() {
return this._labelText;
}
set labelText(value: string) {
this._labelText = value;
if (this.embedded) {
this.embedded.context.labelText = value;
}
};
@Input('wrapperLabelTextWrapper')
wrapperTemplate: TemplateRef<any>;
constructor(
private templateRef: TemplateRef<any>,
private vc: ViewContainerRef,
) {
}
async ngAfterViewInit() {
await new Promise(resolve => setTimeout(resolve));
const ref = this.vc.createEmbeddedView(this.wrapperTemplate, {
wrapped: this.templateRef,
labelText: this.labelText,
});
}
}
并像这样使用它:
<input type="text" *wrapperLabelText="'test';wrapper:wrapperTemplate">
<input type="text" *wrapperLabelText="'test 2';wrapper:wrapperTemplate">
<ng-template #wrapperTemplate let-wrapped="wrapped" let-labelText="labelText">
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-container *ngTemplateOutlet="wrapped"></ng-container>
</div>
</ng-template>
https://stackblitz.com/edit/wrapper-directive?file=src/app/app.component.html
我有一个包装表单控件的组件。这为每个控件添加了可重用代码,但为了简单起见,假设它所做的只是添加一个标签:
app.component:
<app-wrapper labelText="First Name">
<input type="text" >
</app-wrapper>
wrapper.component:
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-content></ng-content>
</div>
export class WrapperComponent {
@Input() labelText;
}
我想改为将包装器改为@Directive,以便进一步减少表单中 HTML 的数量,但我不太清楚它会如何工作。我的想法是这样的:
app.component:
@Component({
selector: 'my-app',
template: `
<input type="text" wrapper labelText="First Name">
<input type="text" wrapper labelText="Last Name" >
<ng-template #tpl>
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-content></ng-content>
</div>
</ng-template>`
})
export class AppComponent {
@ViewChild('tpl', {read:TemplateRef}) tpl
constructor(private service: TemplateService) { }
ngAfterViewInit() {
this.service.template = this.tpl;
}
}
wrapper.dir
@Directive({
selector: '[wrapper]'
})
export class WrapperDirective {
@Input('labelText') labelText;
constructor(
private vc:ViewContainerRef,
private service:TemplateService) { }
ngAfterViewInit() {
of(null).pipe(
delay(1)).subscribe(() => {
let tpl = this.service.template;
this.vc.createEmbeddedView(this.service.template)
})
}
}
不幸的是,我认为这会将标签放在输入标签之后而不是前面。怎么插在前面?
另一个问题是:如何访问模板中的#labelText?
我将创建一个带有属性选择器的组件,您可以在包装输入的 div 上设置该属性选择器。类似于:
import { Component, Input } from '@angular/core';
@Component({
selector: 'div[wrapper]',
template: `
<label>{{labelText}}</label>
<ng-content></ng-content>
`,
styles: [`
:host {
border:thin solid;
padding:10px;
}
`]
})
export class WrapperComponent {
@Input() labelText: string;
}
并像这样使用它:
<div wrapper [labelText]="'test text'">
<input type="text">
</div>
https://stackblitz.com/edit/attribute-selector-component?file=src%2Fapp%2Fwrapper.component.ts
访问模板中的 labelText
非常简单。您只需要将上下文(第二个参数)传递给 createEmbeddedView
({labelText: this.labelText}
)。您还可以通过将索引(第 3 个参数)作为 0.
createEmbeddedView
来将模板作为视图的第一个子项插入
https://angular.io/api/core/ViewContainerRef#createEmbeddedView
如果你真的想使用一个指令,如果你想改变 DOM 中的元素位置,它必须是一个结构指令(在它周围添加一个包装器)。这样你实际上得到了一个元素模板和一个包装器模板,你的指令将它们包装起来(当然,如果你愿意,你也可以使用模板服务......):
import { Directive, Input, ViewContainerRef, TemplateRef, EmbeddedViewRef } from '@angular/core';
@Directive({
selector: '[wrapperLabelText]',
})
export class WrapperDirective {
private _labelText: string;
private embedded: EmbeddedViewRef<any>;
@Input('wrapperLabelText')
get labelText() {
return this._labelText;
}
set labelText(value: string) {
this._labelText = value;
if (this.embedded) {
this.embedded.context.labelText = value;
}
};
@Input('wrapperLabelTextWrapper')
wrapperTemplate: TemplateRef<any>;
constructor(
private templateRef: TemplateRef<any>,
private vc: ViewContainerRef,
) {
}
async ngAfterViewInit() {
await new Promise(resolve => setTimeout(resolve));
const ref = this.vc.createEmbeddedView(this.wrapperTemplate, {
wrapped: this.templateRef,
labelText: this.labelText,
});
}
}
并像这样使用它:
<input type="text" *wrapperLabelText="'test';wrapper:wrapperTemplate">
<input type="text" *wrapperLabelText="'test 2';wrapper:wrapperTemplate">
<ng-template #wrapperTemplate let-wrapped="wrapped" let-labelText="labelText">
<div style="border:thin solid; padding:10px">
<label>{{labelText}}</label>
<ng-container *ngTemplateOutlet="wrapped"></ng-container>
</div>
</ng-template>
https://stackblitz.com/edit/wrapper-directive?file=src/app/app.component.html