Angular 2-RC4 Dynamic Content Loading - javascript

Currently, I'm trying incorporating Leaflet into Angular 2-RC4. I have faced with the following problem of dynamically loading html into popupContent of leaflet markers.
In the parent class I have the following code:
export class BeaconLayerComponent implements OnInit {
constructor(private resolver: ComponentResolver, private viewRef: ViewContainerRef) {}
createMultipleDynamicInstance(markers) {
markers.foreach((marker) => {
createDynamicInstance(marker);
}
}
createDynamicInstance() {
this.resolver.resolveComponent(BeaconPopupComponent).then((factory:ComponentFactory<BeaconPopupComponent>) => {
let popupComponentRef: ComponentRef<BeaconPopupComponent>;
popupComponentRef = this.viewRef.createComponent(factory);
popupComponentRef.instance.name = beaconJSON.name;
popupComponentRef.instance.afterViewCheckedEventEmitter.subscribe((popupInnerHTML: string) => {
//make use of the innerHTML
// ... <= Create the leaflet marker with innerHTML as popupContent
//After retrieving the HTML, destroy the element
popupComponentRef.destroy();
})
});
Child component:
import {Component, AfterContentInit, EventEmitter, AfterViewInit, AfterViewChecked, ElementRef} from "#angular/core";
#Component({
selector: 'beaconPopup',
template: `
<div>{{name}}</div>
`
})
export class BeaconPopupComponent implements AfterViewChecked {
constructor(private elementRef: ElementRef) {}
name:string;
public afterViewCheckedEventEmitter = new EventEmitter();
ngAfterViewChecked() {
console.log("ngAfterViewChecked()");
this.afterViewCheckedEventEmitter.emit(this.elementRef.nativeElement.innerHTML);
}
}
When I run the html I get these errors:
2016-07-19 00:02:29.375 platform-browser.umd.js:1900 Error: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'Happening Beacon'
at ExpressionChangedAfterItHasBeenCheckedException.BaseException [as constructor]
I'm trying to avoid getting the DOM element via JQuery
getDynamicElement(name)
{
str = "<div><div>" + name + "<div></div>"
return $(str).get[0]
}
Is there a better way to do it in Angular 2?

These are two tricks inside your code, 1 dynamical load, 2 how to use jQuery($)
Here is how I create dynamica component from string
compileToComponent(template1: string): Promise<ComponentFactory<any>> {
const metadata = new ComponentMetadata({
template: template1,
directives: ROUTER_DIRECTIVES
});
let decoratedCmp = Component(metadata)(class DynamicComponent { });
return this.resolver.resolveComponent(decoratedCmp);
}
For more details check here
https://github.com/Longfld/DynamicRouter_rc4/blob/master/app/MyRouterLink.ts
or demo here http://plnkr.co/edit/diZgQ8wttafb4xE6ZUFv?p=preview
For rc5 see here :http://plnkr.co/edit/nm5m7N?p=preview

Related

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");
}
}

Creating nested dynamic components in angular

I Want to know how to create nested dynamic components and maintains its parent child relationship.
For example, I have data like this,
- A
--A.1
--A.2
-B
--B.1
-C
I wanted to create the component like this,
<A>
<A1></A1>
<A2></A2>
</A>
<B>
<B1></B1>
</B>
<C></C>
But with my code I could only create parent component or child component. But not both.
Below is my code,
setRootViewContainerRef(view: ViewContainerRef): void {
this.rootViewContainer = view;
}
createComponent(content: any, type: any) {
console.log(content);
if (content.child && content.child.length > 0) {
content.child.forEach(type => {
const typeP = this.contentMappings[type.type];
this.createComponent(type, typeP);
});
} else {
this.renderComp(content,type)
}
}
renderComp(content,type) {
if (!type) {
return
}
this.componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
this.componentReference = this.rootViewContainer.createComponent(this.componentFactory);
if (this.componentReference.instance.contentOnCreate) {
this.componentReference.instance.contentOnCreate(content);
}
}
With this code, I get this output.
Link to working example, StackBlitz
Please help me to resolve this issue.
Updated.
Even after adding the viewChild, It still throws the viewchild not defined.
Refer this image, In the component.instance I'm not seeing the view child element.
Updated stackblitz link https://stackblitz.com/edit/angular-dynamic-new-mepwch?file=src/app/content/a/a.component.ts
You should create ViewContainer on each level that is going to render child components:
a.component.html
<p>
a works!
</p>
<ng-container #container></ng-container>
a.component.ts
export class AComponent implements OnInit {
#ViewChild('container', { read: ViewContainerRef, static: true }) embeddedContainer: ViewContainerRef;
And then render component to dedicated container:
create-dynamic-component.service.ts
#Injectable()
export class CreateDynamicComponentService {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
#Inject(CONTENT_MAPPINGS) private contentMappings: any,
private inlineService: InlineService
) { }
createComponent(content: any, type: any, vcRef) {
const componentRef = this.renderComp(content, type, vcRef)
if (content.child && content.child.length) {
if (!componentRef.instance.embeddedContainer) {
const cmpName = componentRef.instance.constructor.name;
throw new TypeError(`Trying to render embedded content. ${cmpName} must have #ViewChild() embeddedContainer defined`);
}
content.child.forEach(type => {
const typeP = this.contentMappings[type.type];
this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
});
}
}
renderComp(content,type, vcRef: ViewContainerRef) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type);
const componentRef = vcRef.createComponent<any>(componentFactory);
if (componentRef.instance.contentOnCreate) {
componentRef.instance.contentOnCreate(content);
}
return componentRef;
}
}
Note how renderComp method takes ViewContainerRef from the component with children:
this.createComponent(type, typeP, componentRef.instance.embeddedContainer);
Forked Stackblitz

Angular - How can I toggle the visibility of an element in a component from another component?

I have the following scenario in my Angular app:
A component MainDashboardComponent that is visible when I have the route /. Obviously I have the <router-outlet> tag in my app.component.html file, which looks like this:
<app-side-menu></app-side-menu>
<div class="main-container">
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
As you can see I have a SideMenuComponent I use to have a side menu on all my routes. In MainDashboardComponent I have a method that for some reason needs to toggle a chat element that is situated on the side menu.
Inside the SideMenuComponent I have a method that handles the visibility toggle for the chat element and it works as expected. How can I call this method from my MainDashboardComponent and toggle the chat element from there?
What I tried with no success
I tried to inject the SideMenuComponent inside my MainDashboardComponent but, though the method toggleChat() is called, the element doesn't change it's visibility. Looks like I have a kind of multiple instance of the same component I guess...
Can you please help me with this? Thank you!
MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
templateUrl: './main-dashboard.component.html',
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() {}
setFocus(id) {
// here I'd like to call SideMenuComponent togglechat() ...
}
}
SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
}
}
To communicate between different components, there are different ways.
If you want to communicate between parent and child component, you can use EventEmitter to emit event from child component and handle the event in your parent component
If you want to communicate between any components, you can use Service and implement communication with the help of EventEmitter or Subject/BehaviorSubject
In your case, we can create a service, myService.ts and declare and eventEmitter
.service.ts
#Injectable()
export class AppCommonService {
toggle : EventEmitter<boolean> = new EventEmitter<boolean>()
}
mainDashboard.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
toggleChat(){
this.myService.toggle.emit(!this.chatStatus);
}
sideMenu.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
Generally this is the domain of a service!
Just create a service and add the "showCat" property.
Inject the service into both components
Alter SideMenuComponent to:
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
Alter MainDashboardComponent, also use this.myService.showChat to show / hide your chat window
Service TS
#Injectable()
export class MyService{
showCat:boolean = true
}
MainDashboardComponent
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
SideMenuComponent
chatVisiblity = this.myService.showCat //<-- bind this to the element attribute
You could efficiently use child to parent communication in this scenario. You'll need to create a custom event using angular's EventEmitter in your SideMenuComponent and use it in your MainDashboardComponent.
So, here is some code that may help you -
// SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
#Output() valueChange = new EventEmitter();
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
this.valueChange.emit(this.showChat);
}
}
// MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
template: `<app-side-menu (valueChange)='setFocus($event)'></app-side-menu>`
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
setFocus(event) {
// check for required input value
console.log(event);
}
}
Refer these tutorials if required -
https://dzone.com/articles/understanding-output-and-eventemitter-in-angular,
https://angular-2-training-book.rangle.io/handout/components/app_structure/responding_to_component_events.html

Multiple instance of angular 4 directive called from a component mesed up the input values

I have a component in angular 4 that is called three times. In template metadata I have a div with a directive with some bindings like this.
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {};
this.cOptions = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
//this.report.opt is binded to a component when is instantiated.
//this.gServ.objectMerge is a function that merge the two objects
}
}
this.cOptions change for every instance of the component, then in the directive I have this:
import { Directive, ElementRef, HostListener, Input, OnInit } from '#angular/core';
#Directive({
selector: '[gDirective]'
})
export class SGDirective implements OnInit {
public _element: any;
#Input() public cOptions: string;
constructor(public element: ElementRef) {
this._element = this.element.nativeElement;
}
ngOnInit() {
console.log(this.cOptions);
}
}
The problem is that console.log(this.cOptions); always print the same object, even when component set cOptions with diferent values in ngOnInit method of the compnent.
Do you have some idea what is wrong?
Your component property binding [cOptions]="dataChart" doesn't look good, reason being your dataChart is not even defined. it should be like [DIRECTIVE_PROPERTY]="COMPONENT_PROPERTY" and your COMPONENT_PROPERTY is not even defined in SGComponent component class.
Your component class should be something like this:
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="Options">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = {};
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
#Ashwani points out a valid problem with your code. The way your template is wiring things up, nothing will ever be passed to the SGDirective input.
Another potential problem you could be running into has to do with the gServ code. If gServ is a singleton (which is probably the case) and it is returning the same object to each of the SGComponents, then all the SGDirectives will have the same value. A simple way to test this is to put {{Options | json}} in the SGComponent template.
To create a new instance of the gServ service for each SGComponent you can add a providers array to the #Component metadata. It would look like this:
import {gServ} from '../gServ.service';
#Component({
selector: 'sr-comp',
template: `{{Options | json}}<div gDirective [cOptions]="Options"></div>`
providers: [gServ],
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
You have probably the same return/value at this.gServ.objectMerge) (you can test it wihtout calling the service, and passing each one one different objet make by you)
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
//#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {nicolas: 'nicolas1'}; //change this in the next component that use the directive
}
}
If that is the case, your problem is that gServ is provide at the same rootComponent. with angular, service provider at the same rootComponent are singleton.
And use the same type in your directive and your component!!

Angular2: making an iframe src safe using a directive

I am trying to apply a sanitized src attribute to an iframe, directly it works fine, but when putting it all in an attribute directive, it refuses to play ball. Here is the directive code, and the error message that appears
import { OnInit, Directive, ElementRef, Input, Renderer } from '#angular/core';
import { DomSanitizer, SafeResourceUrl} from '#angular/platform-browser';
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
let resumeUrl: SafeResourceUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
// this.el.nativeElement.src = resumeUrl.toString(); // same result
this.render.setElementProperty(this.el.nativeElement, 'src', _url);
// using 'srcdoc' or setElementAttribute brings same results
}
}
I get error: SafeValue must use [property]=binding: /theurl/x.pdf#zoom=100 (see http://g.co/ng/security#xss)
You can try #HostBinding() - not sure if this will work though
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
#HostBinding('src')
resumeUrl:any;
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
this.resumeUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
}
}
this.render.setElementProperty doesn't care about sanitized values, it just calles to DOM APIs and passes the sanitized value as-is.

Categories