Angular2: rendering / reloading a component template

Ideally, I would need to reload / reload my component 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 item. When (in another component) I click IBO (click-to-click), I need to add (I use *ngIf ) a new option in the menu, which will be IBO Details and a child of the list.

Component

IBOsNavigationElement (menu 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> <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, the navigationList will be empty [] , because the user has not clicked a single IBO (client), but as soon as the IBO is called, the list will be filled, and therefore I need a new option to display on the menu.

With this code, when I press IBO, the <li> element and its children are created, but: I only see the <li> element.

Question:

A menu option is generated but not executed by layout styles. It must be initialized with all the elements in order to know how to display the menu options.

I need to reload the template of this component in order to display the menu correctly.

Note:

If I use a template without *ngIf , it works well, but from the first moment I would get the IBO Details option, which makes no sense, because no one clicked during initialization.

 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 Result:

Before clicking on anything (in init):

enter image description here

After I clicked IBO (client):

enter image description here

Update 1:

To clarify what I had in mind:

Menu option generated but not executed by layout styles

If my menu component is initialized without *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 menu output (see images) in accordance with this structure:

 <li> <a> <ul> <li *ngFor> <a> </li> </ul> </li> 

And therefore add the + symbol, submenu behavior, etc.

But if it is initialized without all elements (when *ngIf is false <li> , and its children are not displayed, therefore the layout does not take them into account for drawing / creating menus), and these elements are added after rendering, then they will exist in the source code, but we won’t be able to see them on the menu, because:

  • No + created
  • Lack of submenu behavior
+2
source share
3 answers

I finally got a job!

So my problem is:

When new HTML elements are generated (with *ngIf ), they are not displayed because they are not processed in the same way as other menu items.

So, I asked how to reload or re-display the template with all the “new” elements ... But I did not find where to reload the component or component template. Instead, I applied the menu processing logic to my updated template.

(If you want a short story version, go down below and read the Summary )

So, I plunged into my deepest template 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 am creating a menu directive that displays a menu layout according to existing html elements. And, as you can see, I isolated the behavior that handles menu items by adding a + icon, creating a submenu function, etc ....: renderSubMenus() .

How renderSubMenus() works:

It goes through the DOM elements of the nativeElement parameter passed as the parameter, and applies logic to display the menu correctly.

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 will be the way I create the menu.

Now consider the IBOsNavigationElement component, which enters the menu with the [ibos-navigation-element] attribute.

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 here? A few things...

  • I import the MenuDirective directive, so I can call its renderSubMenus() method.
  • I use ElementRef and find() to select the code block that I want to send to this.menuDirective.renderSubMenus() . I find it through class , see this.$menuElement.find('ul.renderMe') .
  • I am implementing Angular DoCheck to identify the changes I want and apply logic to this change event. See the ngDoCheck() Method, where I check if the navigationList full and if I already made this code block (I had problems because it was executed too many times and I had 6 + buttons: disaster).

Summary:

To reload a template:

  • I created a directive with a method that applies the logic that usually occurs on init .
  • I use this directive in the component that I want to reload.
  • With ElementRef I get the part of the template that I want to "reload".
  • I choose when I want to use the "reload" method, in my case I did it using ngDoCheck() . You can call this method whenever you want.
  • I call the "reload" method of the directive, passing as a parameter the part of the code inside my template that I want to reload (I could pass the whole template if I wanted to).
  • The method will be applied to that part of the template in which I sent the same logic that would be applied if I installed the component with hidden *ngIf elements .

So, technically, I did not restart the component . I applied the same logic to the component template as when I reloaded it.

+1
source

Angular has two change detection strategies:

An OnPush strategy may have better performance if there are multiple elements on the page or when you want more control over the rendering process.

To use this strategy, you must declare it in your component:

 @Component({ selector: '[ibos-navigation-element]', template: `...`, changeDetection: ChangeDetectionStrategy.OnPush }) 

And add to your constructor:

 constructor( private changeDetector: ChangeDetectorRef, ) {} 

If you want to run change detection so that the component can be re-displayed (in your case, immediately after adding a new IBO / client to the model):

 this.changeDetector.markForCheck(); 

Check out the demo from the official tutorial: http://plnkr.co/edit/GC512b?p=preview

If the problem is not in detecting changes, but in terms of CSS / SCSS style, remember that in Angular 2 each component has its own set of CSS classes and the “children” elements are not “inherited”. They are completely isolated from each other. One solution could be to create global CSS / SCSS styles.

+2
source

Try using ChangeDetectorRef.detectChanges () - it works the same as $ scope. $ digest () from Angular 1.

Note: ChangeDetectorRef must be inserted into the component.

0
source

All Articles