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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:26:58  来源:igfitidea点击:

Angular2: rendering / reloading a component's template

angulartypescriptangular2-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 详细信息和子列表。

IBOsNavigationElementcomponent (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, navigationListwill 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):

单击任何内容之前(在初始化时):

enter image description here

在此处输入图片说明

After I clicked an IBO (client):

单击 IBO (客户端)后

enter image description here

在此处输入图片说明

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 *ngIfis falsethe <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:

但是,如果它没有所有的元素初始化时(*ngIffalse<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 nativeElementpassed 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 IBOsNavigationElementcomponent, 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...

好的,那么我在这里做了什么不同的事情?几件事...

  1. I import the directive MenuDirectiveso I can call its renderSubMenus()method.
  2. I use ElementRefand find()to select the block of code that I want to send to this.menuDirective.renderSubMenus(). I find it through its class, see: this.$menuElement.find('ul.renderMe').
  3. 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 list navigationListis 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).
  1. 我导入指令MenuDirective以便我可以调用它的renderSubMenus()方法。
  2. 我使用ElementReffind()选择要发送到的代码块this.menuDirective.renderSubMenus()。我通过它找到它class,请参阅:this.$menuElement.find('ul.renderMe')
  3. 我实现了 Angular 的DoCheck钩子来检测我想要的更改并将逻辑应用于该更改事件。请参阅ngDoCheck()我检查列表 navigationList是否已填充以及是否已呈现此代码块的方法(我遇到了问题,因为呈现的次数太多,我有 6 个+按钮:灾难)

Summary:

概括:

To 'reload'the template:

“重新加载”模板:

  1. I created a directive with a method that applies the logic that usually occurs on init.
  2. I instance that directive in the component that I want to reload.
  3. With ElementRefI get the portion of template that I want to 'reload'.
  4. 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.
  5. 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).
  6. 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.
  1. 我用一个方法创建了一个指令,该方法应用了通常发生在 init 上的逻辑
  2. 我在要重新加载的组件中实例化该指令。
  3. 随着ElementRef我得到我想要的模板的一部分“刷新”
  4. 我选择何时应用“重新加载”方法,在我的情况下,我使用ngDoCheck(). 您可以随时调用该方法。
  5. 我调用指令的“reload”方法,将模板中我想要重新加载的代码部分作为参数传递(如果我愿意,我可以传递整个模板)
  6. 该方法将应用于我发送的模板部分,如果我使用*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 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 必须注入到组件中。