Viewing 10 posts - 1 through 10 (of 10 total)
  • Author
    Posts
  • #100334
    admin
    Keymaster

    how can i add an angular comonent dynamically as a tab page

    #100342
    admin
    Keymaster

    Hello helkatz,
    We prepared a help topic that describes how to add an Angular component dynamically to a tab item. You can find it here: https://www.htmlelements.com/docs/angular-dynamic-component-loading/. We hope it is helpful to you.
    Best regards,
    Dimitar
    Smart HTML Elements Team
    https://www.htmlelements.com

    #104386
    dilbert
    Participant

    The mentioned help topic does work, however, as of Angular 15 the ComponentFactoryResolver is deprecated.  Is there a newer method?  I’m hesitant to base new software off of something that could be removed in the future.

    #104387
    admin
    Keymaster

    As we currently do not have this updated on our website, you may look at https://stackoverflow.com/questions/70946038/replace-deprecated-angular-componentfactoryresolver-componentfactory.

    #104388
    dilbert
    Participant

    With that method I need the ​ViewContainerRef for the containing component which I can easily do by adding a custom anchor to smart-tab-item to expose it.  However, we a new tab is added via the built in add button, the new smart-tab-item doesn’t have this tag.  It also doesn’t get added to a @ViewChildren QueryList. (i.e. @ViewChildren(TabItemComponent) items: QueryList<TabItemComponent>).  The items list is never updated with any smart-tab-items created using the add tab button.

    Is there an easy way to get ViewContainerRef for the dynamically added smart items?

    #104391
    admin
    Keymaster

    Hi dilbert,

    Could you post an example about this?

    Regards,
    Peter

    #104393
    dilbert
    Participant

    Here is an example that illustrates the issue. The first 2 tabs get a dynamic component, but those that are added later do not.

    Template:

    <div>Num of smart-tab-items: {{smartItems.length}}</div>
    <div>Num of tabs: {{numTabs}}</div>
    <smart-tabs #tabs id="tabs" class="demoTabsShort" [closeButtons]="true" [closeButtonMode]="'selected'"
    [addNewTab]="true" (addNewTabClick)="onAddNewTabClick($event)" [reorder]="true">
        <smart-tab-item [label]="'Tab_1'"><ng-container app-view-ref></ng-container></smart-tab-item>
        <smart-tab-item [label]="'Tab_2'"><ng-container app-view-ref></ng-container></smart-tab-item>
    </smart-tabs>

    Main component:

    import { Component, AfterViewInit, ViewChildren, QueryList, ViewChild } from '@angular/core';
    import { TabItemComponent, TabsComponent } from 'smart-webcomponents-angular/tabs';
    import { ThingComponent } from './thing.component';
    import { ViewRefAnchorDirective } from './view-ref-anchor.directive';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements AfterViewInit {
    
      // I am expecting this list to be updated when new tabs are added.
      @ViewChildren(TabItemComponent) smartItems = new QueryList();
    
      @ViewChildren(ViewRefAnchorDirective) anchors = new QueryList();
    
      @ViewChild('tabs', {read: TabsComponent, static: false}) tabs!: TabsComponent;
    
      numTabs = 0;
    
      ngAfterViewInit(): void {
        this.tabs.getTabs().then(tabs => {
          this.numTabs = tabs.length;
        });
    
        this.anchors.forEach(tab => this.loadComponent(tab));
      }
    
      onAddNewTabClick(event: Event) {
        this.tabs.getTabs().then(tabs => {
          this.numTabs = tabs.length;
    
          // At this point this.smartItems only has the original 2 smart-tab-items
          // It would appear the new tabs are not known to Angular since they aren't
          // added to the QueryList.
    
          // this.anchors also only has the original 2.  this.anchors.last references
          // the second tab.  Additional tabs do not get a new component.
          this.loadComponent(this.anchors.last);
        });
      }
    
      loadComponent(viewRefAnchor: ViewRefAnchorDirective) {
        viewRefAnchor.viewContainerRef.clear();
        viewRefAnchor.viewContainerRef.createComponent(ThingComponent);
      }
    }

    Anchor directive:

    import { Directive, ViewContainerRef } from "@angular/core";
    /**
     * An directive to expose the ViewContainerRef to allow components to be dynamically
     * created and added.
     */
    @Directive({
      selector: '[app-view-ref]'
    })
    export class ViewRefAnchorDirective {
    
      constructor(public viewContainerRef: ViewContainerRef) { }
    
    }

    Dynamic component:

    import { Component, OnInit } from '@angular/core';
    
    @Component({
      selector: 'app-thing',
      template: '<p>test-component works!</p>'
    })
    export class ThingComponent implements OnInit {
    
      constructor() { }
    
      ngOnInit(): void {
      }
    
    }
    #104394
    dilbert
    Participant

    The other thing I’ve tried is to use an ngFor to define the tabs:

    <smart-tabs #tabs id="tabs" class="demoTabsShort" [closeButtons]="true" [closeButtonMode]="'selected'"
    [addNewTab]="true" (addNewTabClick)="onAddNewTabClick($event)" [reorder]="true">
        <smart-tab-item *ngFor="let tab of tabNames" [label]="tab"><app-thing></app-thing></smart-tab-item>
    </smart-tabs>

    Where tabNames is just a list of strings.

    When a new tab is added (by appending a new string to the tabNames array), the content of the new tab is the new component. However, the label is not updated and there is an error in the console:

    core.mjs:8506 ERROR TypeError: Cannot read properties of undefined (reading 'firstElementChild')
        at BaseElement._updateTabLabel (smart.tabs.js:34:65344)
        at BaseElement.propertyChangedHandler (smart.tabs.js:34:1110)
        at t.updateProperty (smart.button.js:34:75016)
        at BaseElement.set [as label] (smart.button.js:34:76150)
        at set label [as label] (smart-webcomponents-…ular-tabs.js:771:54)
        at Object.ngOnChangesSetInput [as setInput] (core.mjs:1586:26)
        at setInputsForProperty (core.mjs:11738:17)
        at elementPropertyInternal (core.mjs:10874:9)
        at Module.ɵɵproperty (core.mjs:13637:9)
        at AppComponent_smart_tab_item_6_Template (app.component.html:5:50)
    • This reply was modified 1 year, 10 months ago by dilbert.
    #104396
    admin
    Keymaster

    Hi,

    The thing is that the add new tab button adds an empty tab. It does not add a new tab with the template structure you added in the template i.e the new tab does not have ng-container in it. For that purpose we can use ng-template. Please, take a look at the updated code below

    import { Component, AfterViewInit, ViewChildren, QueryList, ViewChild, ComponentRef, ChangeDetectorRef, ViewContainerRef } from '@angular/core';
    import { TabItem, TabItemComponent, TabsComponent } from 'smart-webcomponents-angular/tabs';
    import { ThingComponent } from './thing.component';
    import { ViewRefAnchorDirective } from './view-ref-anchor.directive';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements AfterViewInit {
    
      // I am expecting this list to be updated when new tabs are added.
      smartItems: TabItem[] = [];
    
      @ViewChildren(ViewRefAnchorDirective) anchors = new QueryList();
    
      @ViewChild('tabs', {read: TabsComponent, static: false}) tabs!: TabsComponent;
      @ViewChild('dynamic', { read: ViewContainerRef })
    
      viewRef!: ViewContainerRef;
    
      numTabs = 0;
    
      ngAfterViewInit(): void {
        this.tabs.getTabs().then(tabs => {
          this.numTabs = tabs.length;
        });
    
        this.anchors.forEach(tab => this.loadComponent(tab as any));
     	this.smartItems = Array.from(document.querySelectorAll('smart-tab-item'));
    	 }
    
      onAddNewTabClick(event: Event) {
        this.tabs.getTabs().then(tabs => {
          this.numTabs = tabs.length;
    
          	// create a new dynamic component.
    		const container = document.createElement('div');
    		const componentRef: ComponentRef<ThingComponent> =  this.viewRef.createComponent(ThingComponent);
    	
    		// dynamically add the component to a new container host element.
    		container.appendChild(componentRef.location.nativeElement);
    		// update tabs.
    		this.tabs.update(tabs.length-1, 'Updated Tab', container);
    		
    		
    		this.smartItems = Array.from(document.querySelectorAll('smart-tab-item'));
    					
        });
      }
    
      loadComponent(viewRefAnchor: ViewRefAnchorDirective) {
        viewRefAnchor.viewContainerRef.clear();
        return viewRefAnchor.viewContainerRef.createComponent(ThingComponent);
      }
    }

    Hope this helps.

    Regards,
    Peter

    #104397
    dilbert
    Participant

    That works.  Thanks!

Viewing 10 posts - 1 through 10 (of 10 total)
  • You must be logged in to reply to this topic.