How to communicate between 2 component - sibling components(Angular 1.5.8) - javascript

Hey I have the following component tree: I have a root component called rules and two sons components called: rulesPanel & rulesEditor.
Now I can create a communication between son and mother component:
rulesEditor can call to rules component and jump an event on him.
rulesPanel can call to rules component and jump an event on him.
I want to have a communication between the 2 brothers:
rulesEditor and rulesPanel.
I don't want to use $scope or $broadcast, I want to do it through the bindings of the component himself.
I have tried to think of way of doing so, but all I got is that I can call to upper level but not to a parallel level.
Edit:
My Question is different from the possible duplication,
I don't want to pass a data, I want to execute a function in one component and then execute another function in the sibling component as a result of a click function in the brother component.
Here is my code and what I have achieved so far:
var app = angular.module("app",[]);
angular.module('app').component('rules', {
template: `
<rules-panel dial-mom="$ctrl.receivePhoneCall(message)">
</rules-panel>
<rules-editor>
</rules-editor>`,
bindings: {
},
controller: rulesController,
});
function rulesController(){
var self = this;
self.receivePhoneCall = function(message){
console.log("Hello Son");
console.log("I got your message:",message)
}
console.log("rulesController")
}
angular.module('app').component('rulesPanel', {
template: `<h1>rulesPanel</h1>
<button ng-click="$ctrl.callMom()">CallMom</button>
<button ng-click="$ctrl.CallBrother()">CallBrother</button>`,
bindings: {
dialMom: '&'
},
controller: rulesPanelController,
});
function rulesPanelController(){
var self = this;
console.log("rulesPanelController");
self.callMom = function(){
console.log("Call mom");
self.dialMom({message:"Love you mom"});
}
self.CallBrother = function(){
console.log("Call brother");
}
}
angular.module('app').component('rulesEditor', {
template: '<h1>rulesEditor</h1>',
bindings: {
},
controller: rulesEditorController,
});
function rulesEditorController(){
console.log("rulesEditorController")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app">
<rules></rules>
</div>

You can use a semi Angular 2 component approach. Meaning you can use Input/output approach to achieve this.
I will give you an example and you can take it from there.
Let's say you have a header and a main component.
In your header component where you want to notify the main you can raise an event like this:
.component('headerComponent', {
template: `
<h3>Header component</h3>
<a ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
<a ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
`,
controller: function() {
this.setView = function(view) {
this.view = view
this.onViewChange({$event: {view: view}})
}
},
bindings: {
view: '<',
onViewChange: '&'
}
})
With binding view: '<' we specify that header component will be able to read outer something and bind it as view property of the own controller.
Header controller can be used like this:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
On the other hand main in simpler, it only needs to define input it accepts:
.component('mainComponent', {
template: `
<h4>Main component</h4>
Main view: {{ $ctrl.view }}
`,
bindings: {
view: '<'
}
})
And finally it all wired together:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
<main-component view="root.view"></main-component>
Here is a plunker.

Related

pass data between components with $scope.$emit()

I'm trying to pass data between two components that have the same parent component (simple version: I have child1 component, child2 component, and both of them are children of parent component). Also all the components are as controllers.
Parent component is also a root component (it doesnt have any parent components) so I thought I had to use $rootScope?
If i want to pass a var myVar (integer) from child1 to child2 I guess I should do :
in child1 component:
$rootScope.$emit('myEvent', vm.myVar);
in parent component:
$scope.$on('changeTab', function(){
console.log('parent', vm.myVar);
});
$scope.$broadcast('changeTab', vm.myVar);
in child2 component:
$rootScope.$on('changeTab', function () {
console.log('child2', vm.myVar);
});
I am new to this and don't understand why this doesn't work, probably nothing big, if anyone could help me out would appreciate it :) ty !
By the way I get "undefined" in both console logs, but well myVar is defined properly in child1.
You need to broadcast to the child's parent:
$scope.$parent.$broadcast('hi', {
msg: 'Hello there!'
});
$scope.$broadcast sends the message down the chain, but if you broadcast to the scope's parent then the parent will send the message down, which includes both siblings.
angular.module('appModule', [])
.controller('ParentController', [function() {
this.hello = 'I am the parent';
}])
.controller('Child1Controller', ['$scope', function($scope) {
this.hello = 'I am Child 1';
var that = this;
$scope.$on('hi', function(event, data) {
console.log(data.msg);
that.hello = data.msg;
});
}])
.controller('Child2Controller', ['$scope', function($scope) {
this.hello = 'I am Child 2';
this.sayHello = function(str) {
$scope.$parent.$broadcast('hi', {
msg: str
});
}
}]);
angular.bootstrap(window.document, ['appModule'], {
strictDi: true
});
div {
border: 1px dotted #f00;
padding: 15px;
}
<div ng-controller="ParentController as parentCtrl">
<p>{{parentCtrl.hello}}</p>
<div ng-controller="Child1Controller as child1Ctrl">
<p>{{child1Ctrl.hello}}</p>
</div>
<div ng-controller="Child2Controller as child2Ctrl">
<p>{{child2Ctrl.hello}}</p>
<button type="button>" ng-click="child2Ctrl.sayHello('Child 2 is cool')">Say Hello to Child 1</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>

Angularjs: Dynamically load directives in view with data

I am trying to make a dynamic framework with angularjs. I load sections with a webapi, that have data about the directive that it uses and the data that should be used in that directive. The data that I send can look like this:
[
{
id: "section1",
directive: "<my-directive></my-directive>",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "<my-other></my-other>",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
]
When I load this data, I convert the directives in to element with $compile.
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
}
So I can actually load this data in the view, like this:
<div ng-repeat="section in sections">
{{section.directive}}
</div>
First of all, this doesn't show up in my view, so how do I fix this?
Then the second issue I have. When I actually get this directive loaded into the view, how will I be able to access the data that should be used in this directive? I do have an id added to the sections.This is what I tried:
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
var parent = angular.element('#sectionsparent'); //The parent element has this id
parent.append(value.directive);
}
This way the section elements show up, but I cannot access the data that should be loaded inside the directive.
Thank you for your help in advance, let me know if you need more information.
EDIT:
When the directive is eventually loaded, I want to be able to access the data that belongs to that section. So if we take first section in the sample data, I want to be able to do the following in the template of the directive:
<!-- This file is myDirectiveTemplate.hmtl -->
<div id="{{id}}>
<h1>{{title}}</h1>
<p>{{text}}</p>
</div>
I don't care if I have to access these properties through a viewmodel object, so it would be {{vm.id}} instead of {{id}}. But I prefer to not have any function calls inside my template to actually get data.
Alright. There may be another way to accomplish this, or perhaps using includes instead of directives, but here's one way at least.
Taking your example code, you can follow your second route with $compile and append but you also need to pass an html-attribute for the isolate scope's content and bind it with a new $scope with the section added. (You also need to wrap in a $timeout so querying the DOM happens after it's initially rendered).
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope, $compile, $timeout) {
$scope.sections = [
{
id: "section1",
directive: "my-directive",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "my-other",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
];
// Need to timeout so rendering occurs and we can query the DOM.
$timeout(() => {
angular.forEach($scope.sections, (section) => {
let newScope = $scope.$new();
newScope.content = section.content;
let dir = section.directive;
let compiled = $compile(`<${dir} content="content"></${dir}>`)(newScope);
let parent = angular.element(document.querySelector('#' + section.id));
parent.append(compiled);
});
});
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<p>{{content.text}}</p>
</div>`,
};
});
app.directive('myOther', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<ul>
<li ng-repeat="item in content.list">{{item}}</li>
</ul>
</div>`,
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="AppCtrl">
<div ng-repeat="section in sections" id="{{section.id}}"></div>
</div>

Angular, queuing ng-init

Is it possible queuing ng-init?
Generally, in first init I want to add JSON file to prototype vars (array) and in another init depending on the params I want to skip getJsonData() or add other JSON file to prototype.
function init(param) {
console.log("startInit");
// big JSON file
var promise = getJSON(param);
return promise.then( function() {
//some func
console.log("finish");
return true;
});
};
function getJSON(param) {
var deferred = $q.defer();
console.log("startInitDataInner");
someService.getJsonData(param).then(function(data) {
// some code
console.log("endInitDataInner");
deferred.resolve();
}, function(error) {
deferred.reject();
});
return deferred.promise;
};
in view ng-init
ng-init="init(param)"
ng-init="init(param)"
// ...
and log:
startInit
startInitDataInner
startInit
startInitDataInner
endInitDataInner
finish
endInitDataInner
finish
//..
Edit:
Generally, I want to create something like plugin in jQuery. I have this code:
<div ng-controller="parentController as parent">
<div ng-controller="childController as child" ng-init="child.init(parent.data)"></div>
</div>
<div ng-controller="parentController as parent">
<div ng-controller="childController as child" ng-init="child.init(parent.data2)"></div>
</div>
and configurable part by user:
angular.module('myApp').controller('parentController', ['$scope', function($scope) {
this.data = {
config: {
lang: "en",
title: "title"
}
};
this.data2 = {
config: {
lang: "pl",
title: "title2"
}
};
}]);
ng-init update api:
angular.extend(this, parent.data);
Do you have any ideas how I should do it differently?
Well, if you are working with angular, you use controllers. What is controller itslfmin general meaning? Right, its a constructor function. The main word here is function. What does function in general? Run the code inside.
So, just place your initial logic at the beggining of controller code (but without wrapping it as a separate function) and it will run just in time your controller will be resolved by angular resolver.
var controller = function () {
// vars, costs, etc.
console.log("startInit");
// big JSON file
var promise = getJSON(param);
return promise.then( function() {
//some func
console.log("finish");
return true;
});
};

Ember.js use itemController if not following naming Conventions

According to official documentation, way to create itemcontroller is:
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
But I'm not setting my ArrayController to App. It is set to a local variable behind a function scope. And the itemController property can only be string (according to documentation). So how do I set the itemController property?
My code looks like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
)
});
App.ChannelController=Ember.Controller.extend({
channels:Channels,
}));
<script type="text/x-handlebars" data-template-name='channel'>
<div>
{{#each channel in channels.list}}
{{channel.label}}
{{/each}}
</div>
</script>
I don't want to pollute App namespace with itemControllers that is to be used locally.
Update
Suppose my channels is like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
),
selected:"display"
});
and I want to something like this in template:
<script type="text/x-handlebars" data-template-name='channel'>
<h1>{{channels.selected}}</h1>
<div>
{{#each channel in channels.list}}
<div {{bind-attr class="channel.isselected:active:inactive"}}>{{channel.label}}</div>
{{/each}}
</div>
</script>
so that it outputs:
<h1>display</h1>
<div>
<div class="active">Display</div>
<div class="inactive">Social</div>
<div class="inactive">Email</div>
</div>
How do I do it with components?
You'll likely want to read the guide of components to get the full picture, but the gist of it is that you want to replace all item controllers with components. However, components will also replace the template inside of the each block as well. I don't entirely understand what's going on in your code, but here's an example roughly based on your code.
// Component
App.ChannelDisplayComponent = Ember.Component.extend({
channel: null,
isSelected: function() {
// Compute this however you want
// Maybe you need to pass in another property
}.property('channel')
});
{{! Component Template }}
<div {{bind-attr class="channel.isSelected:active:inactive"}}>
{{channel.label}}
</div>
{{!Channels Template}}
{{#each channel in channels.list}}
{{channel-component channel=channel}}
{{/each}}
The component is essentially your item controller, only it gets its own template as well.
You really shouldn't be worried about polluting the app namespace (unless you're having naming collisions, but that's a different issue). And as Kitler said, you should move to components instead of item controllers. But if you want to do this, the best way I can think of is overridding the (private) controllerAt hook.
var ItemController = Ember.Controller.extend({});
App.PostsController = Ember.ArrayController.extend({
controllerAt: function(idx, object, controllerClass) {
var subControllers = this._subControllers;
if (subControllers.length > idx) {
if (subControllers[idx]) {
return subControllers[idx];
}
}
var parentController = (this._isVirtual ? this.get('parentController') : this);
var controller = ItemController.create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = controller;
return controller;
}
})

How to bind and automatically update view when $scope is updated in 2 different controllers in Angularjs

Here is a fiddle: http://jsfiddle.net/a0eLhcbm/
I have a simple setup:
<div ng-app="demo" ng-controller="PageController">
{{ page.time }}
<div ng-controller="UsernameController">
{{ user.name }}
</div>
</div>
There is a function that will, say, get the user.name from somewhere else using ajax, and that function belongs to controller PageController.
Question: Is there anyway I can make the {{ user.name }} within the UsernameController to update itself as soon as the controller PageController receives the information?
Here is my javascript setup:
var app = angular.module( 'demo', [] );
function_that_fetches_for_username = function() {
//Some function that fetch for username asynchronously
};
app.controller( 'PageController', function ( $scope ) {
//Initial data
$scope.page = {};
$scope.page.time = Date();
function_that_fetches_for_username();
//How can I make the UsernameController to update its view from this Controller as soon as this controller receives the information?
});
app.controller( 'UsernameController', function( $scope ) {
//Initial data
$scope.user = {};
$scope.user.name = "";
//How can I automatically updated the $scope.user.name in view as soon as the PageController receives the information about the username?
});
There are probably a lot of ways to solve this problem, my share to this is to use either of the two below:
[1] Create a service that you can share to any part of your application (Controllers, Services, Directives, and Filters).
In relation to your problem, you can simply create a User service that can be shared across your controllers. The solution below assumes that the function_that_fetches_for_username() is a service UserResource that has a method get() that simulates fetching data from a server. The User service is an empty object that is shared across all your controllers.
DEMO
JAVASCRIPT
angular.module('demo', [])
.service('UserResource', function($timeout) {
this.get = function() {
return $timeout(function() {
return {
id: 'w3g45w34g5w34g5w34g5w3',
name: 'Ryan'
};
}, 2000);
};
})
.service('User', function() {
return {};
})
.controller('PageController', function($scope, UserResource, User) {
$scope.page = {
time: Date()
};
UserResource.get().then(function(data) {
angular.extend(User, data);
});
})
.controller('UsernameController', function($scope, User) {
$scope.user = User;
});
HTML
<div ng-app="demo" ng-controller="PageController">
{{ page.time }}
<hr>
<div ng-controller="UsernameController">
<div ng-if="user.name">
{{ user.name }}
</div>
<div ng-if="!user.name" style="color: red">
Waiting for Response...
</div>
</div>
</div>
[2] Use the controllerAs syntax for declaring controllers. Use this type of notation for child controllers to access parent controllers using their aliases.
DEMO
JAVASCRIPT
angular.module('demo', [])
.service('UserResource', function($timeout) {
this.get = function() {
return $timeout(function() {
return {
id: 'w3g45w34g5w34g5w34g5w3',
name: 'Ryan'
};
}, 2000);
};
})
.controller('PageController', function(UserResource) {
var ctrl = this;
ctrl.page = {
time: Date()
};
ctrl.user = {};
UserResource.get().then(function(data) {
angular.extend(ctrl.user, data);
});
})
.controller('UsernameController', function() {
this.getUser = function(user) {
console.log(user);
};
});
HTML
<div ng-app="demo" ng-controller="PageController as PC">
{{ PC.page.time }}
<hr>
<div ng-controller="UsernameController as UC">
<div ng-if="PC.user.name">
{{ PC.user.name }}
</div>
<div ng-if="!PC.user.name" style="color: red">
Waiting for Response...
</div>
<button type="button" ng-click="UC.getUser(PC.user)"
ng-disabled="!PC.user.name">
Access user from Page Controller
</button>
</div>
</div>
You can do one of these for sharing the same value through multiple controllers:
Promote the value to a higher level scope all the interested controllers have access to. Controllers will get it through scope inheritance because angular automatically searches the value through the scope hierarchy.
Whoever gets the value broadcasts it through the higher level scope all the controllers have access to. All the controllers listening for this broadcast will get the value.
you can define your user in pageController(that is parent controller to UsernameController) now whenever you change it in pageController it will also be updated in usernameController
second solution is to have ng-view in parent, and in route use controller for UsernameController
index file
<div ng-app="demo" ng-controller="PageController">
{{ page.time }}
<ng-view></ng-view>
</div>
user.html
<div ng-controller="UsernameController">
{{ user.name }}
</div>
route codee
.when("/user",{
controller:"usernameController",
templateUrl : 'user.html'
})
Third solution is to make a service
.factory("userFactory",function(){
var user = {};
return{
setUser : function(usern){
user = usern;
},
getUser : function(usern){
return user;
},
}
})
now you can get user from service and set to service .

Categories