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; }
8
  • Have you tried something like this? Commented Apr 25, 2017 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). Commented Apr 25, 2017 at 15:03
  • 1
    Do you have a parent component for those two components ? Commented Apr 25, 2017 at 15:08
  • 1
    @RichC : yep, it should do the trick. You can use angularjs component bindings to share functions or variables (with = binding) Commented Apr 25, 2017 at 15:16
  • 1
    @RichC : for sure, i'll do it right now Commented Apr 25, 2017 at 15:48

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

6 Comments

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.
I will give you that in a few hours. We can use a classic binding (< or =) to perform it.
awesome, thank you! I'll see if I can figure it out on my own in the meantime as well.
wow - this thing is kickin my butt. Can't figure it out but I made closer scenario to mine in this plunker: plnkr.co/edit/IoezfScfM43XMeqvpon6?p=preview
@RichC : I updated your new plnkr (and the answer) : 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 :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.