The great angular dynamic (components) edit page

This one is a long story...

My comps goes like this, I GET from API a list of config rows, each of those has a list of config fields, each of those dynamically renders to an appropriate component, 2 of them are inherited 1 from another.

The flow is:
1. CTOR and ngOnInit, subscribce to 2 observables, 2 http requests for data.
2. Both subscribces call a ready fn that triggers the *ngIf
3. Then there is an *ngFor for the rows and another *ngFor for the fields
4. Down there I have my #container's for placement of a dynamic component
5. In some cases, since the dynamic component is inherited, its calling ngOnInit twice (its parent's ngOnInit)

Lets see how can we overcome each of these problems...
For implementation, see in my specially crafted git.



Problem 1:  the containers prop (my QueryList) is undefined

@ViewChildren ('container', { read: ViewContainerRef })
containers: QueryList<ViewContainerRef>;

In Part 1 of my app, open the console and see for yourself, when I log:
- ngEvent name
- is data ready
- containers as array, if availabe
- containers



We can see that:
1. Before 1st ngAfterViewInit the containers is undefined
2. While data hasn't arrived the query result is empy
3. Only after the data has arrived and updated, AND ngAfterViewChecked the query return results.

Conclusion - if we use some *ngIf or  *ngFor we must use ngAfterViewChecked conditionally


In Part 2 of my app I am attempting to add some components outside the *ngFor for the example and tutorial.

let type = i == 0 ? UiCompComponent : UiCompInheritedComponent
const componentFactory = this.resolver.resolveComponentFactory(type);
//here 'createComponent' will launch ctor
const componentRef = this.container.createComponent(componentFactory,);
let baseRef = componentRef.instance as UiCompComponent
baseRef.field = f

1st thing we'll is this error

Error: No component factory found for UiCompComponent. Did you add it to @NgModule.entryComponents?

Angular must be introduced to the components that will be added dynamically at the AppModule
providers: [],
entryComponents:[UiCompComponent, UiCompInheritedComponent],

Already we get the Major Error

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngForOf: undefined'. Current value: 'ngForOf: undefined'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook ?



Problem 2:  ExpressionChangedAfterItHasBeenCheckedError 

The solution here, other than playing with long setTimeout, is using
styleUrls: ['./big-edit-ngfor.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})

That means that angular no longer tracks your changes and you have to do it manually with
constructor(private cdr:ChangeDetectorRef) { }

So whenever some data is ready ..
this.ready = true
this.cdr.detectChanges()

In Part 2 example we loop the "data" rows to make comps in a single container.

In Part 3 we loop the containers list from the query

BONUS

In my solution the ngAfterViewChecked to fire endlessly so I gave him a flag, plus the containerQuery wasn't ready and I had to wait for his changes event


ngAfterViewChecked(): void {
if (this.fieldsContainersQuery && this.componentsGenerated == false) {
this.componentsGenerated = true
this.fieldsContainersQuery.changes.subscribe(changes=>{
this.fieldsContainersQuery.forEach(fieldContainerItem => this.generateComponents(fieldContainerItem))
this.cd.detectChanges()
})
}
}


Good Luck

Comments

Post a Comment

Popular posts from this blog

OverTheWire[.com] Natas Walkthrough - JUST HINT, NO SPOILERS

SOLVED The item could not be indexed successfully because the item failed in the indexing subsystem

Asp.Net Ending Response options, Response.End() vs CompleteRequest()