I'm struggling with a problem within Angular v1.6.1 where I am trying to transfer some data from a component to another component.
I have a component called navbar which resides in app\common\navbar has a controller that fetches data from a service. The following files make up navbar component
navbar.component.js
import controller from './navbar.controller';
import template from './navbar.html';
export default navbarComponent = {
restrict: 'E',
bindings: {},
template,
controller
};
navbar.controller.js
class NavbarController {
constructor(httpService) {
const dataUrl = "/assets/data/header.json";
this.name = 'Navbar';
this.headerData = {};
console.log("In "+this.name);
httpService.getData(dataUrl)
.then((response) => {
angular.extend(this.headerData,response.data);
},(error) => { throw error; });
}
}
export default NavbarController;
navbar.html
<section>
<top-header top-data="$ctrl.headerData"></top-header>
<section>
and my httpService resides in app\services folder. It fetches content using axios http library and looks something like this
httpService.js
import axios from 'axios';
export class HttpService {
constructor() {
this.name = "HttpService";
}
getData(api_url){
return axios.get(api_url)
.then((response) => response, (error) => error);
}
}
The component which uses my navbar component's headerData is top-header and resides in app\common\top-header. This is what it contains
top-header.component.js
import template from './top-header.html';
import controller from './top-header.controller';
export default topHeaderComponent = {
restrict: 'E',
template,
bindings: {
topData: '<'
},
controller,
};
top-header.controller.js
class TopHeaderController {
constructor() {
this.name = 'TopHeader';
this.topHeaderData = {};
this.$onInit = function() {
this.topHeaderData = this.topData;
console.log(this.topHeaderData);
console.log(this.topHeaderData.telephoneNumber);
console.log(this.topHeaderData);
}
}
}
export default TopHeaderController;
top-header.html
{{$ctrl.topHeaderData.telephoneNumber}}
and finally my static files resides in assets\data and the JSON I'm trying to fetch header.json contains
header.json
{
"telephoneNumber": 12345678
}
So the problem now I see is that the data does show up in my top-header component but I'm not sure what's happening but the data disappears (comes up undefined) once I try to access the object property.
What I'm saying is that in top-header.controller.js
when I console.log(this.topHeaderData); it shows the object but when I try to console.log(this.topHeaderData.telephoneNumber); it comes up undefined
I think the problem exists because of the execution priority of the Directives. I even set navbar component priority to 5 and it didn't help as the data is undefined.
top-header.controller.js
this.$onInit = function() {
this.topHeaderData = this.topData;
console.log(this.topHeaderData); // shows topData
console.log(this.topHeaderData.telephoneNumber); // undefined
console.log(this.topHeaderData); // shows topData
}
This data this.topHeaderData.telephoneNumber is essential as I use this in my template.
How can I resolve this issue? Any help would be greatly appreciated.
The problem may be in top-header.controller.js: you're assigning binded topData to this.topheaderData in $onInit hook but when component is initialized the data hasn't been fetched yet. Instead of $onInit you should use $onChange hook method which is called by Angular when binded property is updated (in your case when data is fetched from server)
Angular component docs:
$onChanges(changesObj) - Called whenever one-way bindings are updated.
The changesObj is a hash whose keys are the names of the bound
properties that have changed, and the values are an object of the form
{ currentValue, previousValue, isFirstChange() }. Use this hook to
trigger updates within a component such as cloning the bound value to
prevent accidental mutation of the outer value.
Related
I have a non AngularJS snippet that is communicating with my AngularJS modules via custom events. Each of these AngularJS modules represents a different route. When I am leaving each route $onDestroy is triggered but does not remove the event listener. What am I missing to un-register my custom event?
Non AngularJS HTML Snippet
<script>
function sendEvent() {
const payload = getPayload();
const event = new CustomEvent('myCustomEvent', { payload });
window.dispatchEvent(event);
}
</script>
<button onclick="sendEvent()">Send Custom Event</button>
AngularJS Component
Class ModuleA {
$onInit() {
this.$window.addEventListener('myCustomEvent', this.onCustomEventClick.bind(this));
}
$onDestroy() {
this.$window.removeEventListener('myCustomEvent', this.onCustomEventClick.bind(this), false);
}
}
Class ModuleB {
$onInit() {
this.$window.addEventListener('myCustomEvent', this.onCustomEventClick.bind(this));
}
$onDestroy() {
this.$window.removeEventListener('myCustomEvent', this.onCustomEventClick.bind(this), false);
}
}
Every time you call bind it will create a new function and return it instead of modifying the function itself. So the even listeners you provide to addEventListener and removeEventListener are different functions, thus the registered ones are not removed.
To solve it, call bind once in $onInit and keep a reference to the returned function and always use that reference:
Class ModuleB {
$onInit() {
this.onCustomEventClick = this.onCustomEventClick.bind(this)
this.$window.addEventListener('myCustomEvent', this.onCustomEventClick);
}
$onDestroy() {
this.$window.removeEventListener('myCustomEvent', this.onCustomEventClick, false);
}
}
I am trying to add angularjs component using the code as per below. app.component doesn't work like this
Where as if I execute app.component from outsite it works.
How to fix this issue. Notice that the API will return component names. I just need to render them.
app.service('applookup', function($http) {
this.register = function() {
return $http.get('http://localhost:3003/api/provider/fetch/app1').then(function(res){
app.component('appleComponent', {
template : 'test'
});
return res.data.componentList;
});
}
});
As #William and #Zooly mentioned in comments. We can add reference to $compileProvider.component as per below in app.config
app.register = {
component : function(name, object) {
$compileProvider.component(name, object);
return (this);
}
Then use app.register.component to register the component
I am trying to develop an application in angular es6 . I have a problem with directve.
Here is my code
export default class RoleDirective {
constructor() {
this.template="";
this.restrict = 'A';
this.scope = {
role :"#rolePermission"
};
this.controller = RoleDirectiveController;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
// Directive compile function
compile(element,attrs,ctrl) {
console.log("df",this)
}
// Directive link function
link(scope,element,attrs,ctrl) {
console.log("dsf",ctrl.role)
}
}
// Directive's controller
class RoleDirectiveController {
constructor () {
console.log(this.role)
//console.log("role", commonService.userModule().getUserPermission("change_corsmodel"));
//$($element[0]).css('visibility', 'hidden');
}
}
export default angular
.module('common.directive', [])
.directive('rolePermission',[() => new RoleDirective()]);
The problem is i couldn't get the role value inside constructor.
here is my html implementation
<a ui-sref="event" class="button text-uppercase button-md" role-permission="dfsd" detail="sdfdsfsfdssd">Create event</a>
If i console this it will get the controller object. But it will not get any result while use this.role.
Ok, so I managed to find out how this works.
Basically, the scope values cannot be initialized on the controller's constructor (because this is the first thing executed on a new object) and there is also binding to be considered.
There is a hook that you can implement in your controller that can help you with your use case: $onInit:
class RoleDirectiveController {
constructor () {
// data not available yet on 'this' - they couldn't be
}
$onInit() {
console.log(this.role)
}
}
This should work. Note that this is angular1.5+ way of doing things when not relying on $scope to hold the model anymore. Because if you use the scope, you could have it in the controller's constructor (injected).
I have a parent component:
#Component({
selector: 'parent-component',
directives: [I18nDirective, child-component],
template: require('./parent.component.html'),
styles: [require('./parent.component.scss')],
})
export class ParentComponent {
}
Then I have child component:
#Component({
selector: 'child-component',
template: require('./child-component.component.html'),
styles: [require('./child-component.component.scss')]
})
export class ChildComponent {
constructor(private _eventAggregator: EventAggregatorService, private _zone: NgZone) {}
In the parent component's html I use: child-component
The problem is in the spec test of child component. They pass when the test is:
describe('Center On Me Button', () => {
let fixture;
let nativeElement;
let componentInstance;
beforeEachProviders(() => [
ChildComponent,
EventAggregatorService,
GeoLocationService,
provide(GoogleMapsAPIWrapper, {useClass: StabGoogleMapsApiWrapper}),
provide(MapController, {useClass: StabMapControllerService}),
provide(MapEventsService, {useClass: StabMapEventsService})
]);
beforeEach(injectAsync([TestComponentBuilder, GoogleMapsAPIWrapper, GeoLocationService, MapController, EventAggregatorService
], (tcb: TestComponentBuilder ) => {
return tcb
.createAsync(ChildComponent).then((componentFixture: ComponentFixture<ChildComponent>) => {
({nativeElement, componentInstance} = componentFixture);
fixture = componentFixture;
componentFixture.detectChanges();
});
it('should enable Geolocation button when it is clicked', done => {
componentInstance.isGeoLocationButtonEnabled = false;
nativeElement.querySelector('.toggle.fa.fa-crosshairs').click();
expect(componentInstance.isGeoLocationButtonEnabled).toBe(true);
done();
});
}));
My PROBLEM is that in such a configuration the tests pass. But as soon as I want to add the service instance to the child-component constructor, the test wont pass (it fails in the parent.component.spec) with the following message:
TypeError: undefined is not an object (evaluating 'fixture.detectChanges') ... in parent.component.spec.ts
All I have done is added the: private someService: SomeService to the constructor of the child-component. So I have now:
export class ChildComponent {
constructor(private _eventAggregator: EventAggregatorService, private _zone: NgZone, private _someService: SomeService) {}
This service I add is added to the providers list at the top level of angular app. Of course I add it to the providers list in the test itself.
When I do this, tests start to fail with the previous message I wrote. Why the heck is that ? I really do not understand this behaviour and resource are limited on this in the network. NO documentation yet.
Angular 2 - I need a component to output a function so that a nested component can call it.
I'm using typescript and angular 2.
This is the output code:
#Component({
selector: 'jobs',
providers: [Job, JobService, Notification, NotificationService, Counties, Towns],
templateUrl: './app/components/job/jobs.html',
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, NgIf, NgFor, DialogJob, BootFlatDatePicker],
pipes:[SearchFilter],
outputs: ['jobCancelledNotification']
})
jobCancelledNotification = (event) => {console.log("job cancelled via output!!!!"); }
I need "jobCancelledNotification" to be called from a nested dialog component.
For my nested dialog component I have an output:
#Output() jobCancelledNotification = new EventEmitter();
Then I try call it with this:
this.jobCancelledNotification.emit("event");
When the dialog component is ready is needs to call jobCancelledNotification which calls jobCancelledNotification from the parent component.
First, you don't need to include NgIf, NgFor and CORE_DIRECTIVES they are already included.
In parent component:
```<message (message)="call($event)"></message>```
export class App {
response: any;
call(message: any) {
this.response = message;
}
}
In child component:
#Output() message = new EventEmitter();
constructor() {
setTimeout(()=> {
this.message.emit("Hello from MessageCmp");
},2000);
}
https://plnkr.co/edit/vUocS8dDTKsJFGGSSKYk?p=preview