0

I need to click a button in one controller and have it trigger/run a function in a sibling controller using AngularJS.

In my (very simplified) example, I have a header component and a content component. I have two buttons that will change the color of an object (my real scenario is much more complex that this, however). I need to be able to call those same functions from another component.

Here is the Plunker example

INDEX.HTML

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css" />
    <link rel="stylesheet" href="style.css" />

    <script id="header-template" type="text/ng-template">
      <h1>HEADER</h1>
      <button type="button" ng-click="model.makeRed()">Make Red</button>
      <button type="button" ng-click="model.makeBlue()">Make Blue</button>
      <br />
      <br />
      <pre>{{model}}</pre>
      <hr />
    </script>

    <script id="content-template" type="text/ng-template">
      <h2>Content</h2>
      <div ng-class="model.colorme">Object to color</div>
      <br />
      <button type="button" ng-click="model.makeRed()">Make Red</button>
      <button type="button" ng-click="model.makeBlue()">Make Blue</button>
      <br />
      <br />
      <pre>{{model}}</pre>
    </script>

    <script src="script.js"></script>
  </head>

  <body>
    <div class="container">
      <header-component></header-component>
      <content-component></content-component>
    </div>
  </body>

</html>

SCRIPT.JS

console.clear();

function headerController() {
  var model = this; 
  model.test = "test header";

  console.log(model);
}

function contentController() {
  var model = this;
  model.test = "test content";

  model.makeRed = function() {
    model.colorme = "red";
  }

  model.makeBlue = function() {
    model.colorme = "blue";
  }
  console.log(model);
}


var app = angular.module("app", []);

app.component("headerComponent", {
    template: $("#header-template").html(),
    controllerAs: "model",
    controller: [headerController]
});

app.component("contentComponent", {
    template: $("#content-template").html(),
    controllerAs: "model",
    controller: [contentController]
});

STYLE.CSS

.blue { background-color: blue; color: white; }

.red { background-color: red; color: white; }
RichC
  • 7,829
  • 21
  • 85
  • 149
  • Have you tried something like [**this**](http://stackoverflow.com/a/29468486/4927984)? – Mistalis Apr 25 '17 at 15:00
  • Does it matter that I'm using components and those are just straight controllers? Also, I would think there would be some kind of binding method rather than using $rootscope (which I see people recommending against at times). – RichC Apr 25 '17 at 15:03
  • You can use those events in $scope instead of $rootScope – Mistalis Apr 25 '17 at 15:08
  • 1
    Do you have a parent component for those two components ? – Julien TASSIN Apr 25 '17 at 15:08
  • @JulienTASSIN no but I guess I could just add one if that's necessary to trigger functions in siblings? – RichC Apr 25 '17 at 15:14
  • 1
    @RichC : yep, it should do the trick. You can use angularjs component bindings to share functions or variables (with `= ` binding) – Julien TASSIN Apr 25 '17 at 15:16
  • Ok - I'm pretty new to angularjs so is there an example of this or can you update my plunker and post it as an answer so I can give you credit? – RichC Apr 25 '17 at 15:20
  • 1
    @RichC : for sure, i'll do it right now – Julien TASSIN Apr 25 '17 at 15:48

1 Answers1

1

Here are 3 ways to share data or functions across components :

The bad one :

Use < and = bindings to share functions between component is possible but very ugly and should be avoided.

Here is an example :

HTML

<script id="header-template" type="text/ng-template">
  <h1>HEADER</h1>
  <button type="button" ng-click="model.selectAll()" class="btn btn-sm btn-default">Select All</button>
  <button type="button" ng-click="model.deselectAll()" class="btn btn-sm btn-default">Deselect All</button>
  <br />
  <br />
  <hr />
</script>

<script id="content-template" type="text/ng-template">
  <h2>Content</h2>
  <button type="button" ng-click="model.selectAll()" class="btn btn-sm btn-default">Select All</button>
  <button type="button" ng-click="model.deselectAll()" class="btn btn-sm btn-default">Deselect All</button>
  <br />
  <br />
  <div class="item_container" multiple-selection-zone>
      <div ng-repeat="f in model.transaction.fields" multiple-selection-item class="well" ng-class="{'selecting': isSelecting ,'selected': isSelected || model.isSelected}">{{f.label}}</div>
  </div>
  <br />
  <br />
</script>

JS

function parentController(TransactionFactory) { 
  var model = this; 
  model.transaction = TransactionFactory;
  model.selectAll = null;
  model.deselectAll = null;
}

function headerController(TransactionFactory) {
  var model = this; 
  model.transaction = TransactionFactory;
}

function contentController(TransactionFactory) {
  var model = this;
  model.transaction = TransactionFactory;

  model.selectAll = function() {
    //mark all fields as selected
    model.isSelected = true;
  }

  model.deselectAll = function() {
    //deselect all fields that are selected
    model.isSelected = false;
  }

  this.$onInit = function() {
    model.cSelectAll = model.selectAll;
    model.cDeselectAll = model.deselectAll;
  }
}


var app = angular.module("app", ['multipleSelection']);

app.factory('TransactionFactory', function () {
    var transaction = {
        fields: [
          {label: "field 1"}, 
          {label: "field 2"}, 
          {label: "field 3"}, 
          {label: "field 4"}, 
          {label: "field 5"}]
    };
    return transaction;
});

app.component("parentComponent", {
    template: $("#parent-template").html(),
    controllerAs: "model",
    controller: ["TransactionFactory", parentController]
});

app.component("headerComponent", {
    template: $("#header-template").html(),
    controllerAs: "model",
    controller: ["TransactionFactory", headerController],
    bindings: {
      selectAll: '<',
      deselectAll: '<',
    }
});

app.component("contentComponent", {
    template: $("#content-template").html(),
    controllerAs: "model",
    controller: ["TransactionFactory", contentController],
    bindings: {
      cSelectAll: '=',
      cDeselectAll: '=',
    }
});

The plunker.

The correct one

HTML

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css" />
    <link rel="stylesheet" href="style.css" />

    <script id="header-template" type="text/ng-template">
      <h1>HEADER</h1>
      <button type="button" ng-click="model.makeRed()">Make Red</button>
      <button type="button" ng-click="model.makeBlue()">Make Blue</button>
      <br />
      <br />
      <pre>{{model}}</pre>
      <hr />
    </script>

    <script id="content-template" type="text/ng-template">
      <h2>Content</h2>
      <div ng-class="model.colorMe">Object to color</div>
      <br />
      <br />
      <br />
      <pre>{{model}}</pre>
    </script>

    <script id="root-template" type="text/ng-template">
      <header-component color-me="model.colorme" make-red="model.makeRed()" make-blue="model.makeBlue()"></header-component>
      <content-component color-me="model.colorme" make-red="model.makeRed()" make-blue="model.makeBlue()"></content-component>
    </script>

    <script src="script.js"></script>
  </head>

  <body>
    <div class="container">
      <root-component></root-component>
    </div>
  </body>

</html>

JS

console.clear();

function headerController() {
  var model = this;
  model.test = "test header";

  console.log(model);
}

function contentController() {
  var model = this;
  model.test = "test content";
  model.makeBlue();
  console.log(model);
}

function rootController() {
  var model = this;

  model.makeRed = function() {
    console.log('red');
    model.colorme = "red";
  }

  model.makeBlue = function() {
    console.log('blue');
    model.colorme = "blue";
  }
  console.log(model);
}


var app = angular.module("app", []);

app.component("rootComponent", {
  template: $("#root-template").html(),
  controllerAs: "model",
  controller: [rootController]
});

app.component("headerComponent", {
  template: $("#header-template").html(),
  controllerAs: "model",
  controller: [headerController],
  bindings: {
    makeRed: '&',
    makeBlue: '&',
    colorMe: '<',
  }
});

app.component("contentComponent", {
  template: $("#content-template").html(),
  controllerAs: "model",
  controller: [contentController],
  bindings: {
    makeRed: '&',
    makeBlue: '&',
    colorMe: '<',
  }
});

your plunker

With bindings you can share variable (colorMe) with < binding or functions (makeBlue and makeRed) with & binding accross components with the same parent.

The perfect one

You can use a angular.service to store and modify the state and share it between components.

Julien TASSIN
  • 5,004
  • 1
  • 25
  • 40
  • darn - I think I have to have the functions inside the contentComponent because I have to do some complex work inside that component. It's not as simple as just turning a property from blue to red. – RichC Apr 25 '17 at 16:45
  • I will give you that in a few hours. We can use a classic binding (< or =) to perform it. – Julien TASSIN Apr 25 '17 at 16:55
  • awesome, thank you! I'll see if I can figure it out on my own in the meantime as well. – RichC Apr 25 '17 at 17:06
  • wow - this thing is kickin my butt. Can't figure it out but I made closer scenario to mine in this plunker: https://plnkr.co/edit/IoezfScfM43XMeqvpon6?p=preview – RichC Apr 25 '17 at 20:23
  • @RichC : I updated your new plnkr (and the answer) : https://plnkr.co/edit/UTVO5vSMYrDktcVhjh1p?p=preview with an ugly solution that match your case. But you should definitely use service to perform such a think. A service example will come tomorrow in the answer :) – Julien TASSIN Apr 25 '17 at 22:04
  • The only issue with this now is that the directive's selecting and the select/deselect don't work in harmony with each other now but I opened up a new simplified question over here for that: http://stackoverflow.com/questions/43636438/how-to-transfer-a-directives-scope-into-a-controlleras-using-angularjs Thanks again!!! – RichC Apr 26 '17 at 14:03