Is it possible to create HTML items from JS in Angular 2? - javascript

I have simple GET request, which brings some data to show.
this.apiRequest.get(url)
.map(response => response.json())
.subscribe(response => {
this.notes = response.notes
loader.dismiss()
}, err => {
loader.dismiss()
})
Then I show it like this:
<ion-item *ngFor="let note of notes" text-wrap>
<!-- data -->
</ion-item>
The main problem is that when there is a lot of notes the loader dismisses before all items are shown.
I need loop ngFor in js and then hide loader or somehow disable loader from HTML...

this is not what you are looking for but hope will help
Base idea is to provide ngFor loop complete event
Create component
import { Component, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'islast',
template: '<span></span>'
})
export class LastDirective {
#Input() isLast: boolean;
#Output() onLastDone: EventEmitter<boolean> = new EventEmitter<boolean>();
ngOnInit() {
if (this.isLast)
this.onLastDone.emit(true); //you can hide loader from here this is last element in ngfor
}
}
in html
<tr *ngFor="let sm of filteredMembers; let last = last; let i=index;" data-id="{{sm.Id}}">
<td>
<islast [isLast]="last" (onLastDone)="modalMembersDone()">

Related

How to add navigation link to different buttons on the menu in Angular?

I want to add navigation path to all my buttons in the left menu (which is not the main menu).
I am getting the menu items name as #Input. I have created a dictionary for all the items name and their navigation path.
Here is the HTML:
<div class="row-styles" id="elements" *ngFor="let item of elements">
<button *ngIf="(item.action !== NO_ACCESS )" class="inner-children" routerLinkActive="active" id="inner-children"
[routerLink]="">
<span>{{item.resource}}</span>
</button>
</div>
Here is the TS file
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'apm-menu-resource',
templateUrl: './menu-resource.component.html',
styleUrls: ['./menu-resource.component.less']
})
export class MenuResourceComponent implements OnInit {
#Input() public elements = [];
constructor() {
const menupath = new Map<string, string>();
menupath.set('General', '/Adigem/config/general');
menupath.set('Messaging', '/Adigem/config/messaging');
menupath.set('Server', '/Adigem/config/email/server');
menupath.set('Alerting', '/Adigem/config/email/alert');
menupath.set('Network', '/Adigem/config/network');
menupath.set('Inventory', '/Adigem/config/inventory');
menupath.set('External port', '/Adigem/config/snmp/external-port');
menupath.set('Cloud Data', '/Adigem/config/clouddata');
menupath.set('Performance', '/Adigem/config/Performance');
menupath.set('CFG', '/Adigem/config/cfg');
menupath.set('System', '/Adigem/config/system');
console.log(menupath);
}
ngOnInit() {
}
}
I want to know what to add in the router link in the HTML so that it navigates to the proper menu item.
If you have access to the elements array, that's being passed to the component, you could simplify things a lot - you just add the target path to each of the items and your MenuResourceComponent won't have to deal with any path-related logic.
From your snippets I infer that there is a resource property, which is the element's title. If so, the elements array can be modified like this:
elements = [
{resource:'General', path: '/Adigem/config/general'},
{resource:'Messaging', path: '/Adigem/config/messaging'},
...
]
and then in the template:
<div class="row-styles" id="elements" *ngFor="let item of elements">
<button *ngIf="(item.action !== NO_ACCESS )" class="inner-children"
routerLinkActive="active" id="inner-children"
[routerLink]="item.path">
<span>{{item.resource}}</span>
</button>
</div>
However, if you have no other options and need to menupath map, then you can make it a class field:
import { Component, Input } from '#angular/core';
#Component({
selector: 'apm-menu-resource',
templateUrl: './menu-resource.component.html',
styleUrls: ['./menu-resource.component.less']
})
export class MenuResourceComponent{
#Input() public elements = [];
menupath = new Map<string, string>();
constructor() {
this.menupath.set('General', '/Adigem/config/general');
this.menupath.set('Messaging', '/Adigem/config/messaging');
this.menupath.set('Server', '/Adigem/config/email/server');
this.menupath.set('Alerting', '/Adigem/config/email/alert');
this.menupath.set('Network', '/Adigem/config/network');
this.menupath.set('Inventory', '/Adigem/config/inventory');
this.menupath.set('External port', '/Adigem/config/snmp/external-port');
this.menupath.set('Cloud Data', '/Adigem/config/clouddata');
this.menupath.set('Performance', '/Adigem/config/Performance');
this.menupath.set('CFG', '/Adigem/config/cfg');
this.menupath.set('System', '/Adigem/config/system');
console.log(this.menupath);
}
}
and the route binding looks like:
[routerLink]="menupath.get(item.resource)"
I wouldn't encourage the second solution, because you will have to handle the potential case where you receive an item, which is unknown for your menupath map.
Also I have a concern with the NO_ACCESS constant that you use in your template. There is no such property of the component, so this probably breaks the compilation.

Angular - Using custom directive to load several components is returning undefined

I am developing an app and for now, I have a dynamic grid generator which divides the space in the screen to fit several components dynamically. So, the component encharged of this must render the components after angular has rendered the page. In order to achieve that I've followed the angular dynamic component loader guide (https://angular.io/guide/dynamic-component-loader).
So I am in a point where I do have the component where the other components must be rendered, I have my custom directive to render the components.
The directive
#Directive({
selector: '[componentLoader]'
})
export class ComponentLoaderDirective {
constructor (
public ViewContainerRef: ViewContainerRef
) {}
}
Now the component ( grid component )
grid.component.ts
// ... Stuff above
export class GridComponent implements OnInit {
#Input() public items: gridItem[] = [];
#ViewChild(ComponentLoaderDirective) componentLoader: ComponentLoaderDirective | undefined;
constructor(
private sanitizer: DomSanitizer,
private componentFactoryResolver: ComponentFactoryResolver
) {}
ngOnInit(): void { this.processRow(this.items) }
processRow( row: gridItem[] ) {
// Some grid related stuff ...
for ( let item of row ) {
// Stuff performed over every item in each grid row
this.renderComponentIfNeeded(item)
}
}
renderComponentIfNeeded( item: gridItem ):void {
if ( item.components ) {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
let viewContainerRef = this.componentLoader.ViewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent<any>(componentFactory);
componentRef.instance.data = item;
console.log('Directive ', this.componentLoader, 'ComponentRef: ', componentRef);
}
}
And the HTML of the component:
<!-- Dynamic grid generation using ng-template and ng-content. This is generated several times using the *ngFor, for every item in the items array we will have a componentLoader -->
<ng-template componentLoader>
</ng-template>
There is a lot more content in these files but for simplicity I will only post this, If you need more code just tell me.
Okay, so my problem is that when I access to this.contentLoader the returned value is just undefined, so this.componentLoader.viewContainerRef causes an error because componentLoader is undefined.
I've tried adding the exportAs property to the directive's decorator and it is giving exacly the same error.
I've also tried to add the directive in the module declarations without success, and changed the <ng-template componentLoader> to <ng-template #loader=componentLoader> which causes a different error ( No directive has 'componentLoader' exportAs or something like this )
PS: In the ´´´this.componentFacotryResolver.resolveComponentFactory(component)``` I successfully have each component that has been given to the grid.
I prefer you not to solve my issue but to point me in the right direction and help me see what am I doing wrong in order to improve myself.
Any help will be much appreciated :)
I've managed to solve this issue in a very simple way.
I was trying to do too many things inside the grid component so I removed to code related to the component loader and moved it into a single component, called ComponentLoaderComponent.
Inside the component I've setted up all the logic in the same way than I did in the grid component. So now I have a new ts file like this:
import { Component, ComponentFactoryResolver, Input, OnInit, ViewChild } from '#angular/core';
import { ComponentLoaderDirective } from 'src/app/shared/directives/componentLoader.directive';
#Component({
selector: 'component-loader',
templateUrl: './component-loader.component.html',
styleUrls: ['./component-loader.component.css']
})
export class ComponentLoaderComponent implements OnInit {
#Input() public component: any;
#ViewChild(ComponentLoaderDirective, { static: true }) componentLoader!: ComponentLoaderDirective;
constructor(
private componentFactoryResolver: ComponentFactoryResolver
) { }
ngOnInit(): void {
this.loadComponent();
}
loadComponent():void {
if (this.component) {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.component);
let viewContainerRef = this.componentLoader.viewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent<any>(componentFactory);
}
}
And an HTML like this:
<ng-template componentLoader>
</ng-template>
Now from the grid component I only have to call the ComponentLoader for every component I want to add to the grid, so the grid html will look like this:
<div
*ngIf=" gridItem.components && gridItem.components.length > 0"
class="component-container"
>
<component-loader
*ngFor="let component of gridItem.components"
[component]="component">
</component-loader>
</div >
Now the components are getting loaded correclty, anyways I still don't know what I was missing in before.

Some times data is not populating into view (ngFor) through array push in angular 8

I have a simple data which I am populating through ngFor into view. I am getting all the objects and pushing into array and then from there I am populating into html.Here its working fine.But in my project the scenario is like I need to get into this page by selecting a event from a previous page.When I am frequently going and selecting events and coming to this page, some cases my data pushing into array but not populating into view.Is there any solution for this.Here is the code below
home.component.html
<div>
<table>
<tr *ngFor="let x of groupList">
<td ><span>{{x.vehicle_number}}</span></td>
<td ><span>{{x.vehicle_name}}</span></td>
<td ><span>{{x.status}}</span></td>
</tr>
</table>
</div>
home.component.html
import { Component, OnInit } from '#angular/core';
import { CurrencyPipe } from './../pipes/currency.pipe';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
imageSource :any;
statusdata1: any;
moreThanTen:boolean = false;
showit:boolean = false;
groupList:any = [];
constructor() {}
ngOnInit() {
/* First data */
let response =
{"vehicle_number":1,"vehicle_name":"car","status":"yellow"}
let response1 = {"vehicle_number":0,"vehicle_name":"car","status":"yellow"}
let response2 = {"vehicle_number":2,"vehicle_name":"car","status":"yellow"}
this.groupList.push(response,response1,response2);
console.log(this.groupList);
}
}
Please try to implement onPush or ChangeDetectionStrategy in your component
Doing this will instruct Angular to run change detection on these components and their sub-tree only when new references are passed to them versus when data is simply mutated.
Run this.ref.markForCheck() or this.ref.detectChanges() when you update your variable and want it to reflect in html
Please check the following links for more information
https://angular.io/api/core/ChangeDetectionStrategy
https://alligator.io/angular/change-detection-strategy/

How to load dynamic components based on a property from object?

I'm trying to build a list of cards which may contain different components; So for example I have the following array of objects:
{
title: 'Title',
descrption: 'Description',
template: 'table',
},
{
title: 'Title',
descrption: 'Description',
template: 'chart',
}
I get this array as a response from a service, then I need to match each of thos objects to a component based on the template property, so for example, the first item should match to the TableComponent and the second one to the ChartComponent;
I'm trying to follow the Angular Docs regarding Dynamic Component Loading, but I'm not sure how tell the method how to match each object in the array to a specific component.
In my parent component I have made an anchor point where the components should load with a directive:
<ng-template appCheckpointHost></ng-template>
And I'm trying to use the ComponentFactoryResolver as it shows in the example.
loadComponent() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ChartCheckpointComponent);
const viewContainerRef = this.checkHost.viewContainerRef;
}
The example shows a scenario in which the "service" runs every three seconds, gets a random item, and shows it; but what I'm trying to do instead is to fetch all the items when the parent component loads, and render each item with its respective component.
Any ideas to get this to work?
You can create a dictionary like:
const nameToComponentMap = {
table: TableComponent,
chart: ChartComponent
};
And then just use this dictionary to determine which component should be rendered depending on the template property of particular item in your items array:
const componentTypeToRender = nameToComponentMap[item.template];
this.componentFactoryResolver.resolveComponentFactory(componentTypeToRender);
You can view my blog here
First I will need to create a directive to reference to our template instance in view
import { Directive, ViewContainerRef } from "#angular/core";
#Directive({
selector: "[dynamic-ref]"
})
export class DynamicDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Then we simply put the directive inside the view like this
<ng-template dynamic-ref></ng-template>
We put the directive dynamic-ref to ng-content so that we can let Angular know where the component will be render
Next I will create a service to generate the component and destroy it
import {
ComponentFactoryResolver,
Injectable,
ComponentRef
} from "#angular/core";
#Injectable()
export class ComponentFactoryService {
private componentRef: ComponentRef<any>;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
createComponent(
componentInstance: any,
viewContainer: any
): ComponentRef<any> {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
componentInstance
);
const viewContainerRef = viewContainer.viewContainerRef;
viewContainerRef.clear();
this.componentRef = viewContainerRef.createComponent(componentFactory);
return this.componentRef;
}
destroyComponent() {
if (this.componentRef) {
this.componentRef.destroy();
}
}
}
Finally in our component we can call the service like this
#ViewChild(DynamicDirective) dynamic: DynamicDirective;
constructor(
private componentFactoryService: ComponentFactoryService
) {
}
ngOnInit(){
const dynamiCreateComponent = this.componentFactoryService.createComponent(TestComponent, this.dynamic);
(<TestComponent>dynamiCreateComponent.instance).data = 1;
(<TestComponent>dynamiCreateComponent.instance).eventOutput.subscribe(x => console.log(x));
}
ngOnDestroy(){
this.componentFactoryService.destroyComponent();
}
/////////////////////////////////
export class TestComponent {
#Input() data;
#Output() eventOutput: EventEmitter<any> = new EventEmitter<any>();
onBtnClick() {
this.eventOutput.emit("Button is click");
}
}

how to create a component that i can set fetching url data in an attribute in angular 2

I'm trying to create a application with angular 2 , i want to create a component in angular 2 that I set URL in attribute and want use several times from this component and each component have own data...
i want something like this :
its possible or not?
I'll really appreciate if someone help me.
new movies :
<comp url="www.aaaa.com/movies?type=new"></comp>
old movies :
<comp url="www.aaaa.com/movies?type=old"></comp>
#Component({
selector: 'comp',
template: '<div>{{data}}</div>'
})
export class Component {
#Input() url: string;
constructor(private http:Http) {
}
ngOnChanges(changes) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(val => this.data = val);
}
}
If the component has more than one input then you need to check which one was updated. See https://angular.io/api/core/OnChanges for more details.
You can use compoenent as mentioned above answer.
But for this kind of task I always choose directive. You can also create a directive which will take one parameter and do the stuff.
In this way you don't have to create a tag but in any tag you can apply your directive.
#Directive({
selector: '[comp]',
})
export class compDorective implements OnInit {
#Input() url: string;
constructor(private http:Http, private elementRef: ElementRef) {
}
ngOnInit(changes) {
this.http.get(this.url)
.map(res => res.json())
.subscribe(val => this.elementRef.nativeElement.innerHtml = val);
}
}
you can apply this directive to any element like this
<div comp [url]="www.aaaa.com/movies?type=new"></div>
<span comp [url]="www.aaaa.com/movies?type=old"></span>

Categories