Angular 1.5 Parent-Child Component Issue - angularjs

I'm using Angular 1.5 Component router and having trouble getting a scope variable in the parent to be accessible in child components. I've created a plunker here that illustrates the problem. I've created a parent component with this view:
<nav>
<ul class="linkList">
<li><a ng-class="{selected: $ctrl.isSelected('Applications')}" ng-link="['Applications', {search:$ctrl.search}]">Applications</a></li>
<li><a ng-class="{selected: $ctrl.isSelected('Processes')}" ng-link="['Processes']">Processes</a></li>
<li><a ng-class="{selected: $ctrl.isSelected('Tasks')}" ng-link="['Tasks']">Tasks</a></li>
<li><a ng-class="{selected: $ctrl.isSelected('Resources')}" ng-link="['Resources']">Resources</a></li>
</ul>
<div class="filter">
Filter: <input type="search" ng-model="$ctrl.search" />
</div>
<div class="clear"></div>
</nav>
<ng-outlet></ng-outlet>
Notice the "search" variable in the parent component view. I want this to be accessible to child components, but it's not working for me. I've seen examples that show child components being directly referenced in parent components like the following:
<application-grid search="$ctrl.search"></application-grid>
However, doesn't this defeat the purpose of the ng-outlet? I don't think I should have to manually pass parameters to child components like this right? What is the right way to do this?

Just stumbled upon this - hoping you found the solution but if not here it is.
You can get this to work easily using the require property.
Just add this to you child component set up and bingo:
require: {
parent: '^app'
}
Then to access the parent scope object do $ctrl.parent.search.
Note you don't have to call it parent - it can be what ever name you like.
I've forked your plunkr - see it working http://plnkr.co/edit/epPg2xWY6IYJN2Fnvg61

You can access the route parameters through the $routerOnActivate lifecycle hook, which gets called automatically when the route transitions to that component. Here's the example from the Angular docs:
function HeroDetailComponent(heroService) {
var $ctrl = this;
this.$routerOnActivate = function(next, previous) {
// Get the hero identified by the route parameter
var id = next.params.id;
return heroService.getHero(id).then(function(hero) {
$ctrl.hero = hero;
});
};
...
Also, it's worth noting that components created with angular.module().component() always have isolate scopes - i.e., you can't access scope variables from the parent scope in them. They have to be explicitly passed down, either through the router or through the bindings.
For more information, you should take a look at the Component Router section of the Angular developer guide - the documentation for the new router is really sparse, but that guide does a good job of explaining how it all works.

Related

How to preserve scope data when changing states with ui-router?

I'm building a single page web app using AngularJS with ui-router. I have two different states, one parent and one child. In the parent state, 'spots', users can make a selection from an ng-repeat and their selection is shown using the scope.
When a user makes the selection, I have ng-click fire a function which uses $state.go to load the child state 'details'. I would like to load their selection in the child state, but it appears that the scope data is gone?
I've tried using the same controller for each state. ui-sref doesn't work either.
From the parent state HTML template
<div class="card-column mx-0" data-ng-click="makeSelection = true">
<div class="card mx-0 mb-3 ng-scope" data-ng-click="showSpot(spot);" data-ng-repeat="spot in spots | filter:{'game':gameID} | filter:{'walking':distanceID} | filter:{'vehicle':vehicleID} | orderBy:'price' | filter as results">
<div class="row no-gutters">
<div class="col-sm-12 col-md-3 col-lg-3">
<img src="{{ spot.image }}" alt="parking spot"/>
</div>
<div class="col-sm-12 col-md-9 col-lg-9">
<div class="card-body px-4 pt-4">
<h6 class="text-small-extra text-muted font-weight-normal text-uppercase"><span style="letter-spacing: .05rem;">{{ spot.type }}</span></h6>
<h5 class="card-title">{{ spot.address }}</h5>
<h4 class="text-muted float-md-right">${{ spot.price }}<span style="font-size: 1rem; font-weight: 400">/day</span></h4>
</div>
</div>
</div>
</div>
Snippet from the controller
$scope.showDetails = function() {
$state.go('spots.details'); //my route...
}
$scope.showSpot = function(spot) {
$scope.spot = spot;
$scope.showDetails();
}
Snippet from app.js
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/")
$stateProvider
.state('spots',{
url: '/',
templateUrl: "/parkit/master/spots-available.html",
controller: 'parkitController'
})
.state('details', {
parent: 'spots',
url: '/details',
templateUrl: '/parkit/master/details.html',
})
.state('statetwo', {
url: '/statetwo',
template: '<h1>State Two</h1>',
controller: 'parkitController'
});
})
I expected the user selection to show on the child state after ng-click is fired.
You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with
$scope.value = 'something';
In a child component if you access $scope.value the inheritance chain will find $scope.value.
If the child sets
$scope.otherValue = 'something';
If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.
You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data
$scope.data = { value: 'something' };
Now if the child puts a property on the data object
$scope.data.otherValue = 'something';
It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.
let parent = {
value: 'some value',
data: { value: 'some value' }
};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);
Short answer is to just never inject $scope and use controllerAs syntax.
To share data between controllers you use a service that is injected to both controllers. You have the spots collection on the service and use a route param to identify which spot the other controller should use or have a place on the service called currentSpot set by the other controller.
Services are a singleton object that you create at the module level and then all controllers that ask for them in their dependency list get the same instance. They are the preferred way to share data between controllers, $scope hierarchies are bound to lead to confusion as the prototypal inheritance nature of them can be confusing. A child $scope is prototypally inherited from it's parent, this seems like you should be sharing data but when a child controller sets a property it is not visible to the parent.
You are learning an outdated way of Angular programming. Injecting $scope is no longer a recommended way. Look at using components. Components are a wrapper for a controller with an isolated scope and using contollerAs syntax. Isolated scopes make it much cleaner to know where data comes from.
Take a look at my answer on this question
Trying to activate a checkbox from a controller that lives in another controller

Multiple components scope conflict

I am working on an admin panel application which is based on AngularJS 1.5. It is structured as follows:
There is a main component called app, in which I am using all other components.
app.html
<div class="wrapper">
<message-component></message-component>
<header></header>
<leftbar></leftbar>
<body></body>
</div>
In the body component, I am using ui-view to render my component based on the current route.
At one time, three components are usually present : header, leftbar and body. I am using ng-if directive in my leftbar component in which I am calling a function.
leftbar.html
<li ng-if="leftbar.hasPermission('FOO')">
<a ui-sref="foo">
<span>Demo Page</span>
</a>
</li>
leftbar.controller
class LeftbarController {
constructor() {}
hasPermission(name) {
return user[name];
}
}
It basically shows the <li> if the user has a permission named "FOO".
Now, simultaneously my body component is also visible to the user and there is an input field present inside that, so whenever I write something in that field hasPermission method also gets called which is present in a different component.
So what can be the issue here? Why scopes are behaving like this ?

How can I have an abstract parent state know which child state is active?

Using UI-router, I have an abstract parent state with 6 child states. The child states are loaded using tabs on the parent state template. Using ng-class, I want the active tab to be highlighted. I can set a $scope.active_tab variable and change it every time the user clicks a new tab, but that won't work if a child state is navigated to directly via url.
You could use ui-sref-active directive, which allows you to add classes to an element when the related ui-sref state is active
<ul>
<li ui-sref-active="active" class="item">
<!-- ... -->
</li>
<!-- ... -->
</ul>
If you have an abstract state that is the parent of other states, then the abstract state and its controller will be loaded first, then the child states will be loaded after the parent state has finished loading.
In the parent controller you can then listen for the $stateChangeSuccess event, and perform your logic there.
$scope.$on('$stateChangeSuccess', function (event) {
// perform your logic here
});

ngRoute RouteController getting called only once

I've a fiddler to show my setup.
http://jsfiddle.net/smartdev101/dt6kkyy3
The problem is the routeController function is getting called only once on the page load, but then on hash change (as a result of link click), the function routeController is not getting called again.
I've my links generated in another ng module, not sure if the links have to be with in the same module as the router.
<div id="router">
<div ng-view></div>
</div>
<div id="navigation" data-ng-cloak>
<ul id="folios" data-ng-controller="FoliosController" class="nav nav-pills nav-stacked">
<li data-ng-repeat="folio in folios" ng-class="{active: isActive('/search/{{folio.productId}}')}">
{{folio.title}}
</li>
</ul>
<div ui-view></div>
</div>
Edit:
Problem has been further diagnosed. Any list/anchor that was generated by an ng controller, is not responding to hash changes outside the scope, while the static links are. from the plunkr link, "foo" and "bar" clicks trigger the controller but "abc" and "def" are not. please review. Question is how to make "abc" and "def" (dynamic links) respond to changes as well.
http://plnkr.co/edit/ea1OHa?p=preview
The code is behaving as intended with the way you have it coded. You are calling routeController in the constructor of the controller, but its not being invoked anywhere else (the router isn't going to do that either).
If you need the routeController function to be invoked on every route change consider binding to route changing events:
https://docs.angularjs.org/api/ngRoute/service/$route
Take a look at this plunk. Note I am binding to stateChangeSuccess instead since you are using the ui-router instead of ngRoute.
http://plnkr.co/edit/CiYLqnqAzrXoouMSXkuk

get ng-click, on injected element, to change the class of an element already in the view?

I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!

Resources