ng-select not updating in Angular 2 - javascript

hello i am new in angular 2
i can make formGroup in add in ng-select controll and predefine value added.
that is perfectly.
but when button click then new value push in ng-select but ng-select not updating .
here my plunker
https://plnkr.co/edit/Hwfk1T2stkiRcLTxuFmz
//our root app component
import {Component, OnInit, NgModule, ViewChild} from '#angular/core';
import {BrowserModule} from '#angular/platform-browser';
import {FormControl, FormGroup, ReactiveFormsModule} from '#angular/forms';
import {SelectModule} from 'ng-select';
#Component({
selector: 'my-app',
template: `
<h1>ng-select demo app</h1>
<form style="padding:18px;max-width:800px;"
[formGroup]="form">
<div style="margin:5px 0;font-weight:600;">Single select example</div>
<ng-select
[options]="options0"
[multiple]="false"
placeholder="Select one"
formControlName="selectSingle"
>
</ng-select>
<button (click)="pushValue()">Click</button>
<div>Events:</div>
<pre #preSingle>{{logSingleString}}</pre>
</form>`
})
export class App implements OnInit {
form: FormGroup;
multiple0: boolean = false;
options0: any[] = [];
selection: Array<string>;
#ViewChild('preSingle') preSingle;
logSingleString: string = '';
constructor() {
this.options0.push({"label":'test',"value":'Test'});
console.log("Object:::"+JSON.stringify(this.options0));
}
ngOnInit() {
this.form = new FormGroup({});
this.form.addControl('selectSingle', new FormControl(''));
console.log("Object:::"+JSON.stringify(this.options0));
}
pushValue()
{
console.log("pushValue call.");
this.options0.push({"label":"test","value":"Test"});
console.log("Object:::"+JSON.stringify(this.options0));
}
}
#NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
SelectModule
],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
where is wrong ???

you can use Array.slice() to update to array instance in order to let angular detect the change of array.
this.options0 = this.options0.slice();

Looking at ng-select source code i noticed
ngOnChanges(changes: any) {
if (changes.hasOwnProperty('options')) {
this.updateOptionsList(changes['options'].isFirstChange());
}
so in order to update options list you should fire ngOnChanges. It can be done by creating new reference to options0
this.options0 = this.options0.concat({"label":"test","value":"Test"});
or
this.options0 = [...this.options0, {"label":"test","value":"Test"}];
Modified Plunker

Change Detection
Ng-select component implements OnPush change detection which means the dirty checking checks for immutable data types. That means if you do object mutations like:
this.items.push({id: 1, name: 'New item'})
Component will not detect a change. Instead you need to do:
this.items = [...this.items, {id: 1, name: 'New item'}];
This will cause the component to detect the change and update. Some might have concerns that this is a pricey operation, however, it is much more performant than running ngDoCheck and constantly diffing the array.

Related

Angular 12 - display data in an Array using an Observable

Hello Fellow Community,
I am experiencing a problem currently on my angular 12 project :
I would like to display an array named todos which contains all of my object variables. However even tho I can see on the console that my objects are being added to the array : todos, there seems to be a problem (with my observable maybe) because they are not displayed on the html page.
I have 2 components "add-todo" and "todos" as well as 1 service "todoService".
Here is my code :
//todo.service.ts :
import { Injectable } from '#angular/core';
import {Todo} from '../models/todo.model';
import {Subject} from "rxjs"
#Injectable({
providedIn: 'root'
})
export class TodoService {
todos:Todo[]=[];
todoSubject=new Subject <Todo[]>()
constructor() {
setTimeout(()=>{
this.todos=[
{
firstname:"Youssef"
},
{
firstname:"Yacine"
},
{
firstname:"Ismail"
},
];
this.emitTodo();
},3000)
}
addTodo(todo:Todo):void{
this.todos.push(todo);
this.emitTodo();
console.log("Dans todoservice :"+this.todos);
}
emitTodo():void{
this.todoSubject.next(this.todos);
}
}
//todos.component.ts :
import { Component,OnInit,OnDestroy } from '#angular/core';
import {TodoService} from '../services/todo.service';
import {Todo} from '../models/todo.model';
import {Subscription} from 'rxjs'
#Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit, OnDestroy {
todos;
todoSub;
constructor(private todoService:TodoService){}
ngOnInit():void{
this.todoSub=this.todoService.todoSubject.subscribe(
(value:Todo[])=>{
this.todos=value;
console.log("Dans todo value :"+value);
},
(error)=>{
console.log("Erreur "+error)
},
()=>{
console.log("Observable complete");
}
)
}
ngOnDestroy():void{
this.todoSub.unsubscribe();
}
}
//todos.component.html :
<div class="form-group" *ngFor="let todo of todos;">
{{todo|json}}
</div>
<p>todo works!</p>
//and finally,
//add-todo.component.ts :
import { Component, OnInit } from '#angular/core';
import { Todo } from '../models/todo.model';
import {TodoService} from '../services/todo.service';
import {Router} from '#angular/router';
#Component({
selector: 'app-app-todo',
templateUrl: './app-todo.component.html',
styleUrls: ['./app-todo.component.css']
})
export class AppTodoComponent implements OnInit {
todo=new Todo();
constructor(private todoService:TodoService, private router:Router) { }
ngOnInit(): void {
}
onSubmit():void{
this.todoService.addTodo(this.todo);
console.log("Dans add-todo :"+this.todo);
this.router.navigate(["todos"]);
}
}
add-todo.component.html :
<p>{{todo|json}}</p>
<form #addTodoForm="ngForm" (ngSubmit)="onSubmit()">
<label for="name">Firstname :</label>
<input type="text" name="firstname" [(ngModel)]="todo.firstname" required>
<button type="submit" class="btn btn-success" [disabled]="addTodoForm.invalid">Add Todo</button>
</form>```
This is what it shows when I "ng serve" the code :
[![todos.component.html][1]][1]
Then I add a new todo :
[![add-todo.component.html][2]][2]
Then it redirects to the todos.component.html again but it doesn't display all the todos like before :
[![todos.component.html][3]][3]
[1]: https://i.stack.imgur.com/5P1NQ.png
[2]: https://i.stack.imgur.com/diIDX.png
[3]: https://i.stack.imgur.com/PgKxE.png
I think you need to alter arrays and objects in Angular (or any library or framework) immutably for change detection to take place.
Objects and arrays are reference types so they store the location of their memory and doing a push on them does not change the location of memory for them.
Every time you want to change arrays and objects in Angular, do them immutably (assign new values so the location in memory changes and change detection takes place). The maps might not be needed but I have added them so the objects inside of the array have new locations in memory as well.
addTodo(todo:Todo):void{
// this.todos.push(todo);
this.todos = [...this.todos.map(todo => ({ ...todo })), todo];
this.emitTodo();
console.log("Dans todoservice :"+this.todos);
}
emitTodo():void{
this.todoSubject.next([...this.todos.map(todo => ({ ...todo })]);
}
you are storing todos:Todo in the service class. And then try to access todos in your component by injecting that service into your components.
constructor(private todoService:TodoService){}
It's not going to work that way. Please get some idea about how dependency injection works.
Possible workaround,
you can store data in local storage or session storage to access them in different components
you can use Ngrx like data storing technology (recommended large-scale applications)
store data in a local file (Not recommended for large-scale applications)

How to make child component detects object (#Input()) from parent component has changed in Angular

I have an object from parent component also received in a child component similar to this:
{
attribute: 'aaaa',
attribute2: [
{
value
},
{
value
},
{
value
},
]
}
This object is an #Input from a parent component. When I make changes to the objects inside the attribute2 array, I would like the child component detect that changes were made and then gets updated. As this is an object, I could'nt make it work, so I clone the entire object (this.objet = _.cloneDeep(this.object) in the parent component so then the child component detects that changes happened.
Is there any other way of doing this that does not clone the entire object? Thanks in advance
EDIT:
Child Component
export class ChildComponent implements OnInit, OnChanges {
#Input() public object: any;
}
html
<div>
<span>{{object.attribute}}</span>
<div *ngFor="let items of object.attribute2">{{item.value}}</div>
</div>
Parent Component
export class ParentComponent implements OnInit {
public object: any;
updateObject() {
this.object.attribute2[1] = 'Changed value';
this.object = _.cloneDeep(this.object);
}
}
html
<div>
<child-component [object]="object"></child-component>
</div>
An efficient way is to use EventEmitter and service communication to
trigger changes in the child component.
On way as mentioned by #Tony is to use ngOnChanges(). It is a good shortcut for detecting bounded properties change but as you add more and more bindings, using this hook will affect you application in the long run because it will run every time any of the bound property changes whether or not you desire it all the calls.
So for Service based communication, I've created an example on
Stackblitz:
https://stackblitz.com/edit/angular-fgut7t
Gist: https://gist.github.com/stupidly-logical/a34e272156b498513505127967aec851
In this example, I am binding an Array to the child component using #Input() an on addition of new data, the array is updated by the parent and the latest value is passed on the service which then emits this value. The child component subscribes to this value and the relevant code is executed.
The Service:
import { Injectable, EventEmitter } from '#angular/core';
#Injectable({
providedIn: "root"
})
export class DataService {
dataUpdated:EventEmitter<any> = new EventEmitter();
constructor() { }
setLatestData(data) {
this.dataUpdated.emit(data);
}
}
Child Component TS
import { Component, OnInit, Input } from '#angular/core';
import { DataService } from '../data-service.service';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
#Input() allData: [];
latestData: any;
constructor(private dataService: DataService) { }
ngOnInit() {
this.dataService.dataUpdated.subscribe((data) => {
this.latestData = data;
});
}
}
Child Component HTML
<p>
Latest Data: {{ latestData }}
</p>
<h3>List:</h3>
<li *ngFor="let data of allData">
{{ data }}
</li>
Parent Component TS
import { Component } from '#angular/core';
import { DataService } from './data-service.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
dataArr = [];
constructor(private dataService: DataService){}
onAddTimestamp() {
let timestamp = new Date();
this.dataArr.push(timestamp);
this.dataService.setLatestData(timestamp);
}
}
Parent Component HTML
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<button
(click)="onAddTimestamp()"
>
Add Timestamp
</button>
<app-child
[allData] = "dataArr"
></app-child>
Use the ngOnChanges() lifecycle method in your component.
ngOnChanges is called right after the data-bound properties have been
checked and before view and content children are checked if at least
one of them has changed.
Some like this
#Input() object: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes.object.currentValue);
// You can also use object.previousValue and
// object.firstChange for comparing old and new values
}

Angular2, toggle a checked checkbox list

Is there a way to toggle a checked checkbox list in Angular2?
I have a button that when pressed and the full list is in view, it will show only the checked items in the list. When the button is pressed again, it will show the entire list.
Plunkr: http://plnkr.co/edit/jZz4XoHjYJ40bjt2eOU5?p=preview
//our root app component
import {Component, NgModule} from '#angular/core'
import {BrowserModule} from '#angular/platform-browser'
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}}</h2>
</div>
<li *ngFor="let col of data" class="form-group">
<input type="checkbox" name="col" value="{{col.value}}" [(ngModel)]="col.value" (change)="addColumns(col)" />{{col.name}}
</li>
`,
})
export class App {
name:string;
data:any[]=[{"id":"13","name":"AAA"},{"id":"15","name":"BBB"},{"id":"20","name":"CCC"}]
constructor() {
this.name = 'Angular2'
}
get selectedcheckboxes() {
return this.data
.filter(opt => opt.value)
}
addColumns(col){
this.selectedcheckboxes;
console.log(this.selectedcheckboxes)
}
}
#NgModule({
imports: [ BrowserModule,FormsModule ],
declarations: [ App ],
bootstrap: [ App ]
})
export class AppModule {}
HTML:
<body>
<my-app>
loading...
</my-app>
<button class="check">Collapse/Expand</button>
</body>
In Angular1, it looks like this: http://jsfiddle.net/jzhang172/of4yy8k9/ I'm looking to do the same thing in Angular2, but can't understand the syntax.
You can put the main array in other variable and then just change the data variable according your clicked button (to expand or to collapse), you may need one variable to define if it's full list or the selected list
something like:
isFullList: boolean;
mainData: Array<any> = [your main data here];
data: Array<any> = [data to use in list]; //should initied by mandata
toggle() {
//this.isFullList: boolean
if (!this.isFullList) {
this.data = [...this.mainData];
} else {
this.data = [...this.selectedcheckboxes];
}
console.log(this.data)
this.isFullList = ! this.isFullList
}
plunker: http://plnkr.co/edit/V1iiX87gYVMUtIkmpMfT?p=preview
You can implement an filter at your component, and invoke the filter at your template.
In the filter, just add a flag to control to filter or show original list, and toggle the flag by click the button.
Invoke filter at template
*ngFor="let col of getData()"
Filter data in component
getData() {
return this.filter ? this.data.filter(item => item.value === true) : this.data;
}
Plunker Demo

Angular 2 Material Design Autocomplete search box

I got some issues on autocomplete on Angular2 Material Design which these are the things happened:
*When I type a character that is associated with the one I search it won't display on the autocomplete the specific character that I entered on the search textbox as shown in the picture below:
When before typing on the search box.
When after typing on the search box
The second one is that when I select a specific list on the list of users it seems that it will display the [object Object] thingy and I don't know why it happens. See the picture below:
When before selecting a employee on the list
When after selecting a employee on the list
Here is my code below see if there's something that I missed or what.
Angular Code: new-useraccount-components.ts
import { Component } from '#angular/core';
import { WebServiceComponents } from '../WebService/web.service';
import { FormControl } from '#angular/forms';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/map';
#Component({
selector: 'new-user-account',
template: `
<md-card class="card-margin">
<md-card-content>
<md-input-container>
<input mdInput placeholder="Select Employee" [mdAutocomplete]="auto" [formControl]="UserAccountCtrl" /><br />
</md-input-container>
<md-autocomplete #auto="mdAutocomplete">
<md-option *ngFor="let userAccount of filterUserAccount | async" [value]="userAccount">
{{userAccount.username}}
</md-option>
</md-autocomplete>
<md-input-container>
<input mdInput placeholder="Username" /><br />
</md-input-container>
</md-card-content>
</md-card>
`
})
export class NewUserAccountComponent{
UserAccountCtrl: FormControl;
filterUserAccount: any;
async ngOnInit(){
var response = await this.webService.getUserAccounts();
this.userAccounts = response.json();
}
userAccounts = [];
constructor(private webService : WebServiceComponents){
this.UserAccountCtrl = new FormControl();
this.filterUserAccount = this.UserAccountCtrl.valueChanges
.startWith(null)
.map(name => this.filteredUserAccount(name));
}
filteredUserAccount(val: string) {
return val ? this.userAccounts.filter(s => new RegExp(`^${val}`, 'gi').test(s))
: this.userAccounts;
}
}
AppModule: app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { MaterialModule } from '#angular/material';
import { AppComponent } from './app.component';
import { WebServiceComponents } from './WebService/web.service';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { UserAccountComponent } from './UserAccount/useraccount-components';
import { NewUserAccountComponent } from './UserAccount/new-useraccount-component';
#NgModule({
imports: [ BrowserModule, MaterialModule, ReactiveFormsModule, FormsModule ],
declarations: [ AppComponent, UserAccountComponent, NewUserAccountComponent ],
bootstrap: [ AppComponent ],
providers: [ WebServiceComponents ]
})
export class AppModule { }
Sorry I'm just a newbie trying to play with this new framework. Hopefully someone will guide me on how to fix this issue. Thank you and Have a wonderful day ahead!
The docs are quite clear if you read them closely enough:
https://material.angular.io/components/autocomplete/overview
You'll be especially interested in what it has to say under the title "Setting separate control and display values". It is not displaying your search result because you want to display something different than the [value] you've specified.
The other problem you are having is that when selecting an item in the list the actual value of that item is inserted into the search box. Since this is a userAccount and not the username, [object Object] is displayed.
Simply changing to [value]="userAccount.username" could solve both your problems. If this does not give you the desired effect, you'll have to use the [displayWith] attribute as explained in the docs.

In angular2, are there any methods just like $compile()? [duplicate]

I want to manually compile some HTML containing directives. What is the equivalent of $compile in Angular 2?
For example, in Angular 1, I could dynamically compile a fragment of HTML and append it to the DOM:
var e = angular.element('<div directive></div>');
element.append(e);
$compile(e)($scope);
Angular 2.3.0 (2016-12-07)
To get all the details check:
How can I use/create dynamic template to compile dynamic Component with Angular 2.0?
To see that in action:
observe a working plunker (working with 2.3.0+)
The principals:
1) Create Template
2) Create Component
3) Create Module
4) Compile Module
5) Create (and cache) ComponentFactory
6) use Target to create an Instance of it
A quick overview how to create a Component
createNewComponent (tmpl:string) {
#Component({
selector: 'dynamic-component',
template: tmpl,
})
class CustomDynamicComponent implements IHaveDynamicData {
#Input() public entity: any;
};
// a component for this particular template
return CustomDynamicComponent;
}
A way how to inject component into NgModule
createComponentModule (componentType: any) {
#NgModule({
imports: [
PartsModule, // there are 'text-editor', 'string-editor'...
],
declarations: [
componentType
],
})
class RuntimeComponentModule
{
}
// a module for just this Type
return RuntimeComponentModule;
}
A code snippet how to create a ComponentFactory (and cache it)
public createComponentFactory(template: string)
: Promise<ComponentFactory<IHaveDynamicData>> {
let factory = this._cacheOfFactories[template];
if (factory) {
console.log("Module and Type are returned from cache")
return new Promise((resolve) => {
resolve(factory);
});
}
// unknown template ... let's create a Type for it
let type = this.createNewComponent(template);
let module = this.createComponentModule(type);
return new Promise((resolve) => {
this.compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) =>
{
factory = _.find(moduleWithFactories.componentFactories
, { componentType: type });
this._cacheOfFactories[template] = factory;
resolve(factory);
});
});
}
A code snippet how to use the above result
// here we get Factory (just compiled or from cache)
this.typeBuilder
.createComponentFactory(template)
.then((factory: ComponentFactory<IHaveDynamicData>) =>
{
// Target will instantiate and inject component (we'll keep reference to it)
this.componentRef = this
.dynamicComponentTarget
.createComponent(factory);
// let's inject #Inputs to component instance
let component = this.componentRef.instance;
component.entity = this.entity;
//...
});
The full description with all the details read here, or observe working example
.
.
OBSOLETE - Angular 2.0 RC5 related (RC5 only)
to see previous solutions for previous RC versions, please, search through the history of this post
Note: As #BennyBottema mentions in a comment, DynamicComponentLoader is now deprecated, hence so is this answer.
Angular2 doesn't have any $compile equivalent. You can use DynamicComoponentLoader and hack with ES6 classes to compile your code dynamically (see this plunk):
import {Component, DynamicComponentLoader, ElementRef, OnInit} from 'angular2/core'
function compileToComponent(template, directives) {
#Component({
selector: 'fake',
template , directives
})
class FakeComponent {};
return FakeComponent;
}
#Component({
selector: 'hello',
template: '<h1>Hello, Angular!</h1>'
})
class Hello {}
#Component({
selector: 'my-app',
template: '<div #container></div>',
})
export class App implements OnInit {
constructor(
private loader: DynamicComponentLoader,
private elementRef: ElementRef,
) {}
ngOnInit() {} {
const someDynamicHtml = `<hello></hello><h2>${Date.now()}</h2>`;
this.loader.loadIntoLocation(
compileToComponent(someDynamicHtml, [Hello])
this.elementRef,
'container'
);
}
}
But it will work only until html parser is inside angular2 core.
Angular Version I have Used - Angular 4.2.0
Angular 4 is came up with ComponentFactoryResolver to load components at runtime. This is a kind of same implementation of $compile in Angular 1.0 which serves your need
In this below example I am loading ImageWidget component dynamically in to a DashboardTileComponent
In order to load a component you need a directive that you can apply to ng-template which will helps to place the dynamic component
WidgetHostDirective
import { Directive, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[widget-host]',
})
export class DashboardTileWidgetHostDirective {
constructor(public viewContainerRef: ViewContainerRef) {
}
}
this directive injects ViewContainerRef to gain access to the view container of the element that will host the dynamically added component.
DashboardTileComponent(Place holder component to render the dynamic component)
This component accepts an input which is coming from a parent components or you can load from your service based on your implementation. This component is doing the major role to resolve the components at runtime. In this method you can also see a method named renderComponent() which ultimately loads the component name from a service and resolve with ComponentFactoryResolver and finally setting data to the dynamic component.
import { Component, Input, OnInit, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '#angular/core';
import { DashboardTileWidgetHostDirective } from './DashbardWidgetHost.Directive';
import { TileModel } from './Tile.Model';
import { WidgetComponentService } from "./WidgetComponent.Service";
#Component({
selector: 'dashboard-tile',
templateUrl: 'app/tile/DashboardTile.Template.html'
})
export class DashboardTileComponent implements OnInit {
#Input() tile: any;
#ViewChild(DashboardTileWidgetHostDirective) widgetHost: DashboardTileWidgetHostDirective;
constructor(private _componentFactoryResolver: ComponentFactoryResolver,private widgetComponentService:WidgetComponentService) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.renderComponents();
}
renderComponents() {
let component=this.widgetComponentService.getComponent(this.tile.componentName);
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
let viewContainerRef = this.widgetHost.viewContainerRef;
let componentRef = viewContainerRef.createComponent(componentFactory);
(<TileModel>componentRef.instance).data = this.tile;
}
}
DashboardTileComponent.html
<div class="col-md-2 col-lg-2 col-sm-2 col-default-margin col-default">
<ng-template widget-host></ng-template>
</div>
WidgetComponentService
This is a service factory to register all the components that you want to resolve dynamically
import { Injectable } from '#angular/core';
import { ImageTextWidgetComponent } from "../templates/ImageTextWidget.Component";
#Injectable()
export class WidgetComponentService {
getComponent(componentName:string) {
if(componentName==="ImageTextWidgetComponent"){
return ImageTextWidgetComponent
}
}
}
ImageTextWidgetComponent(component we are loading at runtime)
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'dashboard-imagetextwidget',
templateUrl: 'app/templates/ImageTextWidget.html'
})
export class ImageTextWidgetComponent implements OnInit {
#Input() data: any;
constructor() { }
ngOnInit() { }
}
Add Finally add this ImageTextWidgetComponent in to your app module as entryComponent
#NgModule({
imports: [BrowserModule],
providers: [WidgetComponentService],
declarations: [
MainApplicationComponent,
DashboardHostComponent,
DashboardGroupComponent,
DashboardTileComponent,
DashboardTileWidgetHostDirective,
ImageTextWidgetComponent
],
exports: [],
entryComponents: [ImageTextWidgetComponent],
bootstrap: [MainApplicationComponent]
})
export class DashboardModule {
constructor() {
}
}
TileModel
export interface TileModel {
data: any;
}
Orginal Reference from my blog
Official Documentation
Download Sample Source Code
this npm package made it easier for me:
https://www.npmjs.com/package/ngx-dynamic-template
usage:
<ng-template dynamic-template
[template]="'some value:{{param1}}, and some component <lazy-component></lazy-component>'"
[context]="{param1:'value1'}"
[extraModules]="[someDynamicModule]"></ng-template>
In order to dinamically create an instance of a component and attach it to your DOM you can use the following script and should work in Angular RC:
html template:
<div>
<div id="container"></div>
<button (click)="viewMeteo()">Meteo</button>
<button (click)="viewStats()">Stats</button>
</div>
Loader component
import { Component, DynamicComponentLoader, ElementRef, Injector } from '#angular/core';
import { WidgetMeteoComponent } from './widget-meteo';
import { WidgetStatComponent } from './widget-stat';
#Component({
moduleId: module.id,
selector: 'widget-loader',
templateUrl: 'widget-loader.html',
})
export class WidgetLoaderComponent {
constructor( elementRef: ElementRef,
public dcl:DynamicComponentLoader,
public injector: Injector) { }
viewMeteo() {
this.dcl.loadAsRoot(WidgetMeteoComponent, '#container', this.injector);
}
viewStats() {
this.dcl.loadAsRoot(WidgetStatComponent, '#container', this.injector);
}
}
Angular TypeScript/ES6 (Angular 2+)
Works with AOT + JIT at once together.
I created how to use it here:
https://github.com/patrikx3/angular-compile
npm install p3x-angular-compile
Component: Should have a context and some html data...
Html:
<div [p3x-compile]="data" [p3x-compile-context]="ctx">loading ...</div>
You can see the component, that allow to compile simple dynamic Angular components https://www.npmjs.com/package/#codehint-ng/html-compiler
I know this issue is old, but I spent weeks trying to figure out how to make this work with AOT enabled. I was able to compile an object but never able to execute existing components. Well I finally decided to change tact, as I was't looking to compile code so much as execute a custom template. My thought was to add the html which anyone can do and loop though the existing factories. In doing so I can search for the element/attribute/etc. names and execute the component on that HTMLElement. I was able to get it working and figured I should share this to save someone else the immense amount of time I wasted on it.
#Component({
selector: "compile",
template: "",
inputs: ["html"]
})
export class CompileHtmlComponent implements OnDestroy {
constructor(
private content: ViewContainerRef,
private injector: Injector,
private ngModRef: NgModuleRef<any>
) { }
ngOnDestroy() {
this.DestroyComponents();
}
private _ComponentRefCollection: any[] = null;
private _Html: string;
get Html(): string {
return this._Html;
}
#Input("html") set Html(val: string) {
// recompile when the html value is set
this._Html = (val || "") + "";
this.TemplateHTMLCompile(this._Html);
}
private DestroyComponents() { // we need to remove the components we compiled
if (this._ComponentRefCollection) {
this._ComponentRefCollection.forEach((c) => {
c.destroy();
});
}
this._ComponentRefCollection = new Array();
}
private TemplateHTMLCompile(html) {
this.DestroyComponents();
this.content.element.nativeElement.innerHTML = html;
var ref = this.content.element.nativeElement;
var factories = (this.ngModRef.componentFactoryResolver as any)._factories;
// here we loop though the factories, find the element based on the selector
factories.forEach((comp: ComponentFactory<unknown>) => {
var list = ref.querySelectorAll(comp.selector);
list.forEach((item) => {
var parent = item.parentNode;
var next = item.nextSibling;
var ngContentNodes: any[][] = new Array(); // this is for the viewchild/viewchildren of this object
comp.ngContentSelectors.forEach((sel) => {
var ngContentList: any[] = new Array();
if (sel == "*") // all children;
{
item.childNodes.forEach((c) => {
ngContentList.push(c);
});
}
else {
var selList = item.querySelectorAll(sel);
selList.forEach((l) => {
ngContentList.push(l);
});
}
ngContentNodes.push(ngContentList);
});
// here is where we compile the factory based on the node we have
let component = comp.create(this.injector, ngContentNodes, item, this.ngModRef);
this._ComponentRefCollection.push(component); // save for our destroy call
// we need to move the newly compiled element, as it was appended to this components html
if (next) parent.insertBefore(component.location.nativeElement, next);
else parent.appendChild(component.location.nativeElement);
component.hostView.detectChanges(); // tell the component to detectchanges
});
});
}
}
If you want to inject html code use directive
<div [innerHtml]="htmlVar"></div>
If you want to load whole component in some place, use DynamicComponentLoader:
https://angular.io/docs/ts/latest/api/core/DynamicComponentLoader-class.html

Categories

Resources