typescript Angular2:渲染/重新加载组件的模板
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/43395808/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Angular2: rendering / reloading a component's template
提问by SrAxi
Ideally I would need to reload / rerender my component's template but if there is a better way to do this I will gladly implement it.
理想情况下,我需要重新加载/重新渲染我的组件模板,但如果有更好的方法来做到这一点,我很乐意实现它。
Desired behavior:
期望的行为:
So, I have a component for a menu element. When (in another component)I click an IBO (some sort of 'client', per say)is clicked I need to add(I'm using *ngIf
) a new option in the menu that would be IBO Detailsand a child list.
所以,我有一个菜单元素的组件。当(在另一个组件中)我单击 IBO (某种“客户”,据说)时,我需要在菜单中添加(我正在使用*ngIf
)一个新选项,即IBO 详细信息和子列表。
IBOsNavigationElement
component (menu component):
IBOsNavigationElement
组件(菜单组件):
@Component({
selector: '[ibos-navigation-element]',
template: `
<a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
class="menu-item-parent">{{'IBOs' | i18n}}</span>
</a>
<ul>
<li routerLinkActive="active">
<a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
</li>
<li *ngIf="navigationList?.length > 0">
<a href="#">{{'IBO Details' | i18n}}</a>
<ul>
<li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
<a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
</li>
</ul>
</li>
</ul>
`
})
export class IBOsNavigationElement implements OnInit {
private navigationList = <any>[];
constructor(private navigationService: NavigationElementsService) {
this.navigationService.updateIBOsNavigation$.subscribe((navigationData) => {
this.navigationList.push(navigationData);
log.d('I received this Navigation Data:', JSON.stringify(this.navigationList));
}
);
}
ngOnInit() {
}
}
Initially, navigationList
will be empty []
, because the user didn't click any IBO (client), but as soon as an IBO is clicked the list will be populated and, therefore, I need the new option to appear in the menu.
最初,navigationList
将是空的[]
,因为用户没有单击任何 IBO (client),但是一旦单击 IBO,列表将被填充,因此,我需要新选项出现在菜单中。
With this code, when I click an IBO, the <li>
element and it's children are created but: I only see the <li>
element.
使用此代码,当我单击 IBO 时,将<li>
创建元素及其子元素,但是:我只看到该<li>
元素。
Issue:
问题:
The menu option is generated but not proccessed by the layout styles. It needs to be initialized with all the elements in order to know how to display the menu options.
菜单选项由布局样式生成但不处理。它需要使用所有元素进行初始化,以便知道如何显示菜单选项。
I need to reload the template of that component in order to display correctly the menu.
我需要重新加载该组件的模板才能正确显示菜单。
NOTE:
笔记:
If I use the template without the *ngIf
, works well but I would have from the first moment an IBO Detailsoption that has no sense, because no IBO has been clicked when initialized.
如果我使用没有*ngIf
,的模板效果很好,但从一开始我就会有一个没有意义的IBO 详细信息选项,因为在初始化时没有单击 IBO。
template: `
<a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
class="menu-item-parent">{{'IBOs' | i18n}}</span>
</a>
<ul>
<li routerLinkActive="active">
<a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
</li>
<li>
<a href="#">{{'IBO Details' | i18n}}</a>
<ul>
<li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
<a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
</li>
</ul>
</li>
</ul>
`
Desired Output:
期望输出:
Before clicking anything (on init):
单击任何内容之前(在初始化时):
After I clicked an IBO (client):
单击 IBO (客户端)后:
Update 1:
更新 1:
To clarify what I meant with:
为了澄清我的意思:
The menu option is generated but not proccessed by the layout styles
菜单选项由布局样式生成但不处理
If, my menu component is initialized without the *ngIf
:
如果,我的菜单组件没有初始化*ngIf
:
<li>
<a href="#">{{'IBO Details' | i18n}}</a>
<ul>
<li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
<a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
</li>
</ul>
</li>
Then the layout styles can generate the menu output (see images)according to this structure:
然后布局样式可以根据这个结构生成菜单输出(见图):
<li>
<a>
<ul>
<li *ngFor>
<a>
</li>
</ul>
</li>
And therefore add the +
symbol and the submenu behavior, etc.
因此添加+
符号和子菜单行为等。
But, if it's initialized without all the elements (when *ngIf
is false
the <li>
and it's children are not rendered so the layout does not take them into account to draw/create the menu) and these elements are added afterthe rendering, then they will exist in source code, but we won't be able to see them in the menu because:
但是,如果它没有所有的元素初始化时(*ngIf
是false
的<li>
,它的孩子不会呈现这样的布局不考虑它们绘制/创建菜单)和这些元素的添加后的渲染,那么他们将在源存在代码,但我们将无法在菜单中看到它们,因为:
- No
+
created - No submenu behavior
- 没有
+
创建 - 没有子菜单行为
采纳答案by SrAxi
I finally got it working!
我终于让它工作了!
So, my problem was:
所以,我的问题是:
When the new HTML elements are generated (with *ngIf
)they don't get displayed because they don't get processedthe same way as the other menu elements do.
当生成新的 HTML 元素(使用*ngIf
)时,它们不会显示,因为它们的处理方式与其他菜单元素不同。
So I asked how to reload or re-render the template with all the 'new'elements... But I did not find where to reload a component or a component's template. As instead, I applied the logic that process the menu to my updated template.
所以我问如何使用所有“新”元素重新加载或重新渲染模板......但我没有找到重新加载组件或组件模板的位置。相反,我将处理菜单的逻辑应用于我更新的模板。
(If you want the short story version, go at the bottom and read the Summary)
(如果你想要短篇小说版本,请到底部阅读摘要)
So, I dived into my template's deepest logic and created a directive to render the menu:
因此,我深入研究了模板的最深层逻辑并创建了一个指令来呈现菜单:
MenuDirective (directive)
MenuDirective(指令)
@Directive({
selector: '[menuDirective]'
})
export class MenuDirective implements OnInit, AfterContentInit {
constructor(private menu: ElementRef,
private router: Router,
public layoutService: LayoutService) {
this.$menu = $(this.menu.nativeElement);
}
// A lot of boring rendering of layout
ngAfterContentInit() {
this.renderSubMenus(this.$menu);
}
renderSubMenus(menuElement) {
menuElement.find('li:has(> ul)').each((i, li) => {
let $menuItem = $(li);
let $a = $menuItem.find('>a');
let sign = $('<b class="collapse-sign"><em class="fa fa-plus-square-o"/></b>');
$a.on('click', (e) => {
this.toggle($menuItem);
e.stopPropagation();
return false;
}).append(sign);
});
}
}
So here I create the menu directive that renders the layout of the menu according to the existing html elements. And, as you can see, I isolated the behavior that processes the menu elementsadding the +
icon, creating the submenu feature, etc...: renderSubMenus()
.
所以在这里我创建了 menu 指令,它根据现有的 html 元素呈现菜单的布局。而且,如您所见,我隔离了处理菜单元素添加+
图标、创建子菜单功能等的行为...:renderSubMenus()
。
How does renderSubMenus()
behave:
renderSubMenus()
表现如何:
It loops through the DOM elements of the nativeElement
passed as parameter and applies the logic to display the menu in the correct way.
它遍历nativeElement
作为参数传递的 DOM 元素,并应用逻辑以正确的方式显示菜单。
menu.html
菜单.html
<ul menuDirective>
<li ibos-navigation-element></li>
<li>
<a href="#"><i class="fa fa-lg fa-fw fa-shopping-cart"></i> <span
class="menu-item-parent">{{'Orders' | i18n}}</span></a>
<ul>
<li routerLinkActive="active">
<a routerLink="/orders/ordersMain">{{'Orders' | i18n}}</a>
</li>
</ul>
</li>
</ul>
And that would be how I build the menu.
这就是我构建菜单的方式。
Now let's see the IBOsNavigationElement
component, that is included in the menu with the attribute [ibos-navigation-element]
.
现在让我们看看IBOsNavigationElement
组件,它包含在具有属性的菜单中[ibos-navigation-element]
。
IBOsNavigationElement (component)
IBOsNavigationElement(组件)
@Component({
selector: '[ibos-navigation-element]',
template: `
<a href="#"><i class="fa fa-lg fa-fw fa-group"></i> <span
class="menu-item-parent">{{'IBOs' | i18n}}</span>
</a>
<ul class="renderMe">
<li routerLinkActive="active">
<a routerLink="/ibos/IBOsMain">{{'IBOs Main' | i18n}} {{id}}</a>
</li>
<li *ngIf="navigationList?.length > 0">
<a href="#">{{'IBO Details' | i18n}}</a>
<ul>
<li routerLinkActive="active" *ngFor="let navigatedIBO of navigationList">
<a href="#/ibos/IboDetails/{{navigatedIBO['id']}}">{{navigatedIBO['name']}}</a>
</li>
</ul>
</li>
</ul>
`
})
export class IBOsNavigationElement implements OnInit, DoCheck {
private $menuElement: any;
private navigationList = <any>[];
private menuRendered: boolean = false;
constructor(private navigationService: NavigationElementsService, private menuDirective: MenuDirective, private menuElement: ElementRef) {
this.$menuElement = $(this.menuElement.nativeElement);
this.navigationService.updateIBOsNavigation$.subscribe((navigationData) => {
this.navigationList.push(navigationData);
log.d('I received this Navigation Data:', JSON.stringify(this.navigationList));
}
);
}
ngOnInit() {
}
ngDoCheck() {
if (this.navigationList.length > 0 && !this.menuRendered) {
log.er('calling renderSubMenus()');
this.menuDirective.renderSubMenus(this.$menuElement.find('ul.renderMe'));
this.menuRendered = true;
}
}
}
Ok, so what have I done different here? Several things...
好的,那么我在这里做了什么不同的事情?几件事...
- I import the directive
MenuDirective
so I can call itsrenderSubMenus()
method. - I use
ElementRef
andfind()
to select the block of code that I want to send tothis.menuDirective.renderSubMenus()
. I find it through itsclass
, see:this.$menuElement.find('ul.renderMe')
. - I implement Angular's DoCheckhook to detect the changes that I want and apply logic to that change event. See
ngDoCheck()
method where I check if the listnavigationList
is populated and if I have already rendered this block of code (I had issues because was rendering too many times and I had like 6+
buttons: disaster).
- 我导入指令
MenuDirective
以便我可以调用它的renderSubMenus()
方法。 - 我使用
ElementRef
和find()
选择要发送到的代码块this.menuDirective.renderSubMenus()
。我通过它找到它class
,请参阅:this.$menuElement.find('ul.renderMe')
。 - 我实现了 Angular 的DoCheck钩子来检测我想要的更改并将逻辑应用于该更改事件。请参阅
ngDoCheck()
我检查列表navigationList
是否已填充以及是否已呈现此代码块的方法(我遇到了问题,因为呈现的次数太多,我有 6 个+
按钮:灾难)。
Summary:
概括:
To 'reload'the template:
要“重新加载”模板:
- I created a directive with a method that applies the logic that usually occurs on init.
- I instance that directive in the component that I want to reload.
- With
ElementRef
I get the portion of template that I want to 'reload'. - I choose when I want to apply the 'reload'method, in my case I did it with
ngDoCheck()
. You can call that method whenever you want. - I call the directive's 'reload' method passing as parameter the portion of code within my template that I want to reload (I could have passed the entire template if I wanted).
- The method will apply to the portion of template that I sent the same logic that would have applied if I instanced the component with the hidden elements by
*ngIf
.
- 我用一个方法创建了一个指令,该方法应用了通常发生在 init 上的逻辑。
- 我在要重新加载的组件中实例化该指令。
- 随着
ElementRef
我得到我想要的模板的一部分“刷新”。 - 我选择何时应用“重新加载”方法,在我的情况下,我使用
ngDoCheck()
. 您可以随时调用该方法。 - 我调用指令的“reload”方法,将模板中我想要重新加载的代码部分作为参数传递(如果我愿意,我可以传递整个模板)。
- 该方法将应用于我发送的模板部分,如果我使用
*ngIf
.
So, technically, I did not reload the component. I applied to the component's template the same logic that would have been applied if I reloaded it.
所以,从技术上讲,我没有重新加载组件。如果我重新加载它,我将应用到组件的模板的相同逻辑。
回答by Pedro Penna
Angular has two change detection strategies:
Angular 有两种变化检测策略:
The default strategy, that automatically detects changes in the model and re-render the components accordingly.
OnPush, that only re-renders the component when you explicitly tell it to do so. See also https://angular.io/docs/ts/latest/api/core/index/ChangeDetectionStrategy-enum.html
默认策略,自动检测模型中的更改并相应地重新渲染组件。
OnPush,只有当你明确告诉它这样做时才会重新渲染组件。另见https://angular.io/docs/ts/latest/api/core/index/ChangeDetectionStrategy-enum.html
The OnPush strategy can have a better performance when you have several elements on a page or when you want to have more control over the rendering process.
当页面上有多个元素或想要对渲染过程有更多控制时,OnPush 策略可以有更好的性能。
In order to use this strategy, you have to declare it in your component:
为了使用这个策略,你必须在你的组件中声明它:
@Component({
selector: '[ibos-navigation-element]',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
And inject in your constructor:
并注入您的构造函数:
constructor(
private changeDetector: ChangeDetectorRef,
) {}
When you want to fire the change detection so the component can be re-rendered (in your case, right after a new IBO/client is added to the model), call:
当您想要触发更改检测以便可以重新渲染组件时(在您的情况下,在新的 IBO/客户端添加到模型之后),请调用:
this.changeDetector.markForCheck();
Check the live demo from the official tutorial: http://plnkr.co/edit/GC512b?p=preview
从官方教程查看现场演示:http: //plnkr.co/edit/GC512b?p=preview
If the problem is not about change detection, but related to CSS/SCSS styling, bear in mind that in Angular 2 each component has its own set of CSS classes and they're not "inherited" by the "children" elements. They're completely isolated from each another. One solution could be the creation of global CSS/SCSS styles.
如果问题不在于更改检测,而是与 CSS/SCSS 样式有关,请记住,在 Angular 2 中,每个组件都有自己的一组 CSS 类,它们不会被“子项”元素“继承”。它们彼此完全隔离。一种解决方案可能是创建全局 CSS/SCSS 样式。
回答by n0k
Try using ChangeDetectorRef.detectChanges()- it works much like $scope.$digest() from Angular 1.
尝试使用ChangeDetectorRef.detectChanges()- 它的工作原理与 Angular 1 中的 $scope.$digest() 非常相似。
Note: ChangeDetectorRef must be injected into the component.
注意:ChangeDetectorRef 必须注入到组件中。