I'm currently working on an Angular 6 applicaiton. I try to call a method from an expression. Unfortunately it seems it doesn't work anymore.
The example is very simple and looks like that in my car.component.html:
<div>Car Name: {{ getCarName() }} </div>
In my component code car.component.ts, I implemented that function something like that:
getCarName() {
return this.carName;
}
Actually I'm changing the property carName each time I hover over another div on my component UI.
Unfortunately I'm getting the following error:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value...
Do you know how to solve this issue?
Thank you!!
ExpressionChangedAfterItHasBeenCheckedError means, that if you construct your logic this way, you have to detect changes manually inside your component:
import { Component, AfterViewChecked, ChangeDetectorRef } from '#angular/core';
/* Other stuff goes here */
export class PhoneVerifyComponent implements AfterViewChecked {
constructor(private _changeDetectorRef: ChangeDetectorRef) { }
ngAfterViewChecked() {
/* This way the detector will run after every change */
this._changeDetectorRef.detectChanges();
}
getCarName() {
/* This way you run detector only on this function call */
this._changeDetectorRef.detectChanges();
return this.carName;
}
}
You can choose every way you like, run detector in specific place, or after every change. If your goal is just to solve the issue only with this method, I recommend to run it on specific function call, but if you have many cases like that - better run it in ngAfterViewChecked.
BUT
The shortest way is to store the result of this function execution to class property and simply interpolate this property, instead of method:
<div>Car Name: {{ carName }}</div>
Anyway, we don't know your goals and what's going on in your real project, so you have to choose more proper way for your case.
Related
Inside Angular service i have a variable count, which i want to observe whenever it's updated, i want to set that updated value to another variable in a different component.
import {BehaviorSubject} from "rxjs/BehaviorSubject";
import 'rxjs/Rx';
export class DashboardService{
count = new BehaviorSubject<number>(0);
count$=this.count.asObservable();
addCount(data){
this.count.next(data);
}
}
I get the error eventhough i have imported all the relevant libraries .Any idea why i'm getting this? can anyone tell me what's happening with the code ?
}
rxJs version 5.4.3 latest!
Try this:
export class DashboardService {
count = new BehaviorSubject<number>(0);
count$ = this.count.asObservable();
public addCount = (data) => {
this.count.next(data);
}
}
The issue you are encountering is that you are using addCount in a context where this inside the function is altered (i.e. pointing to something else). Using lamda based functions in typescript ensures this can not happen. This is not an excuse for always using them however.
I recommend you read up a bit on lambda/arrow functions, it might also be a good idea to look in to how this works in javascript and in typescript.
I am making a data table component. For accessibility, I want each table header to have a tooltip via a title attribute. The tooltip value can be explicitly set (in case it is different than the text in the header), but by default I want it to use whatever the inner text is.
I have a half-working solution, but it's not quite working and I don't know if I'm breaking some Angular rules by doing it this way.
Here's my abbreviated component html:
<div #headerContent [attr.title]="title"><ng-content></ng-content></div>
I am tagging the div with headerContent so I can reference it later.
Ok, now here's the abbreviated component class:
#Component({ ... })
export class TableHeaderComponent implements AfterContentInit {
#ViewChild('headerContent') headerContent: ElementRef;
#Input() title: string;
ngAfterContentInit() {
if (!this.title) {
this.title = this.headerContent.nativeElement.textContent.trim();
}
}
}
The idea is that if no title was specified, look at the div and grab its text content, using that for the title.
This works fine when I test it in the browser.
Example usage:
<th table-header>Contact</th>
Here, since I didn't specify a title, it should use Contact as the title.
But when I write unit tests for this component, it blows up with:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Contact'
I'm not sure what I'm doing wrong here.
EDIT: Injecting the ChangeDetectorRef into my component class and calling detectChanges() after updating the title property seems to have fixed the problem. It works in the browser and also the unit tests pass.
Still wondering if this is breaking any rules in Angular to do it this way.
I was able to solve this by manually triggering change detection after updating the property.
To do this, you first inject the ChangeDetectorRef in the constructor:
constructor(private cd: ChangeDetectorRef) { }
Then after updating the property, call cd.detectChanges().
I am quite new to angular2 and I have a problem with the change detection.
At the loading of my page, I need to call some API in order to get the information to construct my web page. What I do is that when I receive this information (which is contained in an array) I want to iterate through it using *ngFor. This is my code for a course component.
import {Component,Input} from 'angular2/core';
import {courseCompDiagram, sepExInWeeks} from "../js/coursesTreatment.js";
import {getSampleWeeks} from "../js/courseMng.js";
#Component({
selector: 'course',
directives:[Exercises],
template: `
<div class="course">
<h2>{{aCourse.name}}</h2>
<div class='diag-container row'>
<div id="Completion{{aCourse.name}}"></div>
<div *ngFor="#week of weeks"> {{week.weekNb}} </div>
</div>
</div>`
})
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, function(concernedCourse){
//When my API call is finished, I treat the course, and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
So first of all, I would like to know if it's normal that angular2 doesnt detect the fact that this.weeks changed.
Then I don't know if I should us the ngAfterViewInit function to do my work in. The problem is that I began doing that because in my courseCompDiagram I need to use jquery to find the div containing the id Completion[...] and modify it (using highcharts on it). But maybe I should do all this at some other point of the loading of the page ?
I tried using ngZone and ChangeDetectionStrategy as stated in this topic but I didn't manage to make it work on my case.
Any help is appreciated, even if it doesn't completely solves the problem.
export class Course{
//This is inputed from a parent component
#Input() aCourse;
this.weeks = [];
constructor(private _zone:NgZone) {}
ngAfterViewInit(){
//I call this method and when the callbacks are finished,
//It does the following lines
courseCompDiagram(this.aCourse, (concernedCourse) => {
//When my API call is finished, I treat the course, and store the results in weeks
this._zone.run(() => {
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
});
//This is not supposed to stay in my code,
//but is here to show that if I call it here,
//the weeks will effectively change
this.weeks = getSampleWeeks();
}
}
You should use arrow functions to be able to use lexical this, as described below:
courseCompDiagram(this.aCourse, (concernedCourse) => {
// When my API call is finished, I treat the course,
// and store the results in weeks
this.weeks = sepExInWeeks(concernedCourse.course.exercises);
});
As a matter with raw callbacks, the this keyword doesn't correspond to your component instance.
See this link for more hints about the lexical this of arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions.
Otherwise I have a sample comment regarding your code. You should leverage observables for your HTTP calls. It doesn't seem the case in your code as far as I can see...
I am currently working on porting a Backbone project to an Angular 2 project (obviously with a lot of changes), and one of the project requirements requires certain methods to be accessible publicly.
A quick example:
Component
#component({...})
class MyTest {
private text:string = '';
public setText(text:string) {
this.text = text;
}
}
Obviously, I could have <button (click)="setText('hello world')>Click me!</button>, and I would want to do that as well. However, I'd like to be able to access it publicly.
Like this
<button onclick="angular.MyTest.setText('Hello from outside angular!')"></click>
Or this
// in the js console
angular.MyTest.setText('Hello from outside angular!');
Either way, I would like the method to be publicly exposed so it can be called from outside the angular 2 app.
This is something we've done in backbone, but I guess my Google foo isn't strong enough to find a good solution for this using angular.
We would prefer to only expose some methods and have a list of public apis, so if you have tips for doing that as well, it'd be an added bonus. (I have ideas, but others are welcomed.)
Just make the component register itself in a global map and you can access it from there.
Use either the constructor or ngOnInit() or any of the other lifecycle hooks to register the component and ngOnDestroy() to unregister it.
When you call Angular methods from outside Angular, Angular doesn't recognize model change. This is what Angulars NgZone is for.
To get a reference to Angular zone just inject it to the constructor
constructor(zone:NgZone) {
}
You can either make zone itself available in a global object as well or just execute the code inside the component within the zone.
For example
calledFromOutside(newValue:String) {
this.zone.run(() => {
this.value = newValue;
});
}
or use the global zone reference like
zone.run(() => { component.calledFromOutside(newValue); });
https://plnkr.co/edit/6gv2MbT4yzUhVUfv5u1b?p=preview
In the browser console you have to switch from <topframe> to plunkerPreviewTarget.... because Plunker executes the code in an iFrame. Then run
window.angularComponentRef.zone.run(() => {window.angularComponentRef.component.callFromOutside('1');})
or
window.angularComponentRef.zone.run(() => {window.angularComponentRef.componentFn('2');})
This is how i did it. My component is given below. Don't forget to import NgZone. It is the most important part here. It's NgZone that lets angular understand outside external context. Running functions via zone allows you to reenter Angular zone from a task that was executed outside of the Angular zone. We need it here since we are dealing with an outside call that's not in angular zone.
import { Component, Input , NgZone } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'example',
templateUrl: './example.html',
})
export class ExampleComponent {
public constructor(private zone: NgZone, private router: Router) {
//exposing component to the outside here
//componentFn called from outside and it in return calls callExampleFunction()
window['angularComponentReference'] = {
zone: this.zone,
componentFn: (value) => this.callExampleFunction(value),
component: this,
};
}
public callExampleFunction(value: any): any {
console.log('this works perfect');
}
}
now lets call this from outside.in my case i wanted to reach here through the script tags of my index.html.my index.html is given below.
<script>
//my listener to outside clicks
ipc.on('send-click-to-AT', (evt, entitlement) =>
electronClick(entitlement));;
//function invoked upon the outside click event
function electronClick(entitlement){
//this is the important part.call the exposed function inside angular
//component
window.angularComponentReference.zone.run(() =
{window.angularComponentReference.componentFn(entitlement);});
}
</script>
if you just type the below in developer console and hit enter it will invoke the exposed method and 'this works perfect ' will be printed on console.
window.angularComponentReference.zone.run(() =>
{window.angularComponentReference.componentFn(1);});
entitlement is just some value that is passed here as a parameter.
I was checking the code, and I have faced that the Zone is not probably necessary.
It works well without the NgZone.
In component constructor do this:
constructor(....) {
window['fncIdentifierCompRef'] = {
component = this
};
}
And in the root script try this:
<script>
function theGlobalJavascriptFnc(value) {
try {
if (!window.fncIdentifierCompRef) {
alert('No window.fncIdentifierCompRef');
return;
}
if (!window.fncIdentifierCompRef.component) {
alert('No window.fncIdentifierCompRef.component');
return;
}
window.fncIdentifierCompRef.component.PublicCmpFunc(value);
} catch(ex) {alert('Error on Cmp.PublicCmpFunc Method Call')}
}
</script>
This works to me.
The problem is that Angular's components are transpiled into modules that aren't as easy to access as regular JavaScript code. The process of accessing a module's features depends on the module's format.
An Angular2 class can contain static members that can be defined without instantiating a new object. You might want to change your code to something like:
#component({...})
class MyTest {
private static text: string = '';
public static setText(text:string) {
this.text = text;
}
}
Super simple solution!! save component or function with an alias outside
declare var exposedFunction;
#Component({
templateUrl: 'app.html'
})
export class MyApp {
constructor(public service:MyService){
exposedFunction = service.myFunction;
}
at index.html add in head
<script>
var exposedFunction;
</script>
Inside exposed function do not use this. parameters if you need them you will have to use closures to get it to work
This is particularly useful in ionic to test device notifications on web instead of device
I'm currently learning and using Aurelia and something kind of weird (maybe normal) is happening.
When using the following code
export class NavBar {
get username() {
console.log('o_o')
return 'name' + Date.now()
}
}
And in the template ${username}, the username is always updating, several times per seconds (and console.log are logged several times as well of course).
The workaround is to simply use a function and not a getter and call ${username()} in the template. But is this behaviour normal? So should I use sometimes getter sometimes not?
Thanks!
This is normal, Aurelia polls your property for changes because it has no way of knowing when your property-getter will return a different value.
If it were a simple property (without a getter), Aurelia could observe the property directly, no polling would be needed.
To avoid the polling you could tell Aurelia's binding system what to observe:
import {computedFrom} from 'aurelia-framework';
export class Foo {
_username = 'hello';
#computedFrom('_username')
get username() {
return this._username;
}
}
Another option would be to use a one-time binding:
${username & oneTime}