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 ExampleExample 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/
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;
}
});
}
SummaryIn 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
So to avoid this behavior, taken from the comment as well, you can useAngular reuses previously created DOM elements by default
APP_VIEW_POOL_CAPACITY
and assign it 0
as value.bootstrap(MyApp, [provide(APP_VIEW_POOL_CAPACITY, {useValue: 0})])
Update
Note that since beta.1APP_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
Get Width Height of Remote Image from Url
How to Send Authorization Header with Axios
Why a Variable Defined Global Is Undefined
Obtain Smallest Value from Array in JavaScript
Adding Script Tags in Angular Component Template
How to Subscribe to Topics with Web Browser Using Firebase Cloud Messaging
Can't Require() Default Export Value in Babel 6.X
How Do We Set Remote in Typeahead.Js
Typeerror: Illegal Invocation on Console.Log.Apply
Pass Post Data with Window.Location.Href
Why and When to Use Default Export Over Named Exports in Es6 Modules
How to Check Dimensions of Image Before Uploading
Objects Are Not Valid as a React Child (Found: [Object Promise])
Understanding Xmlhttprequest Over Cors (Responsetext)