Angular2: rendering / reloading a component's template

前端 未结 3 2035
无人共我
无人共我 2021-01-03 02:13

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:

相关标签:
3条回答
  • 2021-01-03 02:33

    Try using ChangeDetectorRef.detectChanges() - it works much like $scope.$digest() from Angular 1.

    Note: ChangeDetectorRef must be injected into the component.

    0 讨论(0)
  • 2021-01-03 02:39

    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 processed the same way as the other menu elements do.

    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)

    @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 elements adding the + icon, creating the submenu feature, etc...: renderSubMenus().

    How does renderSubMenus() behave:

    It loops through the DOM elements of the nativeElement passed as parameter and applies the logic to display the menu in the correct way.

    menu.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 (component)

    @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 MenuDirective so I can call its renderSubMenus() method.
    2. I use ElementRef and 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 DoCheck hook to detect the changes that I want and apply logic to that change event. See ngDoCheck() method where I check if the list navigationList 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).

    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 ElementRef I 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.

    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.

    0 讨论(0)
  • 2021-01-03 02:41

    Angular has two change detection strategies:

    • 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

    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.

    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:

    this.changeDetector.markForCheck();
    

    Check the live demo from the official tutorial: 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.

    0 讨论(0)
提交回复
热议问题