Angular 2 RC1: DataBinding and ComponentResolver - javascript

I've recently updated my project from beta.15 to RC1. Now, DynamicComponentLoader is deprecated. So I've re-writed my code using ComponentResolver.
My component is correctly loaded into another but I'm experiencing an issue : data-binding seems to doesn't works.
Here is my code :
#Component({
selector: 'browser-pane',
styleUrls: ['src/modules/component#browser/src/styles/pane.css'],
template: `
<li class="pane">
<ul class="nodes">
<li *ngFor="let node of nodes; let i = index" id="node-{{i}}"></li>
</ul>
</li>
`
})
export class PaneComponent implements OnInit {
#Input() nodeId: number;
#Input() nodes: any[];
#Input() fillerComponent: any;
#Input() mapComponentData: any;
constructor(private _injector: Injector, private _cr: ComponentResolver) {}
ngOnInit(): any {
this.nodes.forEach((node: any, i: number) => {
this._cr.resolveComponent(this.fillerComponent)
.then((factory: ComponentFactory) => factory.create(this._injector, null, `#node-${i}`))
.then((ref: ComponentRef<any>) => this.mapComponentData(ref.instance, node));
})
}
}
mapComponentData is just a function which map data to the component ref. In my case, the component that I'm creating dynamically needs an #Input() named 'media'. This function will do the following instruction: myComponentInstance.media = media;.
Here is the filler component (simplified) :
#Component({
selector: 'cmp-media-box',
styleUrls: ['src/modules/component#media-node/src/style.css'],
pipes: [ MediaCountPipe ],
template: `
<div class="media-node"
(click)="onClick()"
(dragover)="onDragover($event)"
(drop)="onDrop($event)"
[class.dropDisable]="!drop">
<span class="title">{{ media.title }}</span>
<div class="box" [class.empty]="isEmpty()">{{ media | mediaCount:'playlists':'channels' }}</div>
</div>
`
})
export class MediaNodeComponent {
#Input() media: Media;
private _OSelection:Observable<Media[]>;
private _selectionSubscription: Subscription;
private _removeAllFromSelection;
drop: boolean = false;
constructor(private _store:Store<AppStore>, private _mediaService:MediaService) {
setTimeout(() => this.initialize(), 0);
}
initialize():any {
if(this.media.type === MEDIA) {
this._OSelection = this._store.select(s => s['data#media'].selected);
this._removeAllFromSelection = mediaRemoveAll;
} else if(this.media.type === CHANNEL) {
this._OSelection = this._store.select(s => s['data#channel'].selected);
this._removeAllFromSelection = channelRemoveAll;
}
this.subscribeToSelection();
}
// ...
}
So what's the problem ? Inside thisfillerComponent, this.media is defined. If i put a console.log(this.media) inside initialize, I can see it. But I can't use it inside the template. I've tried many things :
use {{media?.title}}
use {{media.title | async}}
remove the #Input() in front of the media declaration
stop passing media and use a hard-coded variable (just in case)
use DynamicComponentLoader : same result. But I think that dcl uses ComponentResolver behind (not sure about that, I'm checking this point)
...
In other words : I can't use variables inside my template.
What am I doing wrong ?
Speaking about this code, there is another thing that I don't understand. I can't use OnInit on the filler component: ngOnInit() will never be triggered. That's why I'm using this horrible setTimeout().

Related

Dynamic routing list items

I have the following code
(tags component ts)
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Params } from '#angular/router';
#Component({
selector: 'app-tags',
templateUrl: './tags.component.html',
styleUrls: ['./tags.component.css']
})
export class TagsComponent implements OnInit{
Tags= [
'red',
'blue',
'purple'
];
red : boolean
blue : boolean
purple : boolean
constructor(private route:ActivatedRoute ) {}
ngOnInit(){
this.Tags= this.route.snapshot.params['name']
this.route.params
.subscribe((params: Params) => {
this.red = this.Tags.includes ('red');
this.purple = this.Tags.includes ('purple');
this.blue = this.Tags.includes ('blue');
}
);
}
}
(tag component html)
<ul>
<a><li *ngIf="red">red</li></a>
<a><li *ngIf="blue">blue</li></a>
<a><li *ngIf="purple">purple</li></a>
</ul>
(app module)
const appRoutes: Routes= [
{path:'', component: AppComponent},
{path:'tags/:name', component: TagsComponent}
];
Right now when I write the URL with the colour name in it only the colour mentioned would appear in the list item which is exactly what I want to do.
example
Now everything is hardcoded I want to be able to write any other colour that is not in my array like green and get the array to update and my list items to show accordingly.
I am quite new to this so I know my question might be a bit basic but any help is appreciated
Thank you
If in your example you want to format the url like that with a , separating the tag params then split on the , and use a ngFor loop in the html to render each tag.
Try this
export class TagsComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
public tags: string[];
public ngOnInit(): void {
this.setTags(this.route.snapshot.params);
this.route.params.subscribe((params) => this.setTags(params));
}
private setTags(params): void {
if (!params || !params['name']) { return; }
this.tags = params['name'].split(',');
}
}
<ul *ngIf="tags && tags.length">
<a *ngFor="let tag of tags">
<li>{{tag}}</li>
</a>
</ul>
You caon try something like this:
this.route.params.subscribe(params => {
console.log(params['name']);
});

Angular 2 eventEmitter dosen't work

I need to make something simple, I want to display a dialog when I click on an help icon.
I have a parent component:
#Component({
selector: 'app-quotation',
templateUrl: './quotation.component.html'
})
export class QuotationComponent implements OnInit {
public quotation: any = {};
public device: string;
public isDataAvailable = false;
#Output() showPopin: EventEmitter<string> = new EventEmitter<string>();
constructor(private quotationService: QuotationService,
private httpErrors: HttpErrorsService,
private popinService: PopinService) {}
moreInfo(content: string) {
console.log('here');
this.showPopin.emit('bla');
}
}
And his html:
<ul>
<li *ngFor="let item of quotation.HH_Summary_TariffPageDisplay[0].content">
<label></label>
<i class="quotation-popin" (click)="moreInfo()"></i>
<div class="separator"></div>
</li>
</ul>
My popin component:
#Component({
selector: 'app-popin',
templateUrl: './popin.component.html',
styleUrls: ['./popin.component.scss']
})
export class PopinComponent implements OnInit {
public popinTitle: string;
public popinContent: string;
public hidden: boolean = true;
constructor() { }
openPopin(event):void {
console.log("here");
this.hidden = false;
}
}
His HTML:
<div class="card-block popin-container" (showPopin)="openPopin($event)" *ngIf="!hidden">
<div class="card">
<div class="popin-title">
{{ popinTitle }}
<i class="icon icon-azf-cancel"></i>
</div>
<div class="popin-content">
{{ popinContent }}
</div>
</div>
</div>
My parent component is loaded in a router-outlet and my popin is loaded on the same level than the router-outlet, like this:
<app-nav-bar></app-nav-bar>
<app-breadcrumb></app-breadcrumb>
<div class="container">
<router-outlet></router-outlet>
</div>
<app-popin></app-popin>
My problem is the eventEmitter doesn't work and i don't know why, someone can explain me ?
thx,
regards
EventEmitters only work for direct Parent-Child component relationships. You do not have this relationship with the components you are describing here.
In a parent-child relatonship, we will see the child's component element within the parent's template. We do not see this in your example.
You have two options:
Refactor to use a parent-child relationship
Use a service for communication
If you go with option 2, the service should just contain an observable that one component calls next on, and the other component subscribes to.
#Injectable()
export class PopinService {
showPopin = new ReplaySubject<string>(1);
}
Inject this in QuotationComponent and modify moreInfo
moreInfo(content: string): void {
this.popinService.showPopin.next('bla' + content);
}
In PopinComponent inject the popinService and add the following:
ngOnInit() {
this.popinService.showPopin.subscribe(content => {
this.hidden = false;
this.popinContent = content;
});
}
It's because you misuse it.
In your popin component, you just call the function and do a log, and set a variable to false.
And nowhere I can see that you use the app-quotation selector, so you don't really use it, do you ?
Looks like you are sending an output to a child component (popin) . Ideally if you give output that means it should be from child to parent and from parent to child, it is Input.

Angular2, evaluate template from string inside a component

It's possible evaluate template from string in a variable?. I need place the string in the component instead of the expression,
e.g.
template: "<div>{{ template_string }}</div>"
template_string contains: <b>{{ name }}</b>
and all should be evaluated to <div><b>My Name</b></div>
but I see <div>{{ template_string }}</div>
I need something like {{ template_string | eval }} or something else to evaluate the content of the variable on current context.
It's possible? I need something to use this approach because template_string can be changed when the component is used.
Edit1:
Angular Version: 4.0.3
E.g.
#Component({
selector: 'product-item',
template: `
<div class="product">{{ template }}</div>`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `{{ name }} <b>{{ price | currency }}</b>`;
}
Usage:
<product-item [name]="product.name" [price]="product.price"></product-item>
Expected: Product Name USD3.00
Output: {{ name }} <b>{{ price | currency }}</b>
You can create your own directive that will do it:
compile.directive.ts
#Directive({
selector: '[compile]'
})
export class CompileDirective implements OnChanges {
#Input() compile: string;
#Input() compileContext: any;
compRef: ComponentRef<any>;
constructor(private vcRef: ViewContainerRef, private compiler: Compiler) {}
ngOnChanges() {
if(!this.compile) {
if(this.compRef) {
this.updateProperties();
return;
}
throw Error('You forgot to provide template');
}
this.vcRef.clear();
this.compRef = null;
const component = this.createDynamicComponent(this.compile);
const module = this.createDynamicModule(component);
this.compiler.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories: ModuleWithComponentFactories<any>) => {
let compFactory = moduleWithFactories.componentFactories.find(x => x.componentType === component);
this.compRef = this.vcRef.createComponent(compFactory);
this.updateProperties();
})
.catch(error => {
console.log(error);
});
}
updateProperties() {
for(var prop in this.compileContext) {
this.compRef.instance[prop] = this.compileContext[prop];
}
}
private createDynamicComponent (template:string) {
#Component({
selector: 'custom-dynamic-component',
template: template,
})
class CustomDynamicComponent {}
return CustomDynamicComponent;
}
private createDynamicModule (component: Type<any>) {
#NgModule({
// You might need other modules, providers, etc...
// Note that whatever components you want to be able
// to render dynamically must be known to this module
imports: [CommonModule],
declarations: [component]
})
class DynamicModule {}
return DynamicModule;
}
}
Usage:
#Component({
selector: 'product-item',
template: `
<div class="product">
<ng-container *compile="template; context: this"></ng-container>
</div>
`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `{{ name }} <b>{{ price | currency }}</b>`;
}
Plunker Example
See also
Angular 2.1.0 create child component on the fly, dynamically
not sure how you're building the template string
import { ..., OnInit } from '#angular/core';
#Component({
selector: 'product-item',
template: `
<div class="product" [innerHtml]='template_string'>
</div>`,
})
export class ProductItemComponent implements OnInit {
#Input() name: string;
#Input() price: number = 0;
#Input() pre: string;
#Input() mid: string;
#Input() post: string;
template_string;
ngOnInit() {
// this is probably what you want
this.template_string = `${this.pre}${this.name}${this.mid}${this.price}${this.post}`
}
}
<product-item [name]="name" [price]="price" pre="<em>" mid="</em><b>" post="</b>"></product-item>
the string can be built from outside the component, would still recommend something like ngIf to control dynamic templates though.
In Angular double curly braces {{}} are used to evaluation an expression in a component's template. and not work on random strings or dynamically added DOM elements. So one way of doing this is to use typescript string interpolation using ${}. check the rest of code to understand
#Component({
selector: 'product-item',
template: `
<div class="product" [innerHTML]="template"></div>`,
})
export class ProductItemComponent {
#Input() name: string;
#Input() price: number = 0;
#Input() template: string = `${ this.name } <b>${ this.price }}</b>`;
}

angular 4+ assign #Input for ngComponentOutlet dynamically created component

In Angular 4 to dynamically create a component you can use ngComponentOutlet directive: https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html
something like this:
Dynamic component
#Component({
selector: 'dynamic-component',
template: `
Dynamic component
`
})
export class DynamicComponent {
#Input() info: any;
}
App
#Component({
selector: 'my-app',
template: `
App<br>
<ng-container *ngComponentOutlet="component"></ng-container>
`
})
export class AppComponent {
this.component=DynamicComponent;
}
How do I pass #Input() info: any; information in this template <ng-container *ngComponentOutlet="component"></ng-container> ?
Such a feature was discussed in the pull request for ngComponentOutlet but was dropped for now.
Even the componentRef shown currently in https://angular.io/docs/ts/latest/api/common/index/NgComponentOutlet-directive.html is not public and therefore not available https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L78
I'd suggest you create your own directive derived from https://github.com/angular/angular/blob/3ef73c2b1945340ca6bd21f1790260c88698ae26/modules/%40angular/common/src/directives/ng_component_outlet.ts#L72
and assign values to inputs like shown in Angular 2 dynamic tabs with user-click chosen components
this.compRef.instance.someProperty = 'someValue';
With the help of the post of #Günter Zöchbauer I solved a similar problem this way - I hope you can adapt it somehow.
First I defined some interfaces:
// all dynamically loaded components should implement this guy
export interface IDynamicComponent { Context: object; }
// data from parent to dynLoadedComponent
export interface IDynamicComponentData {
component: any;
context?: object;
caller?: any;
}
then I implemented them inside of the dynamically loaded component
dynamicLoadedComponentA.ts
// ...
export class DynamicLoadedComponentA implements IDynamicComponent {
// ...
// data from parent
public Context: object;
// ...
After that I built a new component which is responsible for the magic. Important here is that I had to register all dyn. loaded components as entryComponents.
dynamic.component.ts
#Component({
selector: 'ngc-dynamic-component',
template: ´<ng-template #dynamicContainer></ng-template>´,
entryComponents: [ DynamicLoadedComponentA ]
})
export class DynamicComponent implements OnInit, OnDestroy, OnChanges {
#ViewChild('dynamicContainer', { read: ViewContainerRef }) public dynamicContainer: ViewContainerRef;
#Input() public componentData: IDynamicComponentData;
private componentRef: ComponentRef<any>;
private componentInstance: IDynamicComponent;
constructor(private resolver: ComponentFactoryResolver) { }
public ngOnInit() {
this.createComponent();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['componentData']) {
this.createComponent();
}
}
public ngOnDestroy() {
if (this.componentInstance) {
this.componentInstance = null;
}
if (this.componentRef) {
this.componentRef.destroy();
}
}
private createComponent() {
this.dynamicContainer.clear();
if (this.componentData && this.componentData.component) {
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(this.componentData.component);
this.componentRef = this.dynamicContainer.createComponent(factory);
this.componentInstance = this.componentRef.instance as IDynamicComponent;
// fill context data
Object.assign(this.componentInstance.Context, this.componentData.context || {});
// register output events
// this.componentRef.instance.outputTrigger.subscribe(event => console.log(event));
}
}
}
here the usage of this shiny new stuff:
app.html
<!-- [...] -->
<div>
<ngc-dynamic-component [componentData]="_settingsData"></ngc-dynamic-component>
</div>
<!-- [...] -->
app.ts
// ...
private _settingsData: IDynamicComponent = {
component: DynamicLoadedComponentA,
context: { SomeValue: 42 },
caller: this
};
// ...
I think for now you can use
https://www.npmjs.com/package/ng-dynamic-component
It is made specifically for this issue

Getting dependency from Injector manually inside a directive

I am trying to create a generic directive which will take a class type for rule validation and according to the rule in the class the directive will either show or hide an element.
This is my attempt so far.
PLUNKER Demo
myIf-Directive.ts
#Directive({
selector: '[myIf]'
})
export class MyIfDirective {
constructor(private _viewContainer: ViewContainerRef,
private _template: TemplateRef<Object>)
{ }
#Input() set myIf(rule: string) {
//rule class type will come as string
//how can I use string token to get dependency from injector?
//currently harcoded
//will the injector create new instance or pass on instance from parent?
let injector = ReflectiveInjector.resolveAndCreate([AdminOnly]);
let adminOnly : IRule = injector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
show ? this.showItem() : this.hideItem();
}
private showItem() {
this._viewContainer.createEmbeddedView(this._template);
}
private hideItem() {
this._viewContainer.clear();
}
}
app-component.ts
#Component({
selector: 'my-app',
template: `
<div *myIf="'AdminOnly'">
<h2>Hello {{name}}</h2>
</div>
`,
})
export class App {
name:string;
constructor() {
this.name = 'Angular2'
}
}
But I am stuck in 2 places:
I keep getting the error No Provider for AuthService
I do not know how I can get the dependency from Injector using class name as string rather than the type
Any suggestion whether this is the right way to do it or where I am going wrong is highly appreciated.
You need to pass the parent injector like
export class MyIfDirective {
constructor(private injector:Injector, private _viewContainer: ViewContainerRef,
private _template: TemplateRef<Object>)
{ }
#Input() set myIf(rule: string) {
let resolvedProviders = ReflectiveInjector.resolve([AdminOnly]);
let childInjector = ReflectiveInjector.fromResolvedProviders(resolvedProviders, this.injector);
let adminOnly : IRule = childInjector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
show ? this.showItem() : this.hideItem();
}
private showItem() {
this._viewContainer.createEmbeddedView(this._template);
}
private hideItem() {
this._viewContainer.clear();
}
}
See also Inject service with ReflectiveInjector without specifying all classes in the dependency tree
Just update for Angular version 10+:
From your service:
#Injectable({
providedIn: 'any'
})
export class AdminOnly { ... }
In your directive or a pure function, ...:
import { Injector } from '#angular/core';
...
const injector: Injector = Injector.create({
providers: [{provide: AdminOnly, deps: []}]
});
const adminOnly: AdminOnly = injector.get(AdminOnly);
let show = adminOnly.shouldShowElement();
...
See more

Categories

Resources