testing component with ng-container in DOM Angular , Karma fails? - html

Hello I have a component which display a sub-menu like to the right this:
the html code is below
<h1>{{title}}</h1>
<ng-container *ngFor="let link of links">
<a class="vsk__button-link" [ngClass]="{'vsk__is-active': isActive(link.route)}" [routerLink]="link.route" routerLinkActive="true" *ngIf="containsRoles(link.roles)">
<label>{{link.page}}</label>
</a>
</ng-container>
As you can see I'm displaying links which comes from property binding.
And the problem is that when I'm launching the test my DOM sees only mark-up whose innerHtml is void and ng-container don't appear. Does it mean that I should create other components which till I'm allowed to access this 3 links?
for instance this is my code in karma spec.ts
beforeEach(() => {
fixture = TestBed.createComponent(SubMenuComponent);
component = fixture.componentInstance;
adminFixture.detectChanges();
});
it('should create ng-container', () => {
expect(fixture.debugElement.children[1].children).toBeTruthy();
});

Related

Routing inside a component

I have a component that I use in a page and this component have tabs in it, the tabs works fine they aren't components though, they are just in the component.
This is what I'm trying to do, have a new link when clicking on a tab:
entity/someUid <-- Current behaviour
entity/someUid/messages <-- When clicking on a tab
entity/someUid/languages <-- When clicking on a tab
Page:
<app-entity
(currentTab)="getCurrentTab($event)"
></app-entity>
Page Module:
const routes: Routes = [
{
path: '',
component: EntityPage
}
];
Page Component:
// It does navigate like the example above but instead my 404 page is showing up
getCurrentTab(tab: string) {
this.router.navigate(['entity', this.entity.uid, tab]);
}
The component itself:
<ul>
<li (click)="view = 'messages'; currentTab.emit('messages')">Messages</li>
<li (click)="view = 'languages'; currentTab.emit('languages')">Languages</li>
</ul>
<div *ngIf="view == 'messages'">
Messages Content
</div>
<div *ngIf="view == 'languages'">
Languages Content
</div>
#Output() currentTab: EventEmitter<string> = new EventEmitter();
view: string
How can I make my component have routing while I still can render this component inside a page?
Try this
https://codecraft.tv/courses/angular/routing/nested-routes/
Children routes by adding one more router outlet in app entity and make the routes declared in its module

Angular 8 accordion with Dynamic data without using bootstrap/angular material

I am trying to implement accordion functionality without using bootstrap/angular material accordion. My data is coming dynamically from an api.
I tried doing below but that opens and closes all the panels together. I understand the reason behind it but I don't understand how to approach.
Component.ts
export class AccordionComponent implements OnInit {
isHidden = true;
mFaqs: IFaq[];
constructor(private faqService: FaqService) { }
ngOnInit() {
this.faqService.getFaqs()
.subscribe(faqData => this.mFaqs = faqData );
}
}
component.html
<div class="custom-header" hideToggle="true" (click)="isHidden = !isHidden" *ngFor="let faq of mFaqs?.faqs">
<section>
<section>
Q: {{ faq.question }}
</section>
<p [hidden]="isHidden">
{{ faq.answer }}
</p>
</section>
</div>
It should only close/open the clicked one.
You need to pass unique id for that.
Might be it'll help you.
Angular on click event for multiple items
please go through it.

How to include angular components in doc.fromhtml() jspdf?

I am trying to generate a pdf which includes angular components.
typescript:
let doc = new jsPDF();
let source = document.getElementById("content");
doc.fromHTML(
source,
15,
15,
{
'width': 180
});
doc.save("sample.pdf");
}
}
html:
<div id="content">
<sample-card *ngFor="let x of list; let i = index"
[selection] = list
<sample-card>
</div>
I am using Angular 4
doc.fromHTML() is working for simple 'div' like To be downloaded. But its not working for angular components. How to achieve this?
I did in this way ,
Passsing values in app html to the details, where it display those values in details html
In app.component.html
<div id="content" details [app1]="app" *ngFor="let app of applications">
In details.component.html
{{app1.labels}}
Here i'm waiting to get the data to be loaded[Tried with AfterViewChecked -- keeps on saving files and AfterViewInit is giving null (as it is executing only once)]
So waited for Random time say 5 sec[not good but its downloading the file]
setTimeout(()=>{
setTimeout(this.saveFile(), 1000*5);
},3000);
Here is the output i got[on left pdf and on right webpage with some extra content]
For the id on top div
<div id="content" >
Things to Learn
<div details [app1]="app" *ngFor="let app of applications">
</div>
Hari
</div>
Output is:
Method-2 :
Here i am using event emitter to tell parent when to save the file
when the child component receives the final item from the array of items [passed by parent to child] ,telling parent to save the file now [as i have received all of your items in the array]
Implemented with AfterViewInit where it emits true when length and count are same
On left html and on right ts [of parent]
on left ts and on right html [of child]
Output

Dynamically ADDING and REMOVING Components in Angular

The current official docs only shows how to dynamically change components within an <ng-template> tag. https://angular.io/guide/dynamic-component-loader
What I want to achieve is, let's say I have 3 components: header, section, and footer with the following selectors:
<app-header>
<app-section>
<app-footer>
And then there are 6 buttons that will add or remove each component: Add Header, Add Section, and Add Footer
and when I click Add Header, the page will add <app-header> to the page that renders it, so the page will contain:
<app-header>
And then if I click Add Section twice, the page will now contain:
<app-header>
<app-section>
<app-section>
And if I click Add Footer, the page will now contain all these components:
<app-header>
<app-section>
<app-section>
<app-footer>
Is it possible to achieve this in Angular? Note that ngFor is not the solution I'm looking for, as it only allows to add the same components, not different components to a page.
EDIT: ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined. What I am looking for is something like a stack of components or an array of components where we can add, remove, and change any index of the array easily.
EDIT 2: To make it more clear, let's have another example of why ngFor does not work. Let's say we have the following components:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
Now here comes a new component, <app-description>, which the user wants to insert in between and <app-editor>. ngFor works only if there is one same component that I want to loop over and over. But for different components, ngFor fails here.
What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolver and then injecting them into a ViewContainerRef. One way to do this dynamically is by passing the class of the component as an argument of your function that will create and inject the component.
See example below:
import {
Component,
ComponentFactoryResolver, Type,
ViewChild,
ViewContainerRef
} from '#angular/core';
// Example component (can be any component e.g. app-header app-section)
import { DraggableComponent } from './components/draggable/draggable.component';
#Component({
selector: 'app-root',
template: `
<!-- Pass the component class as an argument to add and remove based on the component class -->
<button (click)="addComponent(draggableComponentClass)">Add</button>
<button (click)="removeComponent(draggableComponentClass)">Remove</button>
<div>
<!-- Use ng-template to ensure that the generated components end up in the right place -->
<ng-template #container>
</ng-template>
</div>
`
})
export class AppComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
// Keep track of list of generated components for removal purposes
components = [];
// Expose class so that it can be used in the template
draggableComponentClass = DraggableComponent;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
addComponent(componentClass: Type<any>) {
// Create component dynamically inside the ng-template
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
const component = this.container.createComponent(componentFactory);
// Push the component so that we can keep track of which components are created
this.components.push(component);
}
removeComponent(componentClass: Type<any>) {
// Find the component
const component = this.components.find((component) => component.instance instanceof componentClass);
const componentIndex = this.components.indexOf(component);
if (componentIndex !== -1) {
// Remove component from both view and array
this.container.remove(this.container.indexOf(component));
this.components.splice(componentIndex, 1);
}
}
}
Notes:
If you want to make it easier to remove the components later on, you can keep track of them in a local variable, see this.components. Alternatively you can loop over all the elements inside the ViewContainerRef.
You have to register your component as an entry component. In your module definition register your component as an entryComponent (entryComponents: [DraggableComponent]).
Running example:
https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5
For more information:
https://angular.io/guide/dynamic-component-loader
Angular v13 or above - simple way to add dynamic components to DOM
parent.component.html
<ng-template #viewContainerRef></ng-template>
parent.component.ts
#ViewChild("viewContainerRef", { read: ViewContainerRef }) vcr!: ViewContainerRef;
ref!: ComponentRef<YourChildComponent>
addChild() {
this.ref = this.vcr.createComponent(YourChildComponent)
}
removeChild() {
const index = this.vcr.indexOf(this.ref.hostView)
if (index != -1) this.vcr.remove(index)
}
Angular v12 or below
I have created a demo to show the dynamic add and remove process.
The parent component creates the child components dynamically and removes them.
Click for demo
Parent Component
// .ts
export class ParentComponent {
#ViewChild("viewContainerRef", { read: ViewContainerRef })
VCR: ViewContainerRef;
child_unique_key: number = 0;
componentsReferences = Array<ComponentRef<ChildComponent>>()
constructor(private CFR: ComponentFactoryResolver) {}
createComponent() {
let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);
let childComponentRef = this.VCR.createComponent(componentFactory);
let childComponent = childComponentRef.instance;
childComponent.unique_key = ++this.child_unique_key;
childComponent.parentRef = this;
// add reference for newly created component
this.componentsReferences.push(childComponentRef);
}
remove(key: number) {
if (this.VCR.length < 1) return;
let componentRef = this.componentsReferences.filter(
x => x.instance.unique_key == key
)[0];
let vcrIndex: number = this.VCR.indexOf(componentRef as any);
// removing component from container
this.VCR.remove(vcrIndex);
// removing component from the list
this.componentsReferences = this.componentsReferences.filter(
x => x.instance.unique_key !== key
);
}
}
// .html
<button type="button" (click)="createComponent()">
I am Parent, Create Child
</button>
<div>
<ng-template #viewContainerRef></ng-template>
</div>
Child Component
// .ts
export class ChildComponent {
public unique_key: number;
public parentRef: ParentComponent;
constructor() {
}
remove_me() {
console.log(this.unique_key)
this.parentRef.remove(this.unique_key)
}
}
// .html
<button (click)="remove_me()">I am a Child {{unique_key}}, click to Remove</button>

Rendering angular directive inside reactjs component

I want to render an AngularJS directive inside a React component. For example, take the following directive for a carousel:
var reactComp = React.createClass({
render: function() {
return ("<div>\
<ul rn-carousel class=\"someClass\">\
<li className=\"someSortOfClass\">\
</li>\
</ul>\
</div>");
}
});
Apparently the directive is not called whatsoever. I also tried to use the componentDidMount function on react to insert the correct directive, but I guess it's a different environment, so that directive isn't called either.
Could anyone shed some light on this?