ExpressionChanged error but no lifecycle hooks - javascript

I'm trying to access a couple of material drawers so I can open/close them in another component, however I am getting the following error:
NavigationComponent.html:30 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'sideNavsDrawers: undefined'. Current value: 'sideNavsDrawers: [object Object],[object Object]'.
I have read that this is normally a product of putting inappropriate functionality into lifecycle hooks, however I have just started this app and have zero lifecycle hooks.
I have a navigation component that I'm attempting to pass a QueryList of MatSideNavs to.
#Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss']
})
export class NavigationComponent {
#ViewChildren(MatSidenav) sideNavsDrawers: QueryList<MatSidenav> | undefined;
}
Within its template I have
<app-toolbar [sideNavsDrawers]="sideNavsDrawers"></app-toolbar>
And within that component
#Component({
selector: 'app-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.scss']
})
export class ToolbarComponent implements OnInit {
#Input() sideNavsDrawers: QueryList<MatSidenav> | undefined;
}
Adding the union for undefined was something I added when I saw the previous value was undefined in the error, but I don't know if it is necessary.
Why are these very simple components generating this error, and how can I eliminate it?
Reproduced on StackBlitz

The exception is triggered when a value used for data binding changes as the view is being updated, which is the case for the QueryList populated with ViewChildren in NavigationComponent.
You can avoid the exception this way:
Redefine the sideNavsDrawers input property as an array:
export class ToolbarComponent implements OnInit {
#Input() sideNavsDrawers: Array<MatSidenav>;
}
Construct the array with the template reference variables instead of using ViewChildren:
<mat-sidenav #leftDrawer ... >
...
<mat-sidenav #rightDrawer ... >
...
<app-toolbar [sideNavsDrawers]="[leftDrawer, rightDrawer]">
See this stackblitz for a demo.

Related

Is there a breaking change in angular when upgrading from angular 8 to 9 in nativeElement

I have recently upgraded my angular cli version to 9.1.4 from earlier version of 8.
So i wanted to ask that is there a breaking change in this nativeElement thing.
Some of the code of my ts file where i used my nativeElement is as follows
import { Component, OnInit, ViewChild, ElementRef, Renderer2, Input } from '#angular/core';
#Component({
selector: 'app-note-card',
templateUrl: './note-card.component.html',
styleUrls: ['./note-card.component.scss']
})
export class NoteCardComponent implements OnInit {
#Input() title: string;
#Input() body: string;
#ViewChild('truncator') truncator: ElementRef<HTMLElement>;
#ViewChild('bodyText') bodyText:ElementRef<HTMLElement>;
constructor(private renderer: Renderer2) { }
ngOnInit() {
// work out if there is a text overflow and if not then hide the truncator
let style = window.getComputedStyle(this.bodyText.nativeElement, null);
let viewableHeight = parseInt(style.getPropertyValue("height"), 10);
if(this.bodyText.nativeElement.scrollHeight>viewableHeight){
// if there is a text overflow, show the fade out truncator
this.renderer.setStyle(this.truncator.nativeElement, 'display', 'block');
}else{
// else (there is a text overflow), hide the fade out truncator
this.renderer.setStyle(this.truncator.nativeElement, 'display', 'none');
}
}
}
And this is the error which i get in the browser
ERROR TypeError: Cannot read property 'nativeElement' of undefined
at NoteCardComponent.ngOnInit (note-card.component.ts:22)
at callHook (core.js:4735)
at callHooks (core.js:4699)
at executeInitAndCheckHooks (core.js:4639)
at selectIndexInternal (core.js:9701)
at Module.ɵɵadvance (core.js:9662)
at NotesListComponent_Template (notes-list.component.html:19)
at executeTemplate (core.js:12098)
at refreshView (core.js:11945)
at refreshComponent (core.js:13410)
defaultErrorLogger # core.js:6237
Any help is welcomed Thanks in advance :)
When you are using ViewChild decorator in OnInit life cycle hook you must specify the static property to be true to resolve query results before change detection runs. By default is false so it resolves after change detection runs.
For use in ngOnInit, like in your case, see the code below:
#ViewChild('truncator', { static: true }) truncator: ElementRef<HTMLElement>;
#ViewChild('bodyText', { static: true }) bodyText:ElementRef<HTMLElement>;
If you are not using in OnInit you need to set it to false. In Angular 9 is optional in this case since the default is false.
I wonder, how did you not get the errors in Angular 8 where this property was mandatory for both cases.
#ViewChild() decorator makes the queried elements available only from AfterViewInit on. So, instead of running your code in ngOnInit method, move it to ngAfterViewInit method.

Cannot read property of undefined #ViewChild not working Angular 5

I'm having a bit of trouble I cant seem to figure out why my #ViewChild isnt working..
Basically I want to call a function in one component from another component so in my sidebar component I have a function called sendData() and I want to be able to call that from a button click in my header component so what Ive done is..
Sidebar component
import { Component, OnInit, Input, OnChanges, SimpleChanges, AfterViewInit } from '#angular/core';
import * as _ from 'lodash';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.scss']
})
export class SidebarComponent implements OnInit, OnChanges {
constructor(
private contenfulService: ContentfulService,
private userService: UserService
) { }
ngOnInit() {
}
sendData(){
...do something
}
}
header.component.ts
import { Component, OnInit, Input, ViewChild, EventEmitter } from '#angular/core';
import { UserService } from '../../../user.service';
import { SidebarComponent } from '../sidebar/sidebar.component';
#Component({
selector: 'app-program-header',
templateUrl: './program-header.component.html',
styleUrls: ['./program-header.component.scss']
})
export class ProgramHeaderComponent implements OnInit {
#ViewChild(SidebarComponent) sidebar;
constructor() { }
ngOnInit() {
}
}
header.component.html
<div (click)="sidebar.sendData()"></div>
but it isnt working Im getting this error in the console...
ERROR TypeError: Cannot read property 'sendData' of undefined
I have removed code for brevity, so please let me know if there is more information you need
Im not sure what the problem is?
EDIT
Or if anyone knows another way to call a function from a seperate component let me know
Any help would be appreciated!
Thanks!
ViewChild is expected to be used to get target element from current component's view(template) which matches the selector.
But according to your comment above, it seems there is no app-sidebar placed in your header.component.html, so ViewChild is not able to get a valid element which results in your current error.
The solution should be place app-sidebar at least once.
<app-sidebar></app-sidebar>
<div (click)="sidebar.sendData()"></div>
If header and sidebar components are siblings, you can not pass data between them directly. Take a look at ‘Output’ and ‘Input’ from angular. Alternatively, you can use a Service to pass data between components. Check out services and observables.
<parent>
<app-program-header>
</app-program-header>
<app-sidebar>
</app-sidebar>
</parent>
Just as an addition to this conversation, I have been squirreled off on a couple of occasions chasing what seemed to be the lack of a functioning #ViewChild when the cause was the #ViewChild referencing a component in a module that was not being imported. The "cannot read property of undefined" can become extremely misleading and may not be related to #ViewChild at all; be sure to check your imports first.
In my case - it was the import from "#shared" which has caused this issue. You have to pay attention, that the component you are using the #ViewChild component reference is not in the same module with same shorthand path. If it is, import the #ViewChild component from
'shared/components/sidebar/sidebar.component'
and not from '#shared'

What is ngDefaultControl in Angular?

No, this is not a duplicate question. You see, there is a ton of questions and issues in SO and Github that prescribe that I add this directive to a tag that has [(ngModel)] directive and is not contained in a form. If I don't add it I get an error:
ERROR Error: No value accessor for form control with unspecified name attribute
Ok, the error goes away if I put this attribute there. BUT, wait! Nobody knows what it does! And Angular's doc doesn't mention it at all. Why do I need a value accessor when I know that I don't need it? How is this attribute connected to value accessors? What does this directive do? What is a value accessor and how do I use it?
And why does everybody keep doing things that they don't understand at all? Just add this line of code and it works, thank you, this is not the way to write good programs.
And then. I read not one but two huge guides about forms in Angular and a section about ngModel:
https://angular.io/guide/forms
https://angular.io/guide/reactive-forms
https://angular.io/guide/template-syntax#ngModel
And you know what? Not a single mention of either value accessors or ngDefaultControl. Where is it?
[ngDefaultControl]
Third party controls require a ControlValueAccessor to function with angular forms. Many of them, like Polymer's <paper-input>, behave like the <input> native element and thus can use the DefaultValueAccessor. Adding an ngDefaultControl attribute will allow them to use that directive.
<paper-input ngDefaultControl [(ngModel)]="value>
or
<paper-input ngDefaultControl formControlName="name">
So this is the main reason why this attribute was introduced.
It was called ng-default-control attribute in alpha versions of angular2.
So ngDefaultControl is one of selectors for DefaultValueAccessor directive:
#Directive({
selector:
'input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])[formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],
[ngDefaultControl]', <------------------------------- this selector
...
})
export class DefaultValueAccessor implements ControlValueAccessor {
What does it mean?
It means that we can apply this attribute to element (like polymer component) that doesn't have its own value accessor. So this element will take behaviour from DefaultValueAccessor and we can use this element with angular forms.
Otherwise you have to provide your own implementation of ControlValueAccessor
ControlValueAccessor
Angular docs states
A ControlValueAccessor acts as a bridge between the Angular forms API
and a native element in the DOM.
Let's write the following template in simple angular2 application:
<input type="text" [(ngModel)]="userName">
To understand how our input above will behave we need to know which directives are applied to this element. Here angular gives out some hint with the error:
Unhandled Promise rejection: Template parse errors: Can't bind to
'ngModel' since it isn't a known property of 'input'.
Okay, we can open SO and get the answer: import FormsModule to your #NgModule:
#NgModule({
imports: [
...,
FormsModule
]
})
export AppModule {}
We imported it and all works as intended. But what's going on under the hood?
FormsModule exports for us the following directives:
#NgModule({
...
exports: [InternalFormsSharedModule, TEMPLATE_DRIVEN_DIRECTIVES]
})
export class FormsModule {}
After some investigation we can discover that three directives will be applied to our input
NgControlStatus
#Directive({
selector: '[formControlName],[ngModel],[formControl]',
...
})
export class NgControlStatus extends AbstractControlStatus {
...
}
NgModel
#Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,
DEFAULT_VALUE_ACCESSOR
#Directive({
selector:
`input:not([type=checkbox])[formControlName],
textarea[formControlName],
input:not([type=checkbox])formControl],
textarea[formControl],
input:not([type=checkbox])[ngModel],
textarea[ngModel],[ngDefaultControl]',
,,,
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgControlStatus directive just manipulates classes like ng-valid, ng-touched, ng-dirty and we can omit it here.
DefaultValueAccesstor provides NG_VALUE_ACCESSOR token in providers array:
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
...
#Directive({
...
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
NgModel directive injects in constructor NG_VALUE_ACCESSOR token that was declared on the same host element.
export NgModel extends NgControl implements OnChanges, OnDestroy {
constructor(...
#Optional() #Self() #Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
In our case NgModel will inject DefaultValueAccessor. And now NgModel directive calls shared setUpControl function:
export function setUpControl(control: FormControl, dir: NgControl): void {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator !, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
dir.valueAccessor !.writeValue(control.value);
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
...
}
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void
{
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
dir.valueAccessor !.writeValue(newValue);
// control -> ngModel
if (emitModelEvent) dir.viewToModelUpdate(newValue);
});
}
And here is the bridge in action:
NgModel sets up control (1) and calls dir.valueAccessor !.registerOnChange method. ControlValueAccessor stores callback in onChange(2) property and fires this callback when input event happens (3). And finally updateControl function is called inside callback (4)
function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
where angular calls forms API control.setValue.
That's a short version of how it works.

Angular 2 - testing a component with #input used in ngOnInit lifecycle hook

Currently I am trying to test a child component which is accepting an input from the host component, and used within the ngOnInit life cycle hook like the code below.
#Component({
selector: 'my-child-component',
template: '<div></div>'
})
class ChildComponent implements OnInit {
#Input() myValue: MyObject;
transformedValue: SomeOtherObject;
ngOnInit():void {
// Do some data transform requiring myValue
transformedValue = ...;
}
}
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class HostComponent {
someValue: MyObject = new MyObject(); // how it is initialized it is not important.
}
How should the ChildComponent be tested in this case where myValue needs the to be present upon creation while being able to have access to ChildComponent.transformedValue for assertion.
I tried creating the ChildComponent using the Angular TestBed class like this
componentFixture = testBed.createComponent(LoginFormComponent)
however the ngOnInit would have already been called up to the point where I call
fixture.componentInstance.myValue = someValue;
I also tried creating a fixture of the HostComponent, and while that works, I got stuck at getting access to the ChildComponent instance that was created, which i require to perform assertions on the ChildComponent.transformedValue field.
Help is greatly appreciated!
Thanks a lot!
Angular offers the ability to inject children components to their parent components using the #ViewChild() decorator. See https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
By updating the TestHostcomponent (that is written within the .spec.ts file) to the following
#Component({
template:`<my-child-component [myValue]="someValue"></my-child-component>`
})
class TestHostComponent {
#ViewChild(MyChildComponent)
childComponent: MyChildComponent;
}
it exposes its child component instance ( and its variables ), making the assertion of the 'transformedValue' possible, as per below.
componentFixture = testBed.createComponent(TestHostComponent)
expect(componentFixture.componentInstance.childComponent.transformedValue).toEqual(...someValue);

Angular 2 alpha 22, How to pass down a value to a component through attributes?

I declared a component with a selector 'app'
then I used it like follow:
<app title="Awesome Title"></app>
in the template I wrote this:
The title passed {{ title }}
I did add write the Component annotation:
#Component({
selector: 'app',
properties: {title:'title'} // also tried ['title']
});
But nothing shows up, the output is
The title passed
Any help? Thanks
At the time of writing the most recent version is alpha.26.
The property definition has changed slightly, so here is the new syntax
#Component({
selector: 'app',
properties: ['title:title']
});
<div [title]="Some Title"></div>
I have started a series of blog articles to demo Angular 2 concepts. Check it out here:
http://www.syntaxsuccess.com/viewarticle/angular-2.0-examples
I have a working example of how to assign properties to components via properties. Check out the tree view example as it uses it heavily.
For some reason, properties don't seem to work on the bootstrapped root app.
Try creating a component called 'title-app' and loading it inside of 'app'.
// inner component
#Component({
selector: 'title-component',
properties: {title:'title'}
})
#View({ template: '<h1>{{title}}</h1>'})
class titleComponent{}
// root app
#Component({ selector: 'app' })
#View({
directives: titleComponent,
template: '<title-component [title]="Some Title"></title-component>'
})
class App{}
You might also want to use the latest version of Angular2: currently alpha-26. From alpha-26+, if your property is the same name you can just call it once.
properties: {'title': 'title'} => properties: 'title'
the correct way to do this is:
import {
ComponentAnnotation as Component,
ViewAnnotation as View, bootstrap
} from 'angular2/angular2';
import {Attribute} from 'angular2/src/core/annotations_impl/di';
#Component({
selector: 'app'
})
#View({
template: '{{goofy}} stuff'
})
class App {
goofy:string;
constructor(#Attribute('goofy') goofy:string) {
this.goofy = goofy;
}
}
bootstrap(App);
See the full Plunker here http://plnkr.co/edit/n2XWez69HNJbixH3tEmR?p=preview
However, this is currently broken and is a known issue https://github.com/angular/angular/issues/1858

Categories