4

The site that I'm working on has a custom CMS, and I'm using angularjs for inline page editing. The database stores HTML used to generate the pages, so I can't strictly use client-side templates and models.

I'm not using HTML5 pushState, so the URLs are like example.com/page#/widget/1/edit or example.com/page#/widget/new, and my current issue is that when I submit a form to edit a widget or add a widget, then I want the page to reload its content via injecting HTML into the DOM and change the URL to the default, which I'm trying with $location.path('/').

My current implementation has multiple issues (which you'll see in the demo), one of which is that in my form submit() callback, changing $location.path only changes the URL on a second form button click. I also try to change the URL which I click 'Delete', and that doesn't work either.

http://jsfiddle.net/sZrer/1

customPage.controller('PageEditCtrl', function($scope, $http, $routeParams, $location, $route, $compile, PageState) {
    var widgetId = $routeParams.id || widgetCount + 1;

    $scope.pageState = PageState;
    $scope.widget = { id: widgetId, name: 'Widget ' + widgetId };
    $scope.submit = function() {
      if ($scope.widget.id > widgetCount) widgetCount += 1;
      setTimeout(function() {
        var element = $compile(genHtml())($scope);
        angular.element('#page-content').html(element);
        $location.path('/');
      }, 100);
    };
  });
joshm1
  • 553
  • 1
  • 10
  • 21

1 Answers1

8

setTimeout() doesn't mix well with Angular (Angular uses an event loop in which it checks for changes to models, but to be able to do that it has to be able to control all parts of your app; setTimeout() is beyond the control of Angular, and when any changes to Angular-parts of your app are made in a setTimeout-handler, it usually takes another 'round' of the Angular event loop before those changes are picked up).

If you replace it with Angular's own $timeout() (be sure to also inject it), it works better (jsfiddle).

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • Good to know, but I only used setTimeout as a replacement for the actual implementation that uses $http.get(...).then(callback), which also causes a problem where $location.path updates but none of the controllers are called and my ng-show and ng-click bindings don't work. I'll update the example to use $http so it's closer to the actual implementation. – joshm1 Jul 20 '13 at 16:08
  • Since you technically answered the question about the second click, I'll reward you the answer. But here is what I should've posted (http://jsfiddle.net/sZrer/2/). You'll see that if you click Show Admin Controls > New Widget > Submit, and then click "Hide Admin Controls", the ng-show bindings in the new content body no longer work. I also don't like how I did had to set the delete link as a directive (doesn't feel idiomatic), but it's the only way I could get the click handler to work between controllers. – joshm1 Jul 20 '13 at 16:19
  • @joshm1 not entirely sure, but it looks like a scoping issue, esp since you're regenerating the Widget elements. Instead of creating new DOM elements, perhaps you could use an `ng-repeat` to render each widget? – robertklep Jul 20 '13 at 19:17
  • This helped me as well, I had a problem where a `$location.path('/')` was not taking effect, but it was part of a callback function after some other events took place. Moving the `location` before the other functions sorted the problem out - I made the assumption the callback timing was messing with Angular's event loop – Pete Jul 28 '13 at 18:19