Angular 2.1.0 Create Child Component on the Fly, Dynamically

Angular 2.1.0 create child component on the fly, dynamically

You can use the following HtmlOutlet directive:

import {
Component,
Directive,
NgModule,
Input,
ViewContainerRef,
Compiler,
ComponentFactory,
ModuleWithComponentFactories,
ComponentRef,
ReflectiveInjector
} from '@angular/core';

import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);

@NgModule({ imports: [CommonModule, RouterModule], declarations: [decoratedCmp] })
class DynamicHtmlModule { }

return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}

@Directive({ selector: 'html-outlet' })
export class HtmlOutlet {
@Input() html: string;
cmpRef: ComponentRef<any>;

constructor(private vcRef: ViewContainerRef, private compiler: Compiler) { }

ngOnChanges() {
const html = this.html;
if (!html) return;

if(this.cmpRef) {
this.cmpRef.destroy();
}

const compMetadata = new Component({
selector: 'dynamic-html',
template: this.html,
});

createComponentFactory(this.compiler, compMetadata)
.then(factory => {
const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
this.cmpRef = this.vcRef.createComponent(factory, 0, injector, []);
});
}

ngOnDestroy() {
if(this.cmpRef) {
this.cmpRef.destroy();
}
}
}

See also Plunker Example

Example with custom component

For AOT compilation see these threads

  • https://github.com/angular/angular/issues/15510
  • http://blog.assaf.co/angular-2-harmony-aot-compilation-with-lazy-jit-2/

See also github Webpack AOT example https://github.com/alexzuza/angular2-build-examples/tree/master/ngc-webpack

How to render a dynamic template with components in Angular2

Problem solved Thanks to Yurzui,

https://plnkr.co/edit/TAbupH4si62x10QZ7xuc?p=preview

The generic HTML outlete from a different post (Angular 2.1.0 create child component on the fly, dynamically) can be used to render templates with custom directives in them.

Don't forget to import a module with all the custom components that you want to render!

export function createComponentFactory(compiler: Compiler, metadata: Component): Promise<ComponentFactory<any>> {
const cmpClass = class DynamicComponent {};
const decoratedCmp = Component(metadata)(cmpClass);

// Import the module with required components here
@NgModule({ imports: [CommonModule, RouterModule, SharedModule], declarations: [decoratedCmp] })
class DynamicHtmlModule { }

return compiler.compileModuleAndAllComponentsAsync(DynamicHtmlModule)
.then((moduleWithComponentFactory: ModuleWithComponentFactories<any>) => {
return moduleWithComponentFactory.componentFactories.find(x => x.componentType === decoratedCmp);
});
}

How to update a child component in Angular when a function returns a new value?

Best to use Angular BehaviorSubject Service and get the components to subscribe to the change event. I have created a simple Pokemon Api that has exactly what you need. It is very easy to see what is happening in the source code.

The service is as follows

@Injectable()
export class PokemonFilterService {
private pokemonfilterStatus = new PokemonFilter();
private pokemonFilterSource = new BehaviorSubject<PokemonFilter>(this.pokemonfilterStatus);

// this updates the data from the component
change(data: PokemonFilter) : void {
this.pokemonFilterSource.next(data);
}

// this lets the subscribing component know the data has changed
public filterDataHasChanged(): Observable<PokemonFilter> {
return this.pokemonFilterSource.asObservable();
}
}

The pokemon-header page has this code

@Component({
selector: 'app-pokemon-header',
templateUrl: './pokemon-header.component.html',
styleUrls: ['./pokemon-header.component.scss']
})
export class PokemonHeaderComponent implements OnInit {
typeList: Array<PokeTypes>;
pokemonFilter: PokemonFilter;

constructor(private route: ActivatedRoute, private pokemonFilterService: PokemonFilterService) { }

ngOnInit(): void {
// one api call - get the list once and pass it down
// fyi the api call was cached by the resolver
this.typeList = this.route.snapshot.data['typeList'].results as Array<PokeTypes>;
this.typeList = this.typeList.sort((a, b) => (a.name > b.name) ? 1 : -1);
// use this service to check if the filter values changes from detail component (reset)
this.pokemonFilterService.filterDataHasChanged().subscribe((pokemonFilter: PokemonFilter) => {
if (pokemonFilter) {
this.pokemonFilter = pokemonFilter;
}
});
}

filterChanged(): void {
this.pokemonFilterService.change(this.pokemonFilter)
}
}

the pokemon-detail can subscribe and change the data and the header component will be notified.

ngOnInit(): void {
this.pagingOffset = 0;
this.getPokemons(this.pagingOffset);

// use this service to check if the filter values change (reset)
this.pokemonFilterService.filterDataHasChanged().subscribe((pokemonFilter : PokemonFilter) => {
if (pokemonFilter) {
this.pokemonFilter = pokemonFilter;
}
});
}

Summary

In one component you change the data and in the other component, you subscribe to the data. The components can be unrelated.

See full source code here https://github.com/tfa7

Adding / Removing components on the fly

As suggested by Tim,

quoting @tbosch's comment

Angular reuses previously created DOM elements by default

So to avoid this behavior, taken from the comment as well, you can use APP_VIEW_POOL_CAPACITY and assign it 0 as value.

bootstrap(MyApp, [provide(APP_VIEW_POOL_CAPACITY, {useValue: 0})])

Update

Note that since beta.1 APP_VIEW_POOL_CAPACITY was removed by #5993 and the DOM is being recreated correctly.

Angular:- routing of parent component based on child component value

One way to achieves this is adding an Output to the child component that emits when function "func()" fires, it's a good way to communicate between child to parent components.

Example:


/// parent.html

<child-shared-comp (dateSelected)="dateSelected($event)"></child-shared-comp>

// parent.component.ts
...
dateSelected(selectedDate: Date) {
//routing {queryParams: selectedDate}
}

//child-shared-component.ts

import { Output, EventEmitter } from '@angular/core';

@Output() dateSelected: EventEmitter<Date> = new EventEmitter<Date>();

func(dateSelected: Date){
If(condition statisfy){
this.dateSelected.emit(dateSelected);
}
}

For more information about Output:
https://angular.io/guide/template-syntax#input-and-output-properties



Related Topics



Leave a reply



Submit