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:
Try using ChangeDetectorRef.detectChanges() - it works much like $scope.$digest() from Angular 1.
Note: ChangeDetectorRef must be injected into the component.
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...
MenuDirective
so I can call its renderSubMenus()
method.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')
.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:
ElementRef
I get the portion of template that I want to 'reload'.ngDoCheck()
. You can call that method whenever you want.*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.
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.