Scope issues when declaring controller for a modal in AngularUI/Boostrap - angularjs

What I'm trying to do is have a custom directive inside a modal that just returns a list of files. The issue I'm having is that the scope seems to be different depending on how I declare my controller on my modal. Inside my modal I have a custom directive with an isolated scope that just returns a list of selected files. The first method I have is declaring it as a parameter in the modal creation.
$scope.openModal = function(){
uploadDialog = $modal.open({
templateUrl: 'modal.html',
size: 'lg',
controller:'modalController'
});
The second method I tried is declaring it at the top of the div of the modal template so I had to make a new div and wrap the whole modal template.
The second method returns everything fine, but the first method doesn't return it at all. I did notice while debugging that the "this" property has the value selectedFiles. Why does the two method yield different results?
Method 1 Plunker: http://plnkr.co/edit/6FTQq7fT49lETR5TEzaF?p=preview
Method 2 Plunker: http://plnkr.co/edit/QWnbH8GZArMgYqgcQ8L9?p=preview

To answer your question, please first see my comments in the DOM elements after a modal template has been compiled below:
Method 1:
<!-- Method 1 controller's scope is here, it is the same as modal's scope -->
<div class="modal fade in ng-isolate-scope">
<div class="modal-dialog modal-lg">
<!-- This ng-transclude create a new scope for each its children elements -->
<div class="modal-content" ng-transclude>
<div class="modal-header ng-scope">
<h3 class="modal-title">Test</h3>
</div>
<!-- The selectedFiles will be stored in this scope, not the controller scope above. -->
<div class="modal-body ng-scope">
<upload-dir files="selectedFiles" class="ng-isolate-scope">
<div>{{selectedFiles}}</div>
<button ng-click="clickHere(selectedFiles)">click here</button>
<div>From $scope: <input type="text" ng-model="test"></div>
<div>From parameter: <input type="text" ng-model="testParam"></div>
</div>
<div class="modal-footer ng-scope"></div>
</div>
</div>
</div>
Method 2:
<!-- The modal's scope is here -->
<div class="modal fade in ng-isolate-scope">
<div class="modal-dialog modal-lg">
<!-- This ng-transclude create a new scope for each its children elements -->
<div class="modal-content" ng-transclude>
<!-- Method 2 controller's scope is here -->
<div ng-controller="modalController" class="ng-scope">
<div class="modal-header">
<h3 class="modal-title">Test</h3>
</div>
<!-- There is no new scope created here, -->
<!-- so the selectedFiles will be stored in the controller's scope above -->
<div class="modal-body">
<upload-dir files="selectedFiles" class="ng-isolate-scope">
<div>{{selectedFiles}}</div>
<button ng-click="clickHere(selectedFiles)">click here</button>
<div>From $scope: <input type="text" ng-model="test"></div>
<div>From parameter: <input type="text" ng-model="testParam"></div>
</div>
<div class="modal-footer"></div>
</div>
</div>
</div>
</div>
As you can see, the controller's scope in Method 1 is not the nearst scope that the selectedFiles is defined, that why the $scope.selectedFiles and $scope.test are undefined.
You could workaround the issue by keeping the selectedFiles in some object before put it in scope, e.g. $scope.model.selectedFiles. Please see the plunker below for an example.
Method 1 Plunker (Modified): http://plnkr.co/edit/pP2L1ZJLxXJXgqR3QAIT?p=preview
Hope this clear things up!

Related

HTML form in ng-if environment

Structure of my app is like following:
<div class="parent">
<div class="child" ng-if="showChild">
<div class="child-view-1" ng-if="!isShown">
</div>
<div class="child-view-2" ng-if="isShown">
</div>
</div>
</div>
Inside child-view-2 I have form element, and of course, it is undefined in controller, probably beacuse of ng-if (as it creates child scope).
isShown variable just switches divs from child-view-1 to child-view-2.
What do you suggest, how can I make form visible all time in controller?
EDIT: My fault, the outer child is controlled by showChild flag...
You have an outer ng-if (on div class="child") based on the value of isShown. When isShown is true the content will be added to the DOM, and the class="child-view-1" will obviously not be added to the DOM because of ng-if="!isShown"
The second div's ng-if is redundant because it is the same condition as the outer one:
<div class="parent">
<div class="child" ng-if="isShown">
<div class="child-view-1" ng-if="!isShown">
<!-- THIS WILL NEVER BE DISPLAYED -->
</div>
<div class="child-view-2" ng-if="isShown">
<!-- THIS WILL ALWAYS BE DISPLAYED WHEN isShown IS TRUE -->
</div>
</div>
</div>

Need to call parent function from nested directive angularjs

Below is my flow of application. I wanted to access the parent controller function from the child directive
<div ng-controller="ParentController">
<first-directive></fist-directive>
</div>
in First Directive another directives loading
<div ng-controller="FirstController">
<second-directive></second-directive>
</div>
from second-directive calling parent function
<div ng-controller="SecondController">
<a ng-click="ParentFunction(2342)"></a>
</div>
ParentFunction() is available in Parent Controller. I wanted to call the function from second-directive.
ParentController-->FirstDirective-->SecondDirective
How to call the parent function from SecondDirective for my scenario?
Make the first directive and second directive to inherit the parent scope(don't define scope property for them). If they are isolated scope then they can not access parent scope.
Then in the second directive do this -
<div ng-controller="SecondController">
<a ng-click="$parent.$parent.ParentFunction(2342)"></a>
</div>
EDIT:
If the directives have isolated scope as OP has commented here, then you have to pass the function to directive, so your code becomes like this
<div ng-controller="ParentController">
<first-directive function-to-call="parentFunction()"></fist-directive>
</div>
<div ng-controller="FirstController">
<second-directive function-to-call="functionToCall()"></fist-directive>
</div>
<div ng-controller="SecondController">
<a ng-click="functionToCall(2342)"></a>
</div>
And the in the directive you have to have some values to scope like this
first Directive
scope: {
functionToCall: '&'
}
Second Directive
scope: {
functionToCall: '&'
}

Filter in ng-repeat not applying

I'm working on a Ionic project which has a view that shows a scrollable list with a search input that filters the displayed results, this view is located in a template that is loaded into another view.
We noticed the search input would move with the content when the scrolling began, disappearing making it a problem when you had scrolled and wish to filter the results.
I tried to workaround this by pulling the search input outside the template and placing it above where it is loaded in the parent view.
While this renders the search input at a fixed location, even though I specified the controller that handles its logic, the filter isn't applied on the list.
Here is how my code looks on the parent view:
<ion-popover-view id="popfecha" class="fit" >
<ion-view view-title="Nuevo ReSAT">
<ion-content class="padding" data-ng-hide="activity.state.notes || activity.state.reasonShow || activity.state.product_lineShow || activity.state.account || activity.state.contact " >
<form name="form" data-ng-submit="submitForm()">
<!-- A long form -->
</form>
</ion-content>
<ion-view class="wrapper" data-ng-show="activity.state.account" >
<!-- Filter section -->
<div data-ng-controller="AccountCatalogueCtrl" class="item item-input filterSearch bar-header popupBorder borderFirstItemPopup">
<label class="item-input-wrapper">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" class="form-control"
ng-model="filterText.name">
</label>
</div>
<!-- ./Filter section -->
<ion-content class="padding" data-ng-show="activity.state.account" >
<div data-ng-controller="AccountCatalogueCtrl" ng-include="'templates/account-catalogue.html'"></div>
</ion-content>
</ion-view>
<ion-content class="padding" data-ng-show="activity.state.notes" >
<div data-ng-controller="ActivityNotesCtrl" ng-include="'templates/activity-notes.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.reasonShow" >
<div data-ng-controller="ReasonCatalogueCtrl" ng-include="'templates/reason-catalogue.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.product_lineShow" >
<div data-ng-controller="ProductLineCatalogueCtrl" ng-include="'templates/product-line-catalogue.html'"></div>
</ion-content>
<ion-content class="padding" data-ng-show="activity.state.contact" >
<div data-ng-controller="ContactCatalogueCtrl" ng-include="'templates/contact-catalogue.html'"></div>
</ion-content>
</ion-view>
</ion-popover-view>
As you can see, I'm specifying the controller AccountCatalogueCtrl to the div that holds the search input and to the div that loads the template account-catalogue.html.
Here's my template:
<ion-list>
<ion-radio class="item-avatar popupBorder borderLastItemPopupIterator whiteBackground" ng-repeat="acc in accountCatalogue | orderBy:'-name' : true | filter:filterText track by acc._id"
type="item-text-wrap" ng-value="acc.name" ng-if="!acc.isDivider" ng-click="select(acc); go()" >
<img src="img/account.png">
<h2>{{acc.name}}</h2>
</ion-radio>
</ion-list>
<ion-infinite-scroll
ng-if="canLoadAccounts"
on-infinite="loadAccounts()"
distance="5%">
</ion-infinite-scroll>
I even placed a watch on the filterText object to see it was being updated, the watch did show me that the property name was changing.
This is my controller:
var mod = angular.module('starter.controllers');
mod.controller('AccountCatalogueCtrl', ['$scope', '$rootScope', '$location', '$timeout', function ($scope, $rootScope, $location, $timeout) {
$scope.select = function(acct){
$rootScope.activity.accountId = acct._id;
};
$scope.go = function ( ) {
$timeout(function(){
$scope.$emit('HideAccount');
},150);
};
// Added the lines below to see if the filter was changing
// Since in the beginning filterText doesn't exist undefined is returned
// Later when a value is placed in the input then the filterText object is created
$scope.$watch('filterText.name', function(n, o){
console.log('filter updated ' + JSON.stringify(n) + ' ' + JSON.stringify(o));
});
}]);
If I place the search input inside my template, the filter is applied, but when placed outside the filter isn't applied. Am I doing something wrong in my implementation? How can I make sure the filter applies even if the component that received it is outside the template that makes use of the filter?
I solved this issue by removing the controller from the search input, so it looked like this:
<!-- Filter section -->
<div class="item item-input filterSearch bar-header popupBorder borderFirstItemPopup">
<label class="item-input-wrapper">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" class="form-control"
ng-model="filterText.name">
</label>
</div>
<!-- ./Filter section -->
I saw that I could access to filterText on my controller AccountCatalogueCtrl due to the component that shows the catalogue has access to the scope variables from the parent component that it is holding it. This way I could change the value outside AccountCatalogueCtrl and affect the filter inside the list.

Why is my ng-hide / show not working with my ng-click?

I want to show the elements that contain displayCategory.name with the ng-click above it, but it's not working as expected.
.divider-row
.row-border(ng-hide="showMe")
.row.row-format
.col-xs-12.top-label
Find where you stand
%hr.profile
.row.labelRow
.col-xs-12
%ul
%li(ng-repeat='category in service.categories')
.clear.btn.Category(ng-click='thisCategory(category) ; showMe = true') {{category.name}}
.divider-row
.row-border(ng-show="showMe")
.row.row-format
.col-sm-12.col-md-12.top-label.nopadLeft
What do you think about {{displayCategory.name}}
I dropped your haml into a converter and this is what it spat out (clearly incorrect):
<div class="divider-row">
<div class="row-border">(ng-hide="showMe")
<div class="row row-format">
<div class="col-xs-12 top-label">
Find where you stand
</div>
</div>
<hr class="profile"/>
<div class="row labelRow">
<div class="col-xs-12">
<ul>
<li>(ng-repeat='category in service.categories')
<div class="clear btn Category">(ng-click='thisCategory(category) ; showMe = true') {{category.name}}</div>
</li>
</ul>
</div>
</div>
</div>
<div class="divider-row"></div>
<div class="row-border">(ng-show="showMe")
<div class="row row-format">
<div class="col-sm-12 col-md-12 top-label nopadLeft">
What do you think about {{displayCategory.name}}
</div>
</div>
</div>
</div>
So after some quick googling I found that you should be writing it like this:
.divider-row
.row-border{"ng-hide" => "showMe"}
.row.row-format
.col-xs-12.top-label
Find where you stand...
As that will convert to what you need:
<div class="divider-row">
<div class="row-border" ng-hide="showMe">
<div class="row row-format">
<div class="col-xs-12 top-label">
Find where you stand
Using curly braces instead of round ones for attributes
I cannot run your code to verify, but I think the problem is that the binding property showMe should be replaced with some object like status.showMe.
For example, define $scope.status = { showMe: false}; outside the ng-repeat (in your controller maybe).
Please check a working demonstration: http://jsfiddle.net/jx854d3y/1/
Explanations:
ng-repeat creates a child scope for each item. The child scope prototypical inherits from the parent scope. In your case, the primitive showMe is assigned to the child scope. While you use it outside the ng-repeat, where it tries to get the value from the parent scope, which is undefined. That is why it is not working.
Basic rule is: always use Object, instead of primitive types, for binding.
For more details, please refer to: https://github.com/angular/angular.js/wiki/Understanding-Scopes

Using AngularJS, how do I set $scope properties in the controller of the parent page

Thanks for looking.
I have the following markup for a modal which shares the same angular controller as it's parent page:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<div class="row" ng-controller="EventCreateController">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
controller: 'EventCreateController',
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
Please note the Event.Item.VideoUrl model reference.
The modal allows a user to set the URL of a video, and the goal is to have that set $scope.Event.Item.VideoUrl in the controller and then close the modal. The parent page and the modal both share the same controller, so I had hoped that this would work.
The modal behavior is fine (opens and closes as it should), but the $scope.Event.Item.VideoUrl property is not getting set.
Any advice is appreciated.
Problem Solved!
Thanks to Bogdan Savluk, I realized that I had a scope inheritance problem. So, removing both the explicit reference to the controller in the modal HTML as well as in the JavaScript constructor, resolved my problem:
<!-- START Add Event Video -->
<script type="text/ng-template" id="EventVideo.html">
<div class="event-modal">
<div class="modal-header"><h3>Event Video</h3></div>
<div class="modal-body">
<p>Please enter the URL of either a <strong>YouTube</strong> or <strong>Vimeo</strong> video.</p>
<span ng-if="!Event.VideoUrlIsValid" style='color:#9f9f9f;'>This doesn't look like a valid YouTube or Vimeo Url. Your video may not work.</span>
<!-- <div class="row" ng-controller="EventCreateController"> <--REMOVE THIS! -->
<div class="row">
<div pr-form-input span="12" name="videoUrl" ng-model="Event.Item.VideoUrl" placeholder="YouTube or Vimeo URL" isRequired="false" no-asterisk></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" ng-click="Event.UI.EventVideoModal.Close()">Done</button></div>
</div>
</script>
<!-- END Add Event Video -->
And here is the relevant JavaScript:
EventVideoModal: {
Open: function () {
$scope.EventVideoModal = $modal.open({
templateUrl: 'EventVideo.html',
//controller: 'EventCreateController', <--REMOVE THIS!!
scope: $scope
});
},
Close: function () {
$scope.EventVideoModal.close();
}
}
If you are passing scope to $modal.open() than scope for modal would be created as child scope from passed scope... - so you will have access to all properties from it.
But in case when you are passing the same controller to it - that controller would be applied to new scope and will override all properties from parent.
So in general, as I see the only thing you need to do to achieve desired result is to remove controller from configuration passed to $modal.open() or replace it with something that is specific only for that modal.

Resources